aboutsummaryrefslogtreecommitdiff
path: root/win32.py
blob: bff980b681842eb6b51a56dc41c60479a060a408 (plain)
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
# 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

# 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
EVENT_OBJECT_FOCUS = 0x8005
EVENT_OBJECT_NAMECHANGE = 0x800C
WINEVENT_OUTOFCONTEXT = 0x0000

eventTypes = {
    #win32con.EVENT_SYSTEM_FOREGROUND: "Foreground",
    EVENT_OBJECT_FOCUS: "Focus",
    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], queue):
        self.WinEventProc = WinEventProcType(self.callback)
        self.mapping = mapping
        self.queue = queue

    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 != windll.user32.GetForegroundWindow():
            # Only check the active window, the events received from other
            # windows are ignored.
            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()
        for pattern, code in self.mapping.items():
            if pattern == "default":
                continue
            if pattern in title:
                self.queue.put ( (code, None) )
                return
        default = self.mapping.get("default", None)
        if default is not None:
            self.queue.put ( (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)