Skip to content

Commit e5b1802

Browse files
committed
Rewrite focused app logic into mixin class
1 parent 649a750 commit e5b1802

File tree

5 files changed

+100
-110
lines changed

5 files changed

+100
-110
lines changed

mousetracks2/components/abstract.py

Lines changed: 55 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,12 +2,15 @@
22
import os
33
import time
44
import traceback
5-
from typing import TYPE_CHECKING, Iterator
5+
from typing import TYPE_CHECKING, Callable, Iterator
66

77
import psutil
88

99
from . import ipc
10+
from ..constants import DEFAULT_PROFILE_NAME
1011
from ..exceptions import ExitRequest
12+
from ..types import RectList, Application
13+
from ..utils.system import UserResizeAppListener
1114

1215
if TYPE_CHECKING:
1316
import multiprocessing.queues
@@ -18,9 +21,13 @@ def __init__(self, q_send: multiprocessing.queues.Queue, q_receive: multiprocess
1821
self._q_send = q_send
1922
self._q_recv = q_receive
2023
self.name = type(self).__name__
24+
self._register_mixin()
2125
self.__post_init__()
2226
self._parent_pid = os.getppid()
2327

28+
def _register_mixin(self) -> None:
29+
"""Subclass to implement custom mixin code."""
30+
2431
def __post_init__(self) -> None:
2532
"""Call this after running `__init__`."""
2633

@@ -153,3 +160,50 @@ def launch(cls, q_send: multiprocessing.queues.Queue, q_receive: multiprocessing
153160
print(f'[{self.name}] Sent process closed notification.')
154161
else:
155162
print(f'[{self.name}] Process closed due to Hub not running.')
163+
164+
165+
class AppComponent(Component):
166+
"""Mixin component to implement methods for tracking the focused application."""
167+
168+
def _register_mixin(self) -> None:
169+
# Setup the hook list
170+
self._app_change_hooks: list[Callable[[Application], None]] = []
171+
172+
# Setup the focused app tracking
173+
self._focused_app = Application('', RectList())
174+
self.focused_app = Application(DEFAULT_PROFILE_NAME, RectList())
175+
176+
# Setup the resize listener
177+
self._resize_listener = UserResizeAppListener()
178+
self._resize_listener.start()
179+
180+
super()._register_mixin()
181+
182+
def register_app_change_hook(self, fn: Callable[[Application], None]) -> None:
183+
"""Register a function to run when the focused application changes.
184+
It takes one input parameter of the `Application` instance.
185+
"""
186+
self._app_change_hooks.append(fn)
187+
fn(self.focused_app)
188+
189+
@property
190+
def focused_app(self) -> Application:
191+
"""Get the currently focused application."""
192+
return self._focused_app
193+
194+
@focused_app.setter
195+
def focused_app(self, application: Application) -> None:
196+
"""Update the currently focused application."""
197+
if application == self._focused_app:
198+
return
199+
self._focused_app = application
200+
201+
for func in self._app_change_hooks:
202+
func(application)
203+
204+
@property
205+
def app_resizing(self) -> bool:
206+
"""Determine if the focused application is being resized."""
207+
if self.focused_app.name == DEFAULT_PROFILE_NAME:
208+
return False
209+
return self._resize_listener.triggered

mousetracks2/components/gui.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
from PySide6 import QtCore, QtGui, QtWidgets
66

77
from . import ipc
8-
from .abstract import Component
8+
from .abstract import Component, AppComponent
99
from ..gui.utils import ICON_PATH
1010
from ..gui.main_window import MainWindow
1111
from ..gui.splash import SplashScreen
@@ -45,7 +45,7 @@ def stop(self) -> None:
4545
self.running = False
4646

4747

48-
class GUI(Component):
48+
class GUI(AppComponent):
4949
def __post_init__(self) -> None:
5050
"""Setup the threads."""
5151
self.error: Exception | None = None

mousetracks2/components/processing.py

Lines changed: 14 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -11,21 +11,21 @@
1111
from send2trash import send2trash
1212

1313
from . import ipc
14-
from .abstract import Component
14+
from .abstract import AppComponent
1515
from ..cli import CLI
1616
from ..config import GlobalConfig
1717
from ..exceptions import ExitRequest
1818
from ..export import Export
1919
from ..file import ArrayResolutionMap, MovementMaps, TrackingProfile, TrackingProfileLoader, get_filename
2020
from ..legacy import keyboard
21-
from ..types import RectList
21+
from ..types import Application
2222
from ..utils import keycodes
2323
from ..utils.math import calculate_line, calculate_distance
2424
from ..utils.monitor import MonitorData
2525
from ..utils.input import get_cursor_pos
2626
from ..utils.interface import Interfaces
27-
from ..utils.system import hide_child_process, UserResizeAppListener
28-
from ..constants import DEFAULT_PROFILE_NAME, UPDATES_PER_SECOND, DOUBLE_CLICK_MS, DOUBLE_CLICK_TOL, RADIAL_ARRAY_SIZE, DEBUG
27+
from ..utils.system import hide_child_process
28+
from ..constants import UPDATES_PER_SECOND, DOUBLE_CLICK_MS, DOUBLE_CLICK_TOL, RADIAL_ARRAY_SIZE, DEBUG
2929
from ..render import render, EmptyRenderError, LayerBlend
3030

3131

@@ -47,13 +47,7 @@ def position(self) -> tuple[int, int]:
4747
return self.message.position
4848

4949

50-
@dataclass
51-
class Application:
52-
name: str
53-
rects: RectList = field(default_factory=RectList)
54-
55-
56-
class Processing(Component):
50+
class Processing(AppComponent):
5751
def __post_init__(self) -> None:
5852
hide_child_process()
5953

@@ -66,11 +60,11 @@ def __post_init__(self) -> None:
6660

6761
# Load in the default profile
6862
self.all_profiles = TrackingProfileLoader()
69-
self._current_application = Application('', RectList())
70-
self.current_application = Application(DEFAULT_PROFILE_NAME, RectList())
7163

72-
self._resize_listener = UserResizeAppListener()
73-
self._resize_listener.start()
64+
# Reset the cursor position on focused application change
65+
def on_application_change(app: Application) -> None:
66+
self.profile.cursor_map.position = None
67+
self.register_app_change_hook(on_application_change)
7468

7569
@property
7670
def timestamp(self) -> int:
@@ -87,22 +81,7 @@ def timestamp(self, timestamp: int) -> None:
8781
@property
8882
def profile(self) -> TrackingProfile:
8983
"""Get the data for the current application."""
90-
return self.all_profiles[self.current_application.name]
91-
92-
@property
93-
def current_application(self) -> Application:
94-
"""Get the currently loaded application."""
95-
return self._current_application
96-
97-
@current_application.setter
98-
def current_application(self, application: Application) -> None:
99-
"""Update the currently loaded application."""
100-
if application == self._current_application:
101-
return
102-
self._current_application = application
103-
104-
# Reset the data
105-
self.profile.cursor_map.position = None
84+
return self.all_profiles[self.focused_app.name]
10685

10786
def _send_profile_data(self, profile: TrackingProfile) -> None:
10887
"""Send all the stats for the profile."""
@@ -169,18 +148,11 @@ def profile_age_days(self) -> int:
169148
current_day = self.timestamp // 86400
170149
return max(0, current_day - creation_day)
171150

172-
@property
173-
def app_resizing(self) -> bool:
174-
"""Determine if the focused application is being resized."""
175-
if self.current_application.name == DEFAULT_PROFILE_NAME:
176-
return False
177-
return self._resize_listener.triggered
178-
179151
def _monitor_offset(self, pixel: tuple[int, int]) -> tuple[tuple[int, int], tuple[int, int]] | None:
180152
"""Detect which monitor the pixel is on."""
181153
monitors = self.monitor_data.physical
182-
if self.current_application.rects:
183-
monitors = self.current_application.rects
154+
if self.focused_app.rects:
155+
monitors = self.focused_app.rects
184156

185157
single_monitor = bool(CLI.single_monitor) if self.profile.config.multi_monitor is None else not self.profile.config.multi_monitor
186158
return monitors.calculate_offset(pixel, combined=single_monitor)
@@ -208,7 +180,7 @@ def _record_move(self, data: MovementMaps, position: tuple[int, int],
208180
# Convert pixels from logical coordinates to physical
209181
old_position = position
210182
new_position = data.position
211-
if force_monitor is None and not self.current_application.rects:
183+
if force_monitor is None and not self.focused_app.rects:
212184
old_position = self.monitor_data.coordinate(position)
213185
if data.position is None:
214186
new_position = old_position
@@ -715,7 +687,7 @@ def _process_message(self, message: ipc.Message) -> None:
715687
raise ExitRequest
716688

717689
case ipc.CurrentProfileChanged():
718-
self.current_application = Application(message.name, message.rects)
690+
self.focused_app = Application(message.name, message.rects)
719691

720692
case ipc.Save():
721693
# Keep track of what saved and what didn't

0 commit comments

Comments
 (0)