Skip to content

Commit d013213

Browse files
committed
Refactor monitor handling
Refactored all the monitor code and standardised the data structures for rects, replacing the previous tuple-based approach.
1 parent e06f16a commit d013213

File tree

11 files changed

+271
-196
lines changed

11 files changed

+271
-196
lines changed

mousetracks2/components/app_detection.py

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
from ..applications import AppList, LOCAL_PATH
1212
from ..constants import DEFAULT_PROFILE_NAME, TRACKING_IGNORE
1313
from ..exceptions import ExitRequest
14+
from ..utils import RectList
1415
from ..utils.system import Window
1516

1617

@@ -25,7 +26,7 @@ def __post_init__(self) -> None:
2526
self._previous_app: tuple[str, str] | None = None
2627
self._previous_pos: tuple[int, int] | None = None
2728
self._previous_res: tuple[int, int] | None = None
28-
self._previous_rects: list[tuple[int, int, int, int]] = []
29+
self._previous_rects = RectList()
2930
self._fallback_title = ''
3031
self._fallback_pid = 0
3132

@@ -114,7 +115,7 @@ def check_running_app(self) -> None:
114115
current_app_name = self.applist.match(exe, title)
115116
if current_app_name is None or current_app_name == TRACKING_IGNORE:
116117
current_app = None
117-
rects = []
118+
rects = RectList()
118119
else:
119120
current_app = current_app_name, exe
120121
rects = window.rects

mousetracks2/components/ipc.py

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,8 @@
99

1010
from ..config import ProfileConfig
1111
from ..enums import BlendMode, Channel
12+
from ..utils import RectList
13+
from ..utils.monitor import MonitorData
1214

1315

1416
class Target:
@@ -197,8 +199,7 @@ class MonitorsChanged(Message):
197199
"""Send the location of each monitor when the setup changes."""
198200

199201
target: int = field(default=Target.GUI | Target.Processing, init=False)
200-
physical_data: list[tuple[int, int, int, int]]
201-
logical_data: list[tuple[int, int, int, int]]
202+
data: MonitorData
202203

203204

204205
@dataclass
@@ -267,7 +268,7 @@ class TrackedApplicationDetected(Message):
267268
target: int = field(default=Target.Tracking, init=False)
268269
name: str
269270
process_id: int | None
270-
rects: list[tuple[int, int, int, int]] = field(default_factory=list)
271+
rects: RectList = field(default_factory=RectList)
271272

272273

273274
@dataclass
@@ -281,7 +282,7 @@ class CurrentProfileChanged(Message):
281282
target: int = field(default=Target.Processing | Target.GUI, init=False)
282283
name: str
283284
process_id: int | None
284-
rects: list[tuple[int, int, int, int]] = field(default_factory=list)
285+
rects: RectList = field(default_factory=RectList)
285286

286287

287288
@dataclass

mousetracks2/components/processing.py

Lines changed: 14 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -17,10 +17,10 @@
1717
from ..export import Export
1818
from ..file import ArrayResolutionMap, MovementMaps, TrackingProfile, TrackingProfileLoader, get_filename
1919
from ..legacy import keyboard
20-
from ..utils import keycodes, get_cursor_pos
21-
from ..utils.math import calculate_line, calculate_distance, calculate_pixel_offset, logical_to_physical
20+
from ..utils import keycodes, RectList, get_cursor_pos
21+
from ..utils.math import calculate_line, calculate_distance
22+
from ..utils.monitor import MonitorData
2223
from ..utils.network import Interfaces
23-
from ..utils.system import monitor_locations
2424
from ..constants import DEFAULT_PROFILE_NAME, UPDATES_PER_SECOND, DOUBLE_CLICK_MS, DOUBLE_CLICK_TOL, RADIAL_ARRAY_SIZE, DEBUG
2525
from ..render import render, EmptyRenderError, LayerBlend
2626

@@ -46,7 +46,7 @@ def position(self) -> tuple[int, int]:
4646
@dataclass
4747
class Application:
4848
name: str
49-
rects: list[tuple[int, int, int, int]] = field(default_factory=list)
49+
rects: RectList = field(default_factory=RectList)
5050

5151

5252
class Processing(Component):
@@ -55,13 +55,13 @@ def __post_init__(self) -> None:
5555
self._timestamp = -1
5656

5757
self.previous_mouse_click: PreviousMouseClick | None = None
58-
self.monitor_data = (monitor_locations(True), monitor_locations(False))
58+
self.monitor_data = MonitorData()
5959
self.previous_monitor = None
6060

6161
# Load in the default profile
6262
self.all_profiles = TrackingProfileLoader()
63-
self._current_application = Application('', [])
64-
self.current_application = Application(DEFAULT_PROFILE_NAME, [])
63+
self._current_application = Application('', RectList())
64+
self.current_application = Application(DEFAULT_PROFILE_NAME, RectList())
6565

6666
@property
6767
def timestamp(self) -> int:
@@ -162,31 +162,12 @@ def profile_age_days(self) -> int:
162162

