Skip to content

Commit ff53376

Browse files
committed
Listen for controller change events
1 parent 1ac2044 commit ff53376

File tree

5 files changed

+61
-24
lines changed

5 files changed

+61
-24
lines changed

mousetracks2/components/tracking.py

Lines changed: 12 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@
2323
from ..utils.monitor import MonitorData
2424
from ..utils.input import get_cursor_pos
2525
from ..utils.interface import Interfaces
26-
from ..utils.system import MonitorEventsListener, hide_child_process
26+
from ..utils.system import MonitorEventListener, ControllerEventListener, hide_child_process
2727

2828

2929
if XInput is None:
@@ -130,9 +130,12 @@ def __post_init__(self) -> None:
130130
self._pynput_mouse_listener.start()
131131
self._pynput_keyboard_listener.start()
132132

133-
self._monitor_listener = MonitorEventsListener()
133+
self._monitor_listener = MonitorEventListener()
134134
self._monitor_listener.start()
135135

136+
self._controller_listener = ControllerEventListener()
137+
self._controller_listener.start()
138+
136139
def _receive_data(self) -> None:
137140
for message in self.receive_data():
138141
match message:
@@ -179,6 +182,11 @@ def _receive_data(self) -> None:
179182
case ipc.SetGlobalGamepadTracking():
180183
print(f'[Tracking] Tracking gamepad data: {message.enable}')
181184
self.track_gamepad = message.enable
185+
if message.enable:
186+
self._controller_listener = ControllerEventListener()
187+
self._controller_listener.start()
188+
else:
189+
self._controller_listener.stop()
182190

183191
case ipc.SetGlobalNetworkTracking():
184192
print(f'[Tracking] Tracking network data: {message.enable}')
@@ -197,7 +205,7 @@ def _receive_data(self) -> None:
197205
if message.disable:
198206
self._monitor_listener.stop()
199207
else:
200-
self._monitor_listener = MonitorEventsListener()
208+
self._monitor_listener = MonitorEventListener()
201209
self._monitor_listener.start()
202210

203211
def _run_with_state(self) -> Iterator[tuple[int, DataState]]:
@@ -441,9 +449,8 @@ def run(self) -> None:
441449

442450
# Determine which gamepads are connected
443451
if self.track_gamepad and XInput is not None:
444-
if not tick % int(UPDATES_PER_SECOND * GlobalConfig.gamepad_check_frequency) or data.gamepad_force_recheck:
452+
if self._controller_listener.triggered:
445453
data.gamepads_current = XInput.get_connected()
446-
data.gamepad_force_recheck = False
447454

448455
if data.gamepads_current != data.gamepads_previous:
449456
print('[Tracking] Gamepad change detected')

mousetracks2/config.py

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,6 @@ class GlobalConfig:
2626
save_frequency: How often to autosave.
2727
max_loaded_profiles: Maximum amount of loaded profiles.
2828
This will only affect profiles without unsaved changes.
29-
gamepad_check_frequency: How often to check the connected gamepads.
3029
application_check_frequency: How often to check the current focused application.
3130
component_check_frequency: How often to check all components are running.
3231
This is used once per message received.
@@ -46,7 +45,6 @@ class GlobalConfig:
4645
inactivity_time: float = 300.0
4746
save_frequency: float = 600.0
4847
max_loaded_profiles: int = 8
49-
gamepad_check_frequency: float = 1.0
5048
application_check_frequency: float = 1.0
5149
component_check_frequency: float = 1.0
5250
shutdown_timeout: float = 15.0

mousetracks2/utils/system/__init__.py

Lines changed: 8 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -3,12 +3,13 @@
33
from pathlib import Path
44
from typing import TYPE_CHECKING, Type
55

6-
from .base import Window as _Window, MonitorEventsListener as _MonitorEventsListener
6+
from . import base
77
from ...constants import APP_EXECUTABLE, IS_BUILT_EXE
88

99
if TYPE_CHECKING:
10-
Window: Type[_Window]
11-
MonitorEventsListener: Type[_MonitorEventsListener]
10+
Window: Type[base.Window]
11+
MonitorEventListener: Type[base.EventListener]
12+
ControllerEventListener: Type[base.EventListener]
1213

1314
match sys.platform:
1415
case 'win32':
@@ -17,7 +18,7 @@
1718
from .windows import get_autostart, set_autostart, remove_autostart
1819
from .windows import is_elevated, relaunch_as_elevated
1920
from .windows import Window
20-
from .windows import MonitorEventsListener
21+
from .windows import MonitorEventListener, ControllerEventListener
2122
from .base import hide_child_process
2223
from .windows import prepare_application_icon
2324
from .windows import update_installer_version_number
@@ -28,7 +29,7 @@
2829
from .macos import get_autostart, set_autostart, remove_autostart
2930
from .base import is_elevated, relaunch_as_elevated
3031
from .macos import Window
31-
from .base import MonitorEventsListener
32+
from .base import MonitorEventListener, ControllerEventListener
3233
from .macos import hide_child_process, prepare_application_icon
3334
from .base import update_installer_version_number
3435

@@ -38,7 +39,7 @@
3839
from .linux import get_autostart, set_autostart, remove_autostart
3940
from .base import is_elevated, relaunch_as_elevated
4041
from .linux import Window
41-
from .base import MonitorEventsListener
42+
from .base import MonitorEventListener, ControllerEventListener
4243
from .base import hide_child_process, prepare_application_icon
4344
from .base import update_installer_version_number
4445

