diff --git a/safeeyes/config/locale/safeeyes.pot b/safeeyes/config/locale/safeeyes.pot index 0747768a..f7e00a69 100644 --- a/safeeyes/config/locale/safeeyes.pot +++ b/safeeyes/config/locale/safeeyes.pot @@ -561,3 +561,6 @@ msgstr "" #, python-format msgid "Old stylesheet found at '%(old)s', ignoring. For custom styles, create a new stylesheet in '%(new)s' instead." msgstr "" + +msgid "Customizing the postpone and skip shortcuts does not work on Wayland." +msgstr "" diff --git a/safeeyes/config/style/safeeyes_style.css b/safeeyes/config/style/safeeyes_style.css index 6df25a45..f2b62555 100644 --- a/safeeyes/config/style/safeeyes_style.css +++ b/safeeyes/config/style/safeeyes_style.css @@ -109,19 +109,3 @@ opacity: 0.9; border-color: transparent; } - -.btn_menu { - border-width: 0px; - border-radius: 0px; - border-image: None; - background: white; - border-color: transparent; -} - -.btn_menu:hover { - border-width: 0px; - border-radius: 0px; - border-image: None; - background: whitesmoke; - border-color: transparent; -} diff --git a/safeeyes/plugins/trayicon/plugin.py b/safeeyes/plugins/trayicon/plugin.py index fbbeb1bc..af9dfa0f 100644 --- a/safeeyes/plugins/trayicon/plugin.py +++ b/safeeyes/plugins/trayicon/plugin.py @@ -26,7 +26,6 @@ from safeeyes import utility from safeeyes.translations import translate as _ import threading -import time import typing """ @@ -429,6 +428,9 @@ def set_xayatanalabel(self, label): class TrayIcon: """Create and show the tray icon along with the tray menu.""" + _animation_timeout_id: typing.Optional[int] = None + _animation_icon_enabled: bool = False + def __init__(self, context, plugin_config): self.context = context self.on_show_settings = context["api"]["show_settings"] @@ -446,7 +448,6 @@ def __init__(self, context, plugin_config): self.idle_condition = threading.Condition() self.lock = threading.Lock() self.allow_disabling = plugin_config["allow_disabling"] - self.animate = False self.menu_locked = False session_bus = Gio.bus_get_sync(Gio.BusType.SESSION) @@ -785,35 +786,37 @@ def __schedule_resume(self, time_minutes): if not self.active: utility.execute_main_thread(self.on_enable_clicked) - def start_animation(self): - if not self.active or not self.animate: - return - utility.execute_main_thread( - lambda: self.sni_service.set_icon("io.github.slgobinath.SafeEyes-disabled") - ) - time.sleep(0.5) - utility.execute_main_thread( - lambda: self.sni_service.set_icon("io.github.slgobinath.SafeEyes-enabled") - ) - if self.animate and self.active: - time.sleep(0.5) - if self.animate and self.active: - utility.start_thread(self.start_animation) + def start_animation(self) -> None: + if self._animation_timeout_id is not None: + self.stop_animation() + + self._animation_icon_enabled = False + + self._animation_timeout_id = GLib.timeout_add(500, self._do_animate) + + def _do_animate(self) -> bool: + if not self.active: + self._animation_timeout_id = None + return GLib.SOURCE_REMOVE + + if self._animation_icon_enabled: + self.sni_service.set_icon("io.github.slgobinath.SafeEyes-enabled") + else: + self.sni_service.set_icon("io.github.slgobinath.SafeEyes-disabled") + + self._animation_icon_enabled = not self._animation_icon_enabled + + return GLib.SOURCE_CONTINUE + + def stop_animation(self) -> None: + if self._animation_timeout_id is not None: + GLib.source_remove(self._animation_timeout_id) + self._animation_timeout_id = None - def stop_animation(self): - self.animate = False if self.active: - utility.execute_main_thread( - lambda: self.sni_service.set_icon( - "io.github.slgobinath.SafeEyes-enabled" - ) - ) + self.sni_service.set_icon("io.github.slgobinath.SafeEyes-enabled") else: - utility.execute_main_thread( - lambda: self.sni_service.set_icon( - "io.github.slgobinath.SafeEyes-disabled" - ) - ) + self.sni_service.set_icon("io.github.slgobinath.SafeEyes-disabled") def init(ctx, safeeyes_cfg, plugin_config): @@ -839,7 +842,6 @@ def on_pre_break(break_obj): """Disable the menu if strict_break is enabled.""" if safeeyes_config.get("strict_break"): tray_icon.lock_menu() - tray_icon.animate = True tray_icon.start_animation() diff --git a/safeeyes/ui/break_screen.py b/safeeyes/ui/break_screen.py index e74594a7..aeacff15 100644 --- a/safeeyes/ui/break_screen.py +++ b/safeeyes/ui/break_screen.py @@ -52,13 +52,15 @@ def __init__(self, application, context, on_skipped, on_postponed): self.enable_postpone = False self.enable_shortcut = False self.is_pretified = False - self.keycode_shortcut_postpone = 65 - self.keycode_shortcut_skip = 9 + self.keycode_shortcut_postpone = 65 # Space + self.keycode_shortcut_skip = 9 # Escape self.on_postponed = on_postponed self.on_skipped = on_skipped self.shortcut_disable_time = 2 self.strict_break = False self.windows = [] + self.show_skip_button = False + self.show_postpone_button = False if not self.context["is_wayland"]: self.x11_display = Display() @@ -69,6 +71,17 @@ def initialize(self, config): self.enable_postpone = config.get("allow_postpone", False) self.keycode_shortcut_postpone = config.get("shortcut_postpone", 65) self.keycode_shortcut_skip = config.get("shortcut_skip", 9) + + if self.context["is_wayland"] and ( + self.keycode_shortcut_postpone != 65 or self.keycode_shortcut_skip != 9 + ): + logging.warning( + _( + "Customizing the postpone and skip shortcuts does not work on " + "Wayland." + ) + ) + self.shortcut_disable_time = config.get("shortcut_disable_time", 2) self.strict_break = config.get("strict_break", False) @@ -146,7 +159,12 @@ def __show_break_screen(self, message, image_path, widget, tray_actions): logging.info("Show break screens in %d display(s)", len(monitors)) skip_button_disabled = self.context.get("skip_button_disabled", False) + self.show_skip_button = not self.strict_break and not skip_button_disabled + postpone_button_disabled = self.context.get("postpone_button_disabled", False) + self.show_postpone_button = ( + self.enable_postpone and not postpone_button_disabled + ) i = 0 @@ -157,6 +175,15 @@ def __show_break_screen(self, message, image_path, widget, tray_actions): window = builder.get_object("window_main") window.set_application(self.application) window.connect("close-request", self.on_window_delete) + + if self.context["is_wayland"]: + # Note: in theory, this could also be used on X11 + # however, that already has its own implementation below + controller = Gtk.EventControllerKey() + controller.connect("key_pressed", self.on_key_pressed_wayland) + controller.set_propagation_phase(Gtk.PropagationPhase.CAPTURE) + window.add_controller(controller) + window.set_title("SafeEyes-" + str(i)) lbl_message = builder.get_object("lbl_message") lbl_count = builder.get_object("lbl_count") @@ -182,7 +209,7 @@ def __show_break_screen(self, message, image_path, widget, tray_actions): toolbar_button.show() # Add the buttons - if self.enable_postpone and not postpone_button_disabled: + if self.show_postpone_button: # Add postpone button btn_postpone = Gtk.Button.new_with_label(_("Postpone")) btn_postpone.get_style_context().add_class("btn_postpone") @@ -190,7 +217,7 @@ def __show_break_screen(self, message, image_path, widget, tray_actions): btn_postpone.set_visible(True) box_buttons.append(btn_postpone) - if not self.strict_break and not skip_button_disabled: + if self.show_skip_button: # Add the skip button btn_skip = Gtk.Button.new_with_label(_("Skip")) btn_skip.get_style_context().add_class("btn_skip") @@ -214,6 +241,11 @@ def __show_break_screen(self, message, image_path, widget, tray_actions): window.fullscreen_on_monitor(monitor) window.present() + # this ensures that none of the buttons is in focus immediately + # otherwise, pressing space presses that button instead of triggering the + # shortcut + window.set_focus(None) + if not self.context["is_wayland"]: self.__window_set_keep_above_x11(window) @@ -284,13 +316,13 @@ def __lock_keyboard_x11(self): if self.enable_shortcut and event.type == X.KeyPress: if ( event.detail == self.keycode_shortcut_skip - and not self.strict_break + and self.show_skip_button ): self.skip_break() break elif ( - self.enable_postpone - and event.detail == self.keycode_shortcut_postpone + event.detail == self.keycode_shortcut_postpone + and self.show_postpone_button ): self.postpone_break() break @@ -298,6 +330,17 @@ def __lock_keyboard_x11(self): # Reduce the CPU usage by sleeping for a second time.sleep(1) + def on_key_pressed_wayland(self, event_controller_key, keyval, keycode, state): + if self.enable_shortcut: + if keyval == Gdk.KEY_space and self.show_postpone_button: + self.postpone_break() + return True + elif keyval == Gdk.KEY_Escape and self.show_skip_button: + self.skip_break() + return True + + return False + def __release_keyboard_x11(self): """Release the locked keyboard.""" logging.info("Unlock the keyboard")