163163
def _monitor_offset(self, pixel: tuple[int, int]) -> tuple[tuple[int, int], tuple[int, int]] | None:
164164
"""Detect which monitor the pixel is on."""
165-
physical_monitor_data, logical_monitor_data = self.monitor_data
166-
167-
monitor_data = physical_monitor_data
165+
monitors = self.monitor_data.physical
168166
if self.current_application.rects:
169-
monitor_data = self.current_application.rects
170-
171-
single_monitor = CLI.single_monitor if self.profile.config.multi_monitor is None else not self.profile.config.multi_monitor
172-
if single_monitor:
173-
x_min, y_min, x_max, y_max = monitor_data[0]
174-
for x1, y1, x2, y2 in monitor_data[1:]:
175-
x_min = min(x_min, x1)
176-
y_min = min(y_min, y1)
177-
x_max = max(x_max, x2)
178-
y_max = max(y_max, y2)
179-
result = calculate_pixel_offset(pixel[0], pixel[1], x_min, y_min, x_max, y_max)
180-
if result is not None:
181-
return result
182-
183-
else:
184-
for x1, y1, x2, y2 in monitor_data:
185-
result = calculate_pixel_offset(pixel[0], pixel[1], x1, y1, x2, y2)
186-
if result is not None:
187-
return result
167+
monitors = self.current_application.rects
188168

189-
return None
169+
single_monitor = bool(CLI.single_monitor) if self.profile.config.multi_monitor is None else not self.profile.config.multi_monitor
170+
return monitors.calculate_offset(pixel, combined=single_monitor)
190171

