aboutsummaryrefslogtreecommitdiff
path: root/lib/errors/dune
diff options
context:
space:
mode:
authorSébastien Dailly <sebastien@dailly.me>2024-12-11 22:20:24 +0100
committerSébastien Dailly <sebastien@dailly.me>2024-12-12 14:19:32 +0100
commit5a558038874765f20b9dc1bcb1890600e2a2065d (patch)
tree215906e1a7e2eb1597e19d81c000fbd63ccbb057 /lib/errors/dune
parent6b377719c10d5ab3343fd5221f99a4a21008e25a (diff)
Correction in the cursor displayed in the console
Diffstat (limited to 'lib/errors/dune')
0 files changed, 0 insertions, 0 deletions
5' href='#n115'>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)