Skip to content

Commit

Permalink
autorefresh: Use usb events instead of polling on windows
Browse files Browse the repository at this point in the history
it was noticed that polling may still cause lag, and affect other
devices
  • Loading branch information
xyzz committed Apr 19, 2022
1 parent c3d563e commit 7ab9ebf
Show file tree
Hide file tree
Showing 5 changed files with 134 additions and 74 deletions.
1 change: 1 addition & 0 deletions requirements.txt
Original file line number Diff line number Diff line change
Expand Up @@ -9,4 +9,5 @@ PyInstaller==3.4
PyQt5==5.9.2
https://github.com/danthedeckie/simpleeval/archive/41c99b8e224a7a0ae0ac59c773598fe79a4470db.zip
sip==4.19.8
pywin32==303; sys_platform == 'win32'
certifi
83 changes: 83 additions & 0 deletions src/main/python/autorefresh/autorefresh.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
import sys

from PyQt5.QtCore import QObject, pyqtSignal


class AutorefreshLocker:

def __init__(self, autorefresh):
self.autorefresh = autorefresh

def __enter__(self):
self.autorefresh._lock()

def __exit__(self):
self.autorefresh._unlock()


class Autorefresh(QObject):

instance = None
devices_updated = pyqtSignal(object, bool)

def __init__(self):
super().__init__()

self.devices = []
self.current_device = None

Autorefresh.instance = self

if sys.platform.startswith("win"):
from autorefresh.autorefresh_thread_win import AutorefreshThreadWin

self.thread = AutorefreshThreadWin()
else:
from autorefresh.autorefresh_thread import AutorefreshThread

self.thread = AutorefreshThread()

self.thread.devices_updated.connect(self.on_devices_updated)
self.thread.start()

def _lock(self):
self.thread.lock()

def _unlock(self):
self.thread.unlock()

@classmethod
def lock(cls):
return AutorefreshLocker(cls.instance)

def load_dummy(self, data):
self.thread.load_dummy(data)

def sideload_via_json(self, data):
self.thread.sideload_via_json(data)

def load_via_stack(self, data):
self.thread.load_via_stack(data)

def select_device(self, idx):
if self.current_device is not None:
self.current_device.close()
self.current_device = None
if idx >= 0:
self.current_device = self.devices[idx]

if self.current_device is not None:
if self.current_device.sideload:
self.current_device.open(self.thread.sideload_json)
elif self.current_device.via_stack:
self.current_device.open(self.thread.via_stack_json["definitions"][self.current_device.via_id])
else:
self.current_device.open(None)
self.thread.set_device(self.current_device)

def on_devices_updated(self, devices, changed):
self.devices = devices
self.devices_updated.emit(devices, changed)

def update(self, quiet=True, hard=False):
self.thread.update(quiet, hard)
Original file line number Diff line number Diff line change
Expand Up @@ -2,23 +2,11 @@
import time
from multiprocessing import RLock

from PyQt5.QtCore import QObject, pyqtSignal, QThread
from PyQt5.QtCore import pyqtSignal, QThread

from util import find_vial_devices


class AutorefreshLocker:

def __init__(self, autorefresh):
self.autorefresh = autorefresh

def __enter__(self):
self.autorefresh._lock()

def __exit__(self):
self.autorefresh._unlock()


class AutorefreshThread(QThread):

devices_updated = pyqtSignal(object, bool)
Expand Down Expand Up @@ -103,63 +91,3 @@ def load_via_stack(self, data):
def set_device(self, current_device):
with self.mutex:
self.current_device = current_device


class Autorefresh(QObject):

instance = None
devices_updated = pyqtSignal(object, bool)

def __init__(self):
super().__init__()

self.devices = []
self.current_device = None

Autorefresh.instance = self

self.thread = AutorefreshThread()
self.thread.devices_updated.connect(self.on_devices_updated)
self.thread.start()

def _lock(self):
self.thread.lock()

def _unlock(self):
self.thread.unlock()

@classmethod
def lock(cls):
return AutorefreshLocker(cls.instance)

def load_dummy(self, data):
self.thread.load_dummy(data)

def sideload_via_json(self, data):
self.thread.sideload_via_json(data)

def load_via_stack(self, data):
self.thread.load_via_stack(data)

def select_device(self, idx):
if self.current_device is not None:
self.current_device.close()
self.current_device = None
if idx >= 0:
self.current_device = self.devices[idx]

if self.current_device is not None:
if self.current_device.sideload:
self.current_device.open(self.thread.sideload_json)
elif self.current_device.via_stack:
self.current_device.open(self.thread.via_stack_json["definitions"][self.current_device.via_id])
else:
self.current_device.open(None)
self.thread.set_device(self.current_device)

def on_devices_updated(self, devices, changed):
self.devices = devices
self.devices_updated.emit(devices, changed)

def update(self, quiet=True, hard=False):
self.thread.update(quiet, hard)
48 changes: 48 additions & 0 deletions src/main/python/autorefresh/autorefresh_thread_win.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
import time
import win32gui, win32con, win32api
import win32gui_struct

from autorefresh.autorefresh_thread import AutorefreshThread


GUID_DEVINTERFACE_USB_DEVICE = "{A5DCBF10-6530-11D2-901F-00C04FB951ED}"
DEVICE_NOTIFY_ALL_INTERFACE_CLASSES = 4

g_device_changes = 0


def device_changed(hwnd, msg, wp, lp):
global g_device_changes
if wp in [win32con.DBT_DEVICEARRIVAL, win32con.DBT_DEVICEREMOVECOMPLETE]:
g_device_changes += 1


class AutorefreshThreadWin(AutorefreshThread):

def run(self):
global g_device_changes

# code based on:
# - https://github.com/libsdl-org/SDL/blob/7b3449b89f0625e4603f5d8681e2bac1f51a9386/src/hidapi/SDL_hidapi.c
# - https://github.com/vmware-archive/salt-windows-install/blob/master/deps/salt/python/App/Lib/site-packages/win32/Demos/win32gui_devicenotify.py
wc = win32gui.WNDCLASS()
wc.hInstance = win32api.GetModuleHandle(None)
wc.lpszClassName = "VIAL_DEVICE_DETECTION"
wc.lpfnWndProc = { win32con.WM_DEVICECHANGE: device_changed }
class_atom = win32gui.RegisterClass(wc)
hwnd = win32gui.CreateWindowEx(0, "VIAL_DEVICE_DETECTION", None, 0, 0, 0, 0, 0, win32con.HWND_MESSAGE, None, None, None)

hdev = win32gui.RegisterDeviceNotification(
hwnd,
win32gui_struct.PackDEV_BROADCAST_DEVICEINTERFACE(GUID_DEVINTERFACE_USB_DEVICE),
win32con.DEVICE_NOTIFY_WINDOW_HANDLE | DEVICE_NOTIFY_ALL_INTERFACE_CLASSES
)

while True:
for x in range(100):
win32gui.PumpWaitingMessages()
time.sleep(0.01)

if g_device_changes > 0:
g_device_changes = 0
self.update()
2 changes: 1 addition & 1 deletion src/main/python/main_window.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
from urllib.request import urlopen

from about_keyboard import AboutKeyboard
from autorefresh import Autorefresh
from autorefresh.autorefresh import Autorefresh
from editor.combos import Combos
from constants import WINDOW_WIDTH, WINDOW_HEIGHT
from widgets.editor_container import EditorContainer
Expand Down

0 comments on commit 7ab9ebf

Please sign in to comment.