From d3336749a35d1b24788bc53314f9481551e9c21f Mon Sep 17 00:00:00 2001 From: Andreas Arvidsson Date: Tue, 12 Nov 2024 15:18:25 +0100 Subject: [PATCH 1/6] Refactor mouse scrolling code --- plugin/mouse/mouse.py | 286 +++++------------------------------ plugin/mouse/mouse_scroll.py | 214 ++++++++++++++++++++++++++ 2 files changed, 249 insertions(+), 251 deletions(-) create mode 100644 plugin/mouse/mouse_scroll.py diff --git a/plugin/mouse/mouse.py b/plugin/mouse/mouse.py index 4088b2fd22..124bf1b64a 100644 --- a/plugin/mouse/mouse.py +++ b/plugin/mouse/mouse.py @@ -1,17 +1,8 @@ import os -from talon import Context, Module, actions, app, clip, cron, ctrl, imgui, settings, ui +from talon import Context, Module, actions, app, clip, ctrl, settings, ui from talon_plugins import eye_zoom_mouse -key = actions.key -self = actions.self -scroll_amount = 0 -click_job = None -scroll_job = None -gaze_job = None -cancel_scroll_on_pop = True -control_mouse_forced = False -hiss_scroll_up = False default_cursor = { "AppStarting": r"%SystemRoot%\Cursors\aero_working.ani", @@ -65,52 +56,12 @@ default=False, desc="When enabled, pop stops mouse drag", ) -mod.setting( - "mouse_enable_hiss_scroll", - type=bool, - default=False, - desc="Hiss noise scrolls down when enabled", -) mod.setting( "mouse_wake_hides_cursor", type=bool, default=False, desc="When enabled, mouse wake will hide the cursor. mouse_wake enables zoom mouse.", ) -mod.setting( - "mouse_hide_mouse_gui", - type=bool, - default=False, - desc="When enabled, the 'Scroll Mouse' GUI will not be shown.", -) -mod.setting( - "mouse_continuous_scroll_amount", - type=int, - default=80, - desc="The default amount used when scrolling continuously", -) -mod.setting( - "mouse_wheel_down_amount", - type=int, - default=120, - desc="The amount to scroll up/down (equivalent to mouse wheel on Windows by default)", -) -mod.setting( - "mouse_wheel_horizontal_amount", - type=int, - default=40, - desc="The amount to scroll left/right", -) - -continuous_scroll_mode = "" - - -@imgui.open(x=700, y=0) -def gui_wheel(gui: imgui.GUI): - gui.text(f"Scroll mode: {continuous_scroll_mode}") - gui.line() - if gui.button("Wheel Stop [stop scrolling]"): - actions.user.mouse_scroll_stop() @mod.action_class @@ -141,19 +92,23 @@ def mouse_drag(button: int): actions.user.mouse_drag_end() # Start drag - ctrl.mouse_click(button=button, down=True) + actions.mouse_drag(button) - def mouse_drag_end(): + def mouse_drag_end() -> bool: """Releases any held mouse buttons""" - for button in ctrl.mouse_buttons_down(): - ctrl.mouse_click(button=button, up=True) + buttons = ctrl.mouse_buttons_down() + if buttons: + for button in buttons: + actions.mouse_release(button) + return True + return False def mouse_drag_toggle(button: int): """If the button is held down, release the button, else start dragging""" if button in list(ctrl.mouse_buttons_down()): - ctrl.mouse_click(button=button, up=True) + actions.mouse_release(button) else: - actions.user.mouse_drag(button=button) + actions.mouse_drag(button) def mouse_sleep(): """Disables control mouse, zoom mouse, and re-enables cursor""" @@ -162,80 +117,9 @@ def mouse_sleep(): actions.tracking.control1_toggle(False) show_cursor_helper(True) - stop_scroll() + actions.user.mouse_scroll_stop() actions.user.mouse_drag_end() - def mouse_scroll_down(amount: float = 1): - """Scrolls down""" - mouse_scroll(amount * settings.get("user.mouse_wheel_down_amount"))() - - def mouse_scroll_down_continuous(): - """Scrolls down continuously""" - global continuous_scroll_mode - continuous_scroll_mode = "scroll down continuous" - mouse_scroll(settings.get("user.mouse_continuous_scroll_amount"))() - - if scroll_job is None: - start_scroll() - - if not settings.get("user.mouse_hide_mouse_gui"): - gui_wheel.show() - - def mouse_scroll_up(amount: float = 1): - """Scrolls up""" - mouse_scroll(-amount * settings.get("user.mouse_wheel_down_amount"))() - - def mouse_scroll_up_continuous(): - """Scrolls up continuously""" - global continuous_scroll_mode - continuous_scroll_mode = "scroll up continuous" - mouse_scroll(-settings.get("user.mouse_continuous_scroll_amount"))() - - if scroll_job is None: - start_scroll() - if not settings.get("user.mouse_hide_mouse_gui"): - gui_wheel.show() - - def mouse_scroll_left(amount: float = 1): - """Scrolls left""" - actions.mouse_scroll( - 0, -amount * settings.get("user.mouse_wheel_horizontal_amount") - ) - - def mouse_scroll_right(amount: float = 1): - """Scrolls right""" - actions.mouse_scroll( - 0, amount * settings.get("user.mouse_wheel_horizontal_amount") - ) - - def mouse_scroll_stop(): - """Stops scrolling""" - stop_scroll() - - def mouse_gaze_scroll(): - """Starts gaze scroll""" - global continuous_scroll_mode - # this calls stop_scroll, which resets continuous_scroll_mode - start_cursor_scrolling() - - continuous_scroll_mode = "gaze scroll" - - if not settings.get("user.mouse_hide_mouse_gui"): - gui_wheel.show() - - # enable 'control mouse' if eye tracker is present and not enabled already - global control_mouse_forced - if not actions.tracking.control_enabled(): - actions.tracking.control_toggle(True) - control_mouse_forced = True - - def mouse_gaze_scroll_toggle(): - """If not scrolling, start gaze scroll, else stop scrolling.""" - if continuous_scroll_mode == "": - actions.user.mouse_gaze_scroll() - else: - actions.user.mouse_scroll_stop() - def copy_mouse_position(): """Copy the current mouse position coordinates""" position = ctrl.mouse_pos() @@ -246,16 +130,6 @@ def mouse_move_center_active_window(): rect = ui.active_window().rect ctrl.mouse_move(rect.left + (rect.width / 2), rect.top + (rect.height / 2)) - def hiss_scroll_up(): - """Change mouse hiss scroll direction to up""" - global hiss_scroll_up - hiss_scroll_up = True - - def hiss_scroll_down(): - """Change mouse hiss scroll direction to down""" - global hiss_scroll_up - hiss_scroll_up = False - def show_cursor_helper(show): """Show/hide the cursor""" @@ -295,124 +169,34 @@ def show_cursor_helper(show): @ctx.action_class("user") class UserActions: def noise_trigger_pop(): - if ( - settings.get("user.mouse_enable_pop_stops_drag") - and ctrl.mouse_buttons_down() - ): - actions.user.mouse_drag_end() - elif settings.get("user.mouse_enable_pop_stops_scroll") and ( - gaze_job or scroll_job - ): - # Allow pop to stop scroll - stop_scroll() - else: - # Otherwise respect the mouse_enable_pop_click setting - setting_val = settings.get("user.mouse_enable_pop_click") - - is_using_eye_tracker = ( - actions.tracking.control_zoom_enabled() - or actions.tracking.control_enabled() - or actions.tracking.control1_enabled() - ) - should_click = ( - setting_val == 2 and not actions.tracking.control_zoom_enabled() - ) or ( - setting_val == 1 - and is_using_eye_tracker - and not actions.tracking.control_zoom_enabled() - ) - if should_click: - ctrl.mouse_click(button=0, hold=16000) - - def noise_trigger_hiss(active: bool): - if settings.get("user.mouse_enable_hiss_scroll"): - if active: - if hiss_scroll_up: - actions.user.mouse_scroll_up_continuous() - else: - actions.user.mouse_scroll_down_continuous() - else: - actions.user.mouse_scroll_stop() - - -def mouse_scroll(amount): - def scroll(): - global scroll_amount - if continuous_scroll_mode: - if (scroll_amount >= 0) == (amount >= 0): - scroll_amount += amount - else: - scroll_amount = amount - actions.mouse_scroll(y=int(amount)) - - return scroll - + dont_click = False -def scroll_continuous_helper(): - global scroll_amount - # print("scroll_continuous_helper") - if scroll_amount and (eye_zoom_mouse.zoom_mouse.state == eye_zoom_mouse.STATE_IDLE): - actions.mouse_scroll(by_lines=False, y=int(scroll_amount / 10)) + if settings.get("user.mouse_enable_pop_stops_drag"): + dont_click = dont_click or actions.user.mouse_drag_end() + if settings.get("user.mouse_enable_pop_stops_scroll"): + # Allow pop to stop scroll + dont_click = dont_click or actions.user.mouse_scroll_stop() -def start_scroll(): - global scroll_job - scroll_job = cron.interval("60ms", scroll_continuous_helper) - - -def gaze_scroll(): - # print("gaze_scroll") - if ( - eye_zoom_mouse.zoom_mouse.state == eye_zoom_mouse.STATE_IDLE - ): # or eye_zoom_mouse.zoom_mouse.state == eye_zoom_mouse.STATE_SLEEP: - x, y = ctrl.mouse_pos() - - # the rect for the window containing the mouse - rect = None - - # on windows, check the active_window first since ui.windows() is not z-ordered - if app.platform == "windows" and ui.active_window().rect.contains(x, y): - rect = ui.active_window().rect - else: - windows = ui.windows() - for w in windows: - if w.rect.contains(x, y): - rect = w.rect - break - - if rect is None: - # print("no window found!") + if dont_click: return - midpoint = rect.y + rect.height / 2 - amount = int(((y - midpoint) / (rect.height / 10)) ** 3) - actions.mouse_scroll(by_lines=False, y=amount) - - # print(f"gaze_scroll: {midpoint} {rect.height} {amount}") - + # Otherwise respect the mouse_enable_pop_click setting + setting_val = settings.get("user.mouse_enable_pop_click") -def stop_scroll(): - global scroll_amount, scroll_job, gaze_job, continuous_scroll_mode - scroll_amount = 0 - if scroll_job: - cron.cancel(scroll_job) - - if gaze_job: - cron.cancel(gaze_job) - - global control_mouse_forced - if control_mouse_forced: - actions.tracking.control_toggle(False) - control_mouse_forced = False - - scroll_job = None - gaze_job = None - gui_wheel.hide() - - continuous_scroll_mode = "" + is_using_eye_tracker = ( + actions.tracking.control_zoom_enabled() + or actions.tracking.control_enabled() + or actions.tracking.control1_enabled() + ) + should_click = ( + setting_val == 2 and not actions.tracking.control_zoom_enabled() + ) or ( + setting_val == 1 + and is_using_eye_tracker + and not actions.tracking.control_zoom_enabled() + ) -def start_cursor_scrolling(): - global scroll_job, gaze_job - stop_scroll() - gaze_job = cron.interval("60ms", gaze_scroll) + if should_click: + ctrl.mouse_click(button=0, hold=16000) diff --git a/plugin/mouse/mouse_scroll.py b/plugin/mouse/mouse_scroll.py new file mode 100644 index 0000000000..f1c9a2f814 --- /dev/null +++ b/plugin/mouse/mouse_scroll.py @@ -0,0 +1,214 @@ +from typing import Literal +from talon import Context, Module, actions, app, cron, ctrl, imgui, settings, ui +from talon_plugins import eye_zoom_mouse + +continuous_scroll_mode = "" +scroll_job = None +gaze_job = None +scroll_dir: Literal[-1, 1] = 1 +hiss_scroll_up = False +control_mouse_forced = False + +mod = Module() +ctx = Context() + + +mod.setting( + "mouse_wheel_down_amount", + type=int, + default=120, + desc="The amount to scroll up/down (equivalent to mouse wheel on Windows by default)", +) +mod.setting( + "mouse_wheel_horizontal_amount", + type=int, + default=40, + desc="The amount to scroll left/right", +) +mod.setting( + "mouse_continuous_scroll_amount", + type=int, + default=80, + desc="The default amount used when scrolling continuously", +) +mod.setting( + "mouse_enable_hiss_scroll", + type=bool, + default=False, + desc="Hiss noise scrolls down when enabled", +) +mod.setting( + "mouse_hide_mouse_gui", + type=bool, + default=False, + desc="When enabled, the 'Scroll Mouse' GUI will not be shown.", +) + + +@imgui.open(x=700, y=0) +def gui_wheel(gui: imgui.GUI): + gui.text(f"Scroll mode: {continuous_scroll_mode}") + gui.line() + if gui.button("Wheel Stop [stop scrolling]"): + actions.user.mouse_scroll_stop() + + +@mod.action_class +class Actions: + def mouse_scroll_up(amount: float = 1): + """Scrolls up""" + y = amount * settings.get("user.mouse_wheel_down_amount") + actions.mouse_scroll(-y) + + def mouse_scroll_down(amount: float = 1): + """Scrolls down""" + y = amount * settings.get("user.mouse_wheel_down_amount") + actions.mouse_scroll(y) + + def mouse_scroll_left(amount: float = 1): + """Scrolls left""" + x = amount * settings.get("user.mouse_wheel_horizontal_amount") + actions.mouse_scroll(0, -x) + + def mouse_scroll_right(amount: float = 1): + """Scrolls right""" + x = amount * settings.get("user.mouse_wheel_horizontal_amount") + actions.mouse_scroll(0, x) + + def mouse_scroll_up_continuous(): + """Scrolls up continuously""" + mouse_scroll_continuous(-1) + + def mouse_scroll_down_continuous(): + """Scrolls down continuously""" + mouse_scroll_continuous(1) + + def mouse_gaze_scroll(): + """Starts gaze scroll""" + global gaze_job, continuous_scroll_mode, control_mouse_forced + + if eye_zoom_mouse.zoom_mouse.state != eye_zoom_mouse.STATE_IDLE: + return + + continuous_scroll_mode = "gaze scroll" + gaze_job = cron.interval("16ms", scroll_gaze_helper) + + if not settings.get("user.mouse_hide_mouse_gui"): + gui_wheel.show() + + # enable 'control mouse' if eye tracker is present and not enabled already + if not actions.tracking.control_enabled(): + actions.tracking.control_toggle(True) + control_mouse_forced = True + + def mouse_gaze_scroll_toggle(): + """If not scrolling, start gaze scroll, else stop scrolling.""" + if continuous_scroll_mode == "": + actions.user.mouse_gaze_scroll() + else: + actions.user.mouse_scroll_stop() + + def mouse_scroll_stop() -> bool: + """Stops scrolling""" + global scroll_job, gaze_job, continuous_scroll_mode, control_mouse_forced + + continuous_scroll_mode = "" + return_value = False + + if scroll_job: + cron.cancel(scroll_job) + scroll_job = None + return_value = True + + if gaze_job: + cron.cancel(gaze_job) + gaze_job = None + return_value = True + + if control_mouse_forced: + actions.tracking.control_toggle(False) + control_mouse_forced = False + + gui_wheel.hide() + + return return_value + + def hiss_scroll_up(): + """Change mouse hiss scroll direction to up""" + global hiss_scroll_up + hiss_scroll_up = True + + def hiss_scroll_down(): + """Change mouse hiss scroll direction to down""" + global hiss_scroll_up + hiss_scroll_up = False + + +@ctx.action_class("user") +class UserActions: + def noise_trigger_hiss(active: bool): + if settings.get("user.mouse_enable_hiss_scroll"): + if active: + if hiss_scroll_up: + actions.user.mouse_scroll_up_continuous() + else: + actions.user.mouse_scroll_down_continuous() + else: + actions.user.mouse_scroll_stop() + + +def mouse_scroll_continuous(new_scroll_dir: Literal[-1, 1]): + global scroll_job, scroll_dir, continuous_scroll_mode + + if eye_zoom_mouse.zoom_mouse.state != eye_zoom_mouse.STATE_IDLE: + return + + if scroll_job: + # Issuing a scroll in the same direction aborts scrolling + if scroll_dir == new_scroll_dir: + cron.cancel(scroll_job) + scroll_job = None + continuous_scroll_mode = "" + else: + scroll_dir = new_scroll_dir + else: + scroll_dir = new_scroll_dir + continuous_scroll_mode = "scroll down continuous" + scroll_continuous_helper() + scroll_job = cron.interval("16ms", scroll_continuous_helper) + + if not settings.get("user.mouse_hide_mouse_gui"): + gui_wheel.show() + + +def scroll_continuous_helper(): + scroll_amount = settings.get("user.mouse_continuous_scroll_amount") + y = scroll_amount * scroll_dir / 10 + actions.mouse_scroll(y) + + +def scroll_gaze_helper(): + x, y = ctrl.mouse_pos() + + # The window containing the mouse + window = get_window_containing(x, y) + + if window is None: + return + + rect = window.rect + midpoint = rect.center.y + amount = ((y - midpoint) / (rect.height / 10)) ** 3 + actions.mouse_scroll(amount) + + +def get_window_containing(x: float, y: float): + # on windows, check the active_window first since ui.windows() is not z-ordered + if app.platform == "windows" and ui.active_window().rect.contains(x, y): + return ui.active_window() + + for window in ui.windows(): + if window.rect.contains(x, y): + return window + + return None From 1dbb6d095e661f0ca35eb6ef7e18f07450b56f94 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Tue, 12 Nov 2024 14:19:41 +0000 Subject: [PATCH 2/6] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- plugin/mouse/mouse.py | 1 - plugin/mouse/mouse_scroll.py | 1 + 2 files changed, 1 insertion(+), 1 deletion(-) diff --git a/plugin/mouse/mouse.py b/plugin/mouse/mouse.py index 124bf1b64a..a005ac2f14 100644 --- a/plugin/mouse/mouse.py +++ b/plugin/mouse/mouse.py @@ -3,7 +3,6 @@ from talon import Context, Module, actions, app, clip, ctrl, settings, ui from talon_plugins import eye_zoom_mouse - default_cursor = { "AppStarting": r"%SystemRoot%\Cursors\aero_working.ani", "Arrow": r"%SystemRoot%\Cursors\aero_arrow.cur", diff --git a/plugin/mouse/mouse_scroll.py b/plugin/mouse/mouse_scroll.py index f1c9a2f814..0c68311e60 100644 --- a/plugin/mouse/mouse_scroll.py +++ b/plugin/mouse/mouse_scroll.py @@ -1,4 +1,5 @@ from typing import Literal + from talon import Context, Module, actions, app, cron, ctrl, imgui, settings, ui from talon_plugins import eye_zoom_mouse From 1a2bb60fead755af2f7683b9909b76192aab9c23 Mon Sep 17 00:00:00 2001 From: Andreas Arvidsson Date: Tue, 12 Nov 2024 15:32:58 +0100 Subject: [PATCH 3/6] Remove unnecessary list wrapping --- plugin/mouse/mouse.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugin/mouse/mouse.py b/plugin/mouse/mouse.py index a005ac2f14..8ba6649a7f 100644 --- a/plugin/mouse/mouse.py +++ b/plugin/mouse/mouse.py @@ -104,7 +104,7 @@ def mouse_drag_end() -> bool: def mouse_drag_toggle(button: int): """If the button is held down, release the button, else start dragging""" - if button in list(ctrl.mouse_buttons_down()): + if button in ctrl.mouse_buttons_down(): actions.mouse_release(button) else: actions.mouse_drag(button) From 8e29c2a6e64ee47cc8227a74d05d197a410a067f Mon Sep 17 00:00:00 2001 From: Andreas Arvidsson Date: Tue, 12 Nov 2024 15:35:15 +0100 Subject: [PATCH 4/6] Cleanup --- plugin/mouse/mouse.py | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/plugin/mouse/mouse.py b/plugin/mouse/mouse.py index 8ba6649a7f..da8e0ce841 100644 --- a/plugin/mouse/mouse.py +++ b/plugin/mouse/mouse.py @@ -170,12 +170,15 @@ class UserActions: def noise_trigger_pop(): dont_click = False + # Allow pop to stop drag if settings.get("user.mouse_enable_pop_stops_drag"): - dont_click = dont_click or actions.user.mouse_drag_end() + if actions.user.mouse_drag_end(): + dont_click = True - if settings.get("user.mouse_enable_pop_stops_scroll"): # Allow pop to stop scroll - dont_click = dont_click or actions.user.mouse_scroll_stop() + if settings.get("user.mouse_enable_pop_stops_scroll"): + if actions.user.mouse_scroll_stop(): + dont_click = True if dont_click: return From aa23e6ac6b981133b3374cc45bb172ef13cb036b Mon Sep 17 00:00:00 2001 From: Andreas Arvidsson Date: Tue, 12 Nov 2024 15:35:51 +0100 Subject: [PATCH 5/6] Refactor --- plugin/mouse/mouse.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugin/mouse/mouse.py b/plugin/mouse/mouse.py index da8e0ce841..8a40942f61 100644 --- a/plugin/mouse/mouse.py +++ b/plugin/mouse/mouse.py @@ -175,7 +175,7 @@ def noise_trigger_pop(): if actions.user.mouse_drag_end(): dont_click = True - # Allow pop to stop scroll + # Allow pop to stop scroll if settings.get("user.mouse_enable_pop_stops_scroll"): if actions.user.mouse_scroll_stop(): dont_click = True From 2604d2c7ec3a020928ce72c7ee77b2174606dc54 Mon Sep 17 00:00:00 2001 From: Andreas Arvidsson Date: Wed, 13 Nov 2024 07:15:13 +0100 Subject: [PATCH 6/6] Reorder --- plugin/mouse/mouse.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugin/mouse/mouse.py b/plugin/mouse/mouse.py index d62df42d18..5ee4db412c 100644 --- a/plugin/mouse/mouse.py +++ b/plugin/mouse/mouse.py @@ -78,8 +78,8 @@ def mouse_sleep(): actions.tracking.control_toggle(False) actions.tracking.control1_toggle(False) - actions.user.mouse_scroll_stop() actions.user.mouse_cursor_show() + actions.user.mouse_scroll_stop() actions.user.mouse_drag_end() def copy_mouse_position():