191172
def _record_move(self, data: MovementMaps, position: tuple[int, int],
192173
force_monitor: tuple[int, int] | None = None) -> float:
@@ -209,11 +190,11 @@ def _record_move(self, data: MovementMaps, position: tuple[int, int],
209190
moving, and will always skip the first frame of movement.
210191
"""
211192
# Convert logical to physical
212-
old_position = logical_to_physical(position, self.monitor_data[1], self.monitor_data[0])
193+
old_position = self.monitor_data.coordinate(position)
213194
if data.position is None:
214195
new_position = old_position
215196
else:
216-
new_position = logical_to_physical(data.position, self.monitor_data[1], self.monitor_data[0])
197+
new_position = self.monitor_data.coordinate(data.position)
217198

218199
# If the ticks match then overwrite the old data
219200
if self.tick == data.tick:
@@ -711,7 +692,7 @@ def _process_message(self, message: ipc.Message) -> None:
711692

712693
case ipc.MonitorsChanged():
713694
print(f'[Processing] Monitors changed.')
714-
self.monitor_data = message.physical_data, message.logical_data
695+
self.monitor_data = message.data
715696

716697
case ipc.ThumbstickMove():
717698
if not self.profile.config.track_gamepad:

mousetracks2/components/tracking.py

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -19,8 +19,9 @@
1919
from ..constants import UPDATES_PER_SECOND, DEFAULT_PROFILE_NAME
2020
from ..exceptions import ExitRequest
2121
from ..utils import get_cursor_pos, keycodes
22+
from ..utils.monitor import MonitorData
2223
from ..utils.network import Interfaces
23-
from ..utils.system import monitor_locations, MonitorEventsListener
24+
from ..utils.system import MonitorEventsListener
2425

2526

2627
if XInput is None:
@@ -74,8 +75,7 @@ class DataState:
7475
mouse_inactive: bool = field(default=False)
7576
mouse_clicks: dict[int, tuple[int, int]] = field(default_factory=dict)
7677
mouse_position: tuple[int, int] | None = field(default_factory=get_cursor_pos)
77-
monitors: tuple[list[tuple[int, int, int, int]], list[tuple[int, int, int, int]]] = field(
78-
default_factory=lambda: (monitor_locations(True), monitor_locations(False)))
78+
monitors: MonitorData = field(default_factory=MonitorData)
7979
gamepads_current: tuple[bool, bool, bool, bool] = field(default_factory=_getConnectedGamepads)
8080
gamepads_previous: tuple[bool, bool, bool, bool] = field(default_factory=_getConnectedGamepads)
8181
gamepad_force_recheck: bool = field(default=False)
@@ -267,7 +267,8 @@ def _check_monitor_data(self, pixel: tuple[int, int]) -> None:
267267
"""Check if the monitor data is valid for the pixel.
268268
If not, recalculate it and update the other components.
269269
"""
270-
for x1, y1, x2, y2 in self.data.monitors[0]:
270+
for monitor in self.data.monitors.logical: # TODO: Test
271+
x1, y1, x2, y2 = monitor.rect
271272
if x1 <= pixel[0] < x2 and y1 <= pixel[1] < y2:
272273
break
273274
else:
@@ -278,10 +279,10 @@ def _refresh_monitor_data(self) -> None:
278279
"""Check the monitor data is up to date.
279280
If not, then send a signal with the updated data.
280281
"""
281-
self.data.monitors, old_data = (monitor_locations(True), monitor_locations(False)), self.data.monitors
282+
self.data.monitors, old_data = MonitorData(), self.data.monitors
282283
if old_data != self.data.monitors:
283284
print('[Tracking] Monitor change detected')
284-
self.send_data(ipc.MonitorsChanged(*self.data.monitors))
285+
self.send_data(ipc.MonitorsChanged(self.data.monitors))
285286

286287
@contextmanager
287288
def _exception_handler(self) -> Iterator[None]:

mousetracks2/gui/main_window.py

Lines changed: 13 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -31,9 +31,10 @@
3131
from ..file import PROFILE_DIR, get_profile_names, get_filename, sanitise_profile_name, TrackingProfile
3232
from ..legacy import colours
3333
from ..update import is_latest_version
34-
from ..utils import keycodes, get_cursor_pos
35-
from ..utils.math import calculate_line, calculate_distance, calculate_pixel_offset, logical_to_physical
36-
from ..utils.system import monitor_locations, get_autostart, set_autostart, remove_autostart
34+
from ..utils import keycodes, RectList, get_cursor_pos
35+
from ..utils.math import calculate_line, calculate_distance
36+
from ..utils.monitor import MonitorData
37+
from ..utils.system import get_autostart, set_autostart, remove_autostart
3738

3839
if TYPE_CHECKING:
3940
from ..components.gui import GUI
@@ -59,7 +60,7 @@ class Profile:
5960
"""Hold data related to the currently running profile."""
6061

6162
name: str
62-
rects: list[tuple[int, int, int, int]] = field(default_factory=list)
63+
rects: RectList = field(default_factory=RectList)
6364
track_mouse: bool = True
6465
track_keyboard: bool = True
6566
track_gamepad: bool = True
@@ -311,7 +312,7 @@ def __init__(self, component: GUI) -> None:
311312
self.mouse_click_count = self.mouse_held_count = self.mouse_scroll_count = 0
312313
self.button_press_count = self.key_press_count = 0
313314
self.elapsed_time = self.active_time = self.inactive_time = 0
314-
self.monitor_data = monitor_locations(True), monitor_locations(False)
315+
self.monitor_data = MonitorData()
315316
self.render_type = ipc.RenderType.MouseMovement
316317
self.tick_current = 0
317318
self.last_render: tuple[ipc.RenderType, int] = (self.render_type, -1)
@@ -1227,31 +1228,12 @@ def toggle_auto_switch_profile(self, state: QtCore.Qt.CheckState) -> None:
12271228

12281229
def _monitor_offset(self, pixel: tuple[int, int]) -> tuple[tuple[int, int], tuple[int, int]] | None:
12291230
"""Detect which monitor the pixel is on."""
1230-
physical_monitor_data, logical_monitor_data = self.monitor_data
1231-
1232-
monitor_data = physical_monitor_data
1231+
monitors = self.monitor_data.physical
12331232
if self.current_profile.rects:
1234-
monitor_data = self.current_profile.rects
1235-
1236-
single_monitor = self.ui.single_monitor.isChecked() if self.ui.opts_monitor.isChecked() else CLI.single_monitor
1237-
if single_monitor:
1238-
x_min, y_min, x_max, y_max = monitor_data[0]
1239-
for x1, y1, x2, y2 in monitor_data[1:]:
1240-
x_min = min(x_min, x1)
1241-
y_min = min(y_min, y1)
1242-
x_max = max(x_max, x2)
1243-
y_max = max(y_max, y2)
1244-
result = calculate_pixel_offset(pixel[0], pixel[1], x_min, y_min, x_max, y_max)
1245-
if result is not None:
1246-
return result
1247-
1248-
else:
1249-
for x1, y1, x2, y2 in monitor_data:
1250-
result = calculate_pixel_offset(pixel[0], pixel[1], x1, y1, x2, y2)
1251-
if result is not None:
1252-
return result
1233+
monitors = self.current_profile.rects
12531234

1254-
return None
1235+
single_monitor = self.ui.single_monitor.isChecked() if self.ui.opts_monitor.isChecked() else bool(CLI.single_monitor)
1236+
return monitors.calculate_offset(pixel, combined=single_monitor)
12551237

12561238
def start_rendering_timer(self) -> None:
12571239
"""Start the timer to display rendering text.
@@ -1603,7 +1585,7 @@ def _process_message(self, message: ipc.Message) -> None:
16031585

16041586
# When monitors change, store the new data
16051587
case ipc.MonitorsChanged():
1606-
self.monitor_data = message.physical_data, message.logical_data
1588+
self.monitor_data = message.data
16071589

16081590
case ipc.Render():
16091591
if message.array.any():
@@ -2284,9 +2266,9 @@ def draw_pixmap_line(self, old_position: tuple[int, int] | None, new_position: t
22842266

22852267
# Convert logical to physical
22862268
if old_position is not None:
2287-
old_position = logical_to_physical(old_position, self.monitor_data[1], self.monitor_data[0])
2269+
old_position = self.monitor_data.coordinate(old_position)
22882270
if new_position is not None:
2289-
new_position = logical_to_physical(new_position, self.monitor_data[1], self.monitor_data[0])
2271+
new_position = self.monitor_data.coordinate(new_position)
22902272

22912273
unique_pixels = set()
22922274
size = self.ui.thumbnail.pixmap_size()

0 commit comments

Comments
 (0)