# HG changeset patch # User sqwishy # Date 1515296168 28800 # Sat Jan 06 19:36:08 2018 -0800 # Node ID bd2473983680685b5ed9b6f056ac623b3700b99d # Parent 71a4c853b7883045901d6db888cc7e26131201a0 fixes silly explosion in winhooker because lifetimes diff --git a/setup.py b/setup.py --- a/setup.py +++ b/setup.py @@ -6,7 +6,7 @@ setup( name = "WinRustler", - version = "0.9.0", # Keep in sync with winrustler/__init__.py + version = "0.9.1", # Keep in sync with winrustler/__init__.py author = "somebody", author_email = "somebody@froghat.ca", description = ("Thing for rustling windows in Windows."), diff --git a/winrustler/__init__.py b/winrustler/__init__.py --- a/winrustler/__init__.py +++ b/winrustler/__init__.py @@ -1,1 +1,1 @@ -__version__ = '0.9.0' # Keep in sync with ../setup.py +__version__ = '0.9.1' # Keep in sync with ../setup.py diff --git a/winrustler/ui/__main__.py b/winrustler/ui/__main__.py --- a/winrustler/ui/__main__.py +++ b/winrustler/ui/__main__.py @@ -27,7 +27,7 @@ import signal def quit_on_sigint(*args): - print("CTRL-C handled, quitting...") + logger.info("CTRL-C handled, quitting...") qApp.quit() signal.signal(signal.SIGINT, quit_on_sigint) diff --git a/winrustler/ui/app.py b/winrustler/ui/app.py --- a/winrustler/ui/app.py +++ b/winrustler/ui/app.py @@ -20,7 +20,6 @@ class WindowSet(QObject): """ This is very much like the WindowDiscovery but it has a signal ... """ - discovered = pyqtSignal(set, set) def __init__(self, *args, **kwargs): @@ -38,7 +37,6 @@ class WinRustlerApp(QApplication): - rustled = pyqtSignal(object) suggested = pyqtSignal(object) @@ -48,6 +46,9 @@ self.windisc = WindowDiscovery(self.winset.sync) self.discovery_timer = QTimer(self, interval=200, singleShot=True) # refresh debounce self.hooker = WinHooker(self) + # Some goofy meme because we get window events after the QObject is + # deleted but not the python object. + self.aboutToQuit.connect(self.hooker._unhook) # Use a windows event hook to determine when we might want to update # the list of windows. Connect it to the debounce timer. self.hooker.event.connect(self.discovery_timer.start) diff --git a/winrustler/ui/state.py b/winrustler/ui/state.py --- a/winrustler/ui/state.py +++ b/winrustler/ui/state.py @@ -1,27 +1,32 @@ import logging +import attr +from attr.exceptions import NotAnAttrsClassError + from PyQt5.QtCore import QSettings from winrustler.ui.debug import log_exceptions + logger = logging.getLogger(__name__) +IDENT_KEY = "__typename__" + def program_settings(suffix=""): return QSettings("WinRustler Corp.", "WinRustler" + suffix) -import attr -from attr.exceptions import NotAnAttrsClassError - -IDENT_KEY = "__typename__" - @attr.s() class Serialization(): """ Some crap for transforming attr classes into data types that can be stored and restored into QSettings without PyQt5 being a dick and trying to use pickling... + + When we encounter an attrs class, we convert it to a dictionary and set + the key "__typename__" to some value we can use later to figure out what + constructor to use to restore the thing later. """ _cls_idents = attr.ib(default=attr.Factory(dict)) diff --git a/winrustler/ui/widgets/match.py b/winrustler/ui/widgets/match.py --- a/winrustler/ui/widgets/match.py +++ b/winrustler/ui/widgets/match.py @@ -61,6 +61,7 @@ def sync_windows(self, *args, **kwargs): # Because slots and reference counting self._sync(*args, **kwargs) + @pyqtSlot(object) def _refilter(self, pattern): items = [self._model.item(row) for row in range(self._model.rowCount())] #hwnds = [item.data(role=Qt.UserRole) for item in items] @@ -89,7 +90,6 @@ #self._bb.clicked.connect(self._bb_on_clicked) self._apply = self._bb.addButton(QDialogButtonBox.Apply) - #self._apply_and_close = self._bb.addButton('&Apply && Close', QDialogButtonBox.AcceptRole) self._close = self._bb.addButton(QDialogButtonBox.Close) self._layout = QVBoxLayout(self) diff --git a/winrustler/ui/widgets/tray.py b/winrustler/ui/widgets/tray.py --- a/winrustler/ui/widgets/tray.py +++ b/winrustler/ui/widgets/tray.py @@ -85,7 +85,8 @@ msg = rustle_description(req) self.showMessage("I did something.", msg, icon) - def _window_destroyed(self, ptr): + @pyqtSlot() + def _window_destroyed(self): self.window = None def _about(self): diff --git a/winrustler/ui/winapi.py b/winrustler/ui/winapi.py --- a/winrustler/ui/winapi.py +++ b/winrustler/ui/winapi.py @@ -1,14 +1,19 @@ +import logging import ctypes from ctypes import wintypes -from PyQt5.QtCore import QObject, pyqtSignal +from PyQt5.QtCore import QObject, pyqtSignal, pyqtSlot from PyQt5.QtGui import QIcon from PyQt5.QtWinExtras import QtWin from winrustler.winconsts import (WM_GETICON, ICON_SMALL, ICON_BIG, GW_OWNER, GCL_HICON, GCL_HICONSM) + user32 = ctypes.windll.user32 +logger = logging.getLogger(__name__) + +EVENT_SYSTEM_FOREGROUND = 0x3 def get_hicons(hwnd): @@ -33,23 +38,30 @@ class WinHooker(QObject): - event = pyqtSignal() def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) - @ctypes.WINFUNCTYPE(wintypes.HANDLE, wintypes.DWORD, wintypes.HWND, wintypes.LONG, wintypes.LONG, wintypes.DWORD, wintypes.DWORD) + @ctypes.WINFUNCTYPE(wintypes.HANDLE, wintypes.DWORD, wintypes.HWND, + wintypes.LONG, wintypes.LONG, wintypes.DWORD, wintypes.DWORD) def wineventproc(something, event, hwnd, no, body, cares): self.event.emit() - self.wineventproc = wineventproc - EVENT_SYSTEM_FOREGROUND = 0x3 - self.hook = user32.SetWinEventHook(EVENT_SYSTEM_FOREGROUND, EVENT_SYSTEM_FOREGROUND, 0, self.wineventproc, 0, 0, 0) + logger.debug("New %r, setting up WinEventHook.", self) + self.hook = user32.SetWinEventHook(EVENT_SYSTEM_FOREGROUND, + EVENT_SYSTEM_FOREGROUND, 0, self.wineventproc, 0, 0, 0) if 0 == self.hook: raise ctypes.WinError() + @pyqtSlot() + def _unhook(self): + if getattr(self, 'hook', None) is not None: + logger.debug("%r is being deleted, unhooking.", self) + if not user32.UnhookWinEvent(self.hook): + raise ctypes.WinError() + self.hook = None + def __del__(self): - if not user32.UnhookWinEvent(self.hook): - raise ctypes.WinError() + self._unhook()