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()