1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
|
# Required for the window title name
from ctypes import wintypes, windll, create_unicode_buffer, WINFUNCTYPE
from typing import Optional, Dict
from zope import interface
from interfaces import desktopEvent
import win32api
import win32gui
import win32con
from zope import component
from interfaces.message import IMessage, Debug
from consumer import Mapping
# This code tries to hook the Focus changes events with the callback method.
# The types of events we want to listen for, and the names we'll use for
# them in the log output. Pick from
# http://msdn.microsoft.com/en-us/library/windows/desktop/dd318066(v=vs.85).aspx
WINEVENT_OUTOFCONTEXT = 0x0000
eventTypes = {
#win32con.EVENT_SYSTEM_FOREGROUND: "Foreground",
win32con.EVENT_OBJECT_FOCUS: "Focus",
win32con.EVENT_OBJECT_NAMECHANGE: "NameChange",
win32con.EVENT_OBJECT_SHOW: "Show",
#win32con.EVENT_SYSTEM_DIALOGSTART: "Dialog",
#win32con.EVENT_SYSTEM_CAPTURESTART: "Capture",
#win32con.EVENT_SYSTEM_MINIMIZEEND: "UnMinimize"
}
WinEventProcType = WINFUNCTYPE(
None,
wintypes.HANDLE,
wintypes.DWORD,
wintypes.HWND,
wintypes.LONG,
wintypes.LONG,
wintypes.DWORD,
wintypes.DWORD
)
def setHook(WinEventProc, eventType):
""" Register the hook foo being notified when the window change
"""
return windll.user32.SetWinEventHook(
eventType,
eventType,
0,
WinEventProc,
0,
0,
WINEVENT_OUTOFCONTEXT
)
@interface.implementer(desktopEvent.IDesktop)
class Listener(object):
def __init__(self, mapping: Dict[str, str]):
self.WinEventProc = WinEventProcType(self.callback)
self.mapping = mapping
def getForegroundWindowTitle(self) -> Optional[str]:
""" Get the window title name.
Example found from https://stackoverflow.com/a/58355052
See the function in the winuser librarry :
https://learn.microsoft.com/en-us/windows/win32/api/winuser/
"""
hWnd = windll.user32.GetForegroundWindow()
length = windll.user32.GetWindowTextLengthW(hWnd)
buf = create_unicode_buffer(length + 1)
windll.user32.GetWindowTextW(hWnd, buf, length + 1)
# 1-liner alternative: return buf.value if buf.value else None
if buf.value:
return buf.value
else:
return None
def callback(self, hWinEventHook, event, hwnd, idObject, idChild, dwEventThread,
dwmsEventTime) -> None:
if hwnd is None:
return
length = windll.user32.GetWindowTextLengthW(hwnd)
title = create_unicode_buffer(length + 1)
windll.user32.GetWindowTextW(hwnd, title, length + 1)
if title.value is None:
return
title = str(title.value).lower()
if title == "":
return
foreground_hwnd = windll.user32.GetForegroundWindow()
if event != win32con.EVENT_OBJECT_FOCUS and foreground_hwnd != hwnd:
return
for pattern, code in self.mapping.items():
if pattern == "default":
continue
if pattern in title:
component.handle(Mapping((code, None)))
return
# Get the default mapping to apply if there is any defined
# This only applies when the window raising the event is the main
# window
if hwnd == foreground_hwnd:
# We are not allowed to display message into the handler, because
# the GIL is released in the callback. We can’t modifiy elements
# from other threads, as it’s create weird errors :
#
# Fatal Python error: PyEval_RestoreThread: the function must be
# called with the GIL held, but the GIL is released (the current
# Python thread state is NULL)
print("Mapping '%s' to default" % title)
component.handle(Mapping((default, None)))
def start(self) -> None:
self.hookIDs = [setHook(self.WinEventProc, et) for et in eventTypes.keys()]
if not any(self.hookIDs):
print('SetWinEventHook failed')
sys.exit(1)
def stop(self) -> None:
for hook in self.hookIDs:
windll.user32.UnhookWinEvent(hook)
|