fixes silly explosion in winhooker because lifetimes
M setup.py +1 -1
@@ 6,7 6,7 @@ def read(fname):
 
 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."),

          
M winrustler/__init__.py +1 -1
@@ 1,1 1,1 @@ 
-__version__ = '0.9.0'  # Keep in sync with ../setup.py
+__version__ = '0.9.1'  # Keep in sync with ../setup.py

          
M winrustler/ui/__main__.py +1 -1
@@ 27,7 27,7 @@ def main():
 
     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)
 

          
M winrustler/ui/app.py +3 -2
@@ 20,7 20,6 @@ logger = logging.getLogger(__name__)
 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 WindowSet(QObject):
 
 
 class WinRustlerApp(QApplication):
-
     rustled = pyqtSignal(object)
     suggested = pyqtSignal(object)
 

          
@@ 48,6 46,9 @@ class WinRustlerApp(QApplication):
         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)

          
M winrustler/ui/state.py +10 -5
@@ 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))

          
M winrustler/ui/widgets/match.py +1 -1
@@ 61,6 61,7 @@ class ComposeMatch(QWidget):
     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 @@ class MatchDialog(QDialog):
         #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)

          
M winrustler/ui/widgets/tray.py +2 -1
@@ 85,7 85,8 @@ class RustlerTray(QSystemTrayIcon):
         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):

          
M winrustler/ui/winapi.py +20 -8
@@ 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 @@ def get_window_icon(hwnd):
 
 
 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()