@@ -48,7 +49,7 @@
4849
'get_autostart', 'set_autostart', 'remove_autostart', 'remap_autostart',
4950
'is_elevated', 'relaunch_as_elevated',
5051
'Window',
51-
'MonitorEventsListener',
52+
'MonitorEventListener', 'ControllerEventListener',
5253
'hide_child_process', 'prepare_application_icon',
5354
'update_installer_version_number',
5455
]

mousetracks2/utils/system/base.py

Lines changed: 12 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -88,17 +88,17 @@ def size(self) -> tuple[int, int]:
8888
return (0, 0)
8989

9090

91-
class MonitorEventsListener(threading.Thread):
92-
"""Listen for monitor change events.
91+
class EventListener(threading.Thread):
92+
"""Base class to listen for events.
9393
9494
The most basic implementation is to check every second for changes.
95-
If an operating system has hooks then this class can be subclassed.
95+
If an operating system has hooks then this should be overridden.
9696
9797
The initial event is triggered on startup.
9898
"""
9999

100100
def __init__(self) -> None:
101-
super().__init__(name='MonitorEventsListener', daemon=True)
101+
super().__init__(name='EventListener', daemon=True)
102102
self._queue = queue.Queue() # type: queue.Queue[None]
103103
self._running = True
104104

@@ -127,6 +127,14 @@ def triggered(self) -> bool:
127127
count += 1
128128

129129

130+
class MonitorEventListener(EventListener):
131+
"""Listen for monitor change events."""
132+
133+
134+
class ControllerEventListener(EventListener):
135+
"""Listen for controller change events."""
136+
137+
130138
def hide_child_process() -> None:
131139
"""This is here to allow macOS to hide the child processes."""
132140

mousetracks2/utils/system/windows.py

Lines changed: 29 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@
1313
import subprocess
1414
import winreg
1515

16-
from .base import Window as _Window, MonitorEventsListener as _MonitorEventsListener
16+
from . import base
1717
from ...constants import APP_EXECUTABLE, PACKAGE_IDENTIFIER
1818
from ...types import Rect, RectList
1919
from ...version import VERSION
@@ -49,6 +49,8 @@
4949

5050
PROCESS_QUERY_LIMITED_INFORMATION = 0x1000
5151

52+
DBT_DEVNODES_CHANGED = 0x0007
53+
5254
BOOL = ctypes.wintypes.BOOL
5355

5456
DWORD = ctypes.wintypes.DWORD
@@ -415,7 +417,7 @@ def relaunch_as_elevated() -> None:
415417
sys.exit()
416418

417419

418-
class Window(_Window):
420+
class Window(base.Window):
419421
def __init__(self, hwnd: int) -> None:
420422
self._hwnd = hwnd
421423
self._handle = WindowHandle(self._hwnd)
@@ -454,24 +456,31 @@ def size(self) -> tuple[int, int]:
454456
return self._pid.size
455457

456458

457-
class MonitorEventsListener(_MonitorEventsListener):
458-
"""Listen for monitor change events."""
459+
class EventListener(base.EventListener):
460+
"""Base Windows event listener.
461+
462+
Override the `check` method to implement this.
463+
"""
459464

460465
def __init__(self) -> None:
461466
super().__init__()
462467
self._hwnd = None # type: int | None
463468

469+
def check(self, hwnd: int, msg: int, wparam: int, lparam: int) -> bool:
470+
"""Determine if a specific event has been fired."""
471+
return False
472+
464473
def run(self) -> None:
465474
"""Create and start the message listener."""
466475
def wndproc(hwnd: int, msg: int, wparam: int, lparam: int) -> int:
467-
if msg in (WM_DISPLAYCHANGE, WM_DEVICECHANGE):
476+
if self.check(hwnd, msg, wparam, lparam):
468477
self.trigger()
469478
return user32.DefWindowProcW(hwnd, msg, wparam, lparam)
470479

471480
hinst = kernel32.GetModuleHandleW(None)
472481
wndproc_c = WNDPROCTYPE(wndproc)
473482

474-
class_name = 'MouseTracksHiddenWindowClass'
483+
class_name = type(self).__name__
475484
wc = WNDCLASS()
476485
wc.lpfnWndProc = wndproc_c
477486
wc.lpszClassName = class_name
@@ -501,6 +510,20 @@ def stop(self) -> None:
501510
user32.PostMessageW(self._hwnd, WM_QUIT, 0, 0)
502511

503512

513+
class MonitorEventListener(EventListener):
514+
"""Listen for monitor change events."""
515+
516+
def check(self, hwnd: int, msg: int, wparam: int, lparam: int) -> bool:
517+
return msg == WM_DISPLAYCHANGE
518+
519+
520+
class ControllerEventListener(EventListener):
521+
"""Listen for controller change events."""
522+
523+
def check(self, hwnd: int, msg: int, wparam: int, lparam: int) -> bool:
524+
return msg == WM_DEVICECHANGE and wparam == DBT_DEVNODES_CHANGED
525+
526+
504527
def prepare_application_icon(icon_path: Path | str) -> None:
505528
"""Register app so that setting an icon is possible."""
506529
ctypes.windll.shell32.SetCurrentProcessExplicitAppUserModelID(PACKAGE_IDENTIFIER)

0 commit comments

Comments
 (0)