Browse Source

added tray icon

Fabian Peter Hammerle 4 years ago
parent
commit
1e080ec84c
3 changed files with 98 additions and 0 deletions
  1. 2 0
      CHANGELOG.md
  2. 13 0
      rescriptoon/__init__.py
  3. 83 0
      rescriptoon/_ui.py

+ 2 - 0
CHANGELOG.md

@@ -5,3 +5,5 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
 and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
 
 ## Unreleased
+### Added
+- simple tray icon indicating whether extended controls are enabled

+ 13 - 0
rescriptoon/__init__.py

@@ -33,6 +33,7 @@ from rescriptoon._actions import (
     ToggleOverlayAction,
 )
 from rescriptoon._keys import keysym_to_label
+from rescriptoon._ui import SystemTrayUnavailable, TrayIcon
 
 _TOONTOWN_ENGINE_WINDOW_NAME = "Toontown Rewritten"
 
@@ -110,6 +111,10 @@ class Overlay:
         self._active_key_registry = {}
         self._enabled = False
         self._engine_windows = None
+        try:
+            self._tray_icon = TrayIcon(display=self._xdisplay)
+        except SystemTrayUnavailable:
+            self._tray_icon = None
 
     @property
     def xdisplay(self) -> Xlib.display.Display:
@@ -148,11 +153,17 @@ class Overlay:
             _x_wait_for_event(self.xdisplay, timeout_seconds=0.05)
         self._disable()
 
+    def _draw_tray_icon(self) -> None:
+        if self._tray_icon:
+            self._tray_icon.draw(self.enabled)
+
     def _handle_xevent(self, xevent: Xlib.protocol.rq.Event) -> None:
         if isinstance(
             xevent, (Xlib.protocol.event.KeyPress, Xlib.protocol.event.KeyRelease)
         ):
             self._handle_xkeyevent(xevent)
+        elif self._tray_icon and xevent.type == Xlib.X.ConfigureNotify:
+            self._draw_tray_icon()
 
     def _handle_xkeyevent(
         self,
@@ -178,6 +189,7 @@ class Overlay:
             if keysym != self._toggle_keysym:
                 self._grab_key(self.xdisplay.keysym_to_keycode(keysym),)
         self._enabled = True
+        self._draw_tray_icon()
         logging.info(
             "rescriptoon is now enabled. press %s to disable.",
             self._toggle_keysym_name,
@@ -188,6 +200,7 @@ class Overlay:
             if keysym != self._toggle_keysym:
                 self._ungrab_key(self.xdisplay.keysym_to_keycode(keysym),)
         self._enabled = False
+        self._draw_tray_icon()
 
     def disable(self) -> None:
         self._disable()

+ 83 - 0
rescriptoon/_ui.py

@@ -0,0 +1,83 @@
+"""
+rescriptoon
+Copyright (C) 2018-2019 Fabian Peter Hammerle <fabian@hammerle.me>
+
+This program is free software: you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation, either version 3 of the License, or
+(at your option) any later version.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program.  If not, see <https://www.gnu.org/licenses/>.
+"""
+
+import typing
+
+import Xlib.display
+
+
+def _get_system_tray(
+    display: Xlib.display.Display,
+) -> typing.Optional["Xlib.display.Window"]:
+    tray: "Xlib.display.Window" = display.get_selection_owner(
+        display.intern_atom("_NET_SYSTEM_TRAY_S{}".format(display.get_default_screen()))
+    )
+    if tray != Xlib.X.NONE:
+        return tray
+    return None
+
+
+class SystemTrayUnavailable(Exception):
+    pass
+
+
+def _add_window_to_system_tray(
+    display: Xlib.display.Display, window: "Xlib.display.Window"
+) -> None:
+    system_tray = _get_system_tray(display)
+    if not system_tray:
+        raise SystemTrayUnavailable()
+    display.send_event(
+        system_tray,
+        Xlib.display.event.ClientMessage(
+            client_type=display.intern_atom("_NET_SYSTEM_TRAY_OPCODE"),
+            window=system_tray.id,
+            data=(32, (Xlib.X.CurrentTime, 0, window.id, 0, 0,),),
+        ),
+    )
+
+
+class TrayIcon:
+    def __init__(self, display: "Xlib.display.Display"):
+        self._display = display
+        screen = display.screen()
+        self._window = screen.root.create_window(
+            # x, y
+            -1,
+            -1,
+            # width, height
+            1,
+            1,
+            # border width
+            0,
+            screen.root_depth,
+            event_mask=Xlib.X.StructureNotifyMask,
+        )
+        self._window.set_wm_class("RescriptoonTrayIcon", "Rescriptoon")
+        self._graphics_context = self._window.create_gc()
+        colormap = screen.default_colormap
+        self._color_enabled = colormap.alloc_named_color("green").pixel
+        self._color_disabled = colormap.alloc_named_color("red").pixel
+        _add_window_to_system_tray(display=display, window=self._window)
+
+    def draw(self, enabled: bool) -> None:
+        dim = self._window.get_geometry()
+        self._graphics_context.change(
+            foreground=self._color_enabled if enabled else self._color_disabled
+        )
+        self._window.fill_rectangle(self._graphics_context, 0, 0, dim.width, dim.height)