# 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 # 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], 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 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 for pattern, code in self.mapping.items(): if pattern == "default": continue if pattern in title: self.queue.put ( (code, None) ) return print("Matching '%s' to default" % title) # Get the default mapping to apply if there is any defined 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)