Skip to content

Commit 56f6d61

Browse files
committed
Refactor X11 backend service layout
1 parent 86a09c4 commit 56f6d61

77 files changed

Lines changed: 3275 additions & 1705 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

docking/app.py

Lines changed: 15 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -74,9 +74,11 @@
7474
gi.require_version("Gtk", "3.0")
7575
from gi.repository import GLib, Gtk
7676

77+
from docking.applets.services import AppletServices
7778
from docking.core.config import Config
7879
from docking.core.theme import Theme
7980
from docking.ipc import DockItemsService
81+
from docking.platform.backends.base import SessionBackend
8082
from docking.platform.backends.selection import create_session_backend
8183
from docking.platform.environment import apply_tweaks, detect_desktop
8284
from docking.platform.launcher import Launcher
@@ -99,6 +101,15 @@ def main() -> None:
99101
model = DockModel(config=config, launcher=launcher)
100102
renderer = DockRenderer()
101103
backend = create_session_backend(config=config, launcher=launcher, model=model)
104+
model.set_applet_services(
105+
AppletServices(
106+
desktop_actions=backend.desktop_actions,
107+
workspaces=backend.workspaces,
108+
window_picker=backend.window_picker,
109+
idle=backend.idle,
110+
screen_capture=backend.screen_capture,
111+
)
112+
)
102113
unity = UnityLauncherListener(model=model)
103114

104115
window = build_dock_window(
@@ -108,6 +119,7 @@ def main() -> None:
108119
theme=theme,
109120
window_tracker=backend.windows,
110121
preview_service=backend.previews,
122+
surface_service=backend.surface,
111123
visibility_service=backend.visibility,
112124
launcher=launcher,
113125
)
@@ -130,17 +142,15 @@ def main() -> None:
130142
window.stop_update_checks()
131143
new_year.stop()
132144
unity.stop()
133-
backend.stop()
134145
model.stop_applets()
146+
backend.stop()
135147

136148

137149
def _start_runtime(
138-
items_service: DockItemsService, model: DockModel, backend: object
150+
items_service: DockItemsService, model: DockModel, backend: SessionBackend
139151
) -> bool:
140152
"""Start background runtime pieces after the window has been shown."""
141-
backend_start = getattr(backend, "start", None)
142-
if callable(backend_start):
143-
backend_start()
153+
backend.start()
144154
items_service.start()
145155
model.start_applets()
146156
return False

docking/applets/base.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -83,6 +83,7 @@
8383
from docking.log import get_logger
8484

8585
if TYPE_CHECKING:
86+
from docking.applets.services import AppletServices
8687
from docking.core.config import Config
8788

8889
log = get_logger("applets.base")
@@ -455,6 +456,11 @@ def apply_prefs(self) -> None:
455456
"""
456457
return
457458

459+
def set_services(self, services: AppletServices) -> None:
460+
"""Attach backend services; applets that need them override this hook."""
461+
_ = services
462+
return
463+
458464
def start(self, notify: Callable[[], None]) -> None:
459465
"""Start timers/monitors. Call notify() to trigger redraw."""
460466
self._notify = notify

docking/applets/colorpicker/applet.py

Lines changed: 17 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -26,18 +26,20 @@
2626
from docking.applets.base import Applet
2727
from docking.applets.colorpicker import meta
2828
from docking.applets.colorpicker.render import create_icon
29-
from docking.applets.colorpicker.state import pick_pixel, rgb_to_hex
29+
from docking.applets.colorpicker.state import rgb_to_hex
3030
from docking.applets.menu import menu_sections
3131
from docking.applets.popup import (
3232
create_capture_overlay,
3333
dismiss_capture_overlay,
3434
draw_transparent_capture_overlay,
3535
)
36+
from docking.applets.services import AppletServices
3637
from docking.i18n import _
3738
from docking.log import get_logger, with_context
3839

3940
if TYPE_CHECKING:
4041
from docking.core.config import Config
42+
from docking.platform.backends.base import ScreenCaptureService
4143

4244
log = with_context(get_logger(name="colorpicker"), applet_id=meta.id)
4345

@@ -62,6 +64,7 @@ def __init__(self, icon_size: int, config: Config | None = None) -> None:
6264
self._hex = ""
6365
self._show_hex = True
6466
self._overlay: Gtk.Window | None = None
67+
self._screen_capture: ScreenCaptureService | None = None
6568

6669
if config:
6770
prefs = config.applet_prefs.get(meta.id, {})
@@ -75,6 +78,9 @@ def __init__(self, icon_size: int, config: Config | None = None) -> None:
7578
super().__init__(icon_size=icon_size, config=config)
7679
self.present()
7780

81+
def set_services(self, services: AppletServices) -> None:
82+
self._screen_capture = services.screen_capture
83+
7884
def create_icon(self, size: int) -> GdkPixbuf.Pixbuf | None:
7985
return create_icon(
8086
size=size,
@@ -91,6 +97,10 @@ def on_clicked(self) -> None:
9197
"""Enter pick mode - fullscreen transparent overlay captures click."""
9298
self._start_pick()
9399

100+
def stop(self) -> None:
101+
self._dismiss_overlay()
102+
super().stop()
103+
94104
def get_menu_items(self) -> list[Gtk.MenuItem]:
95105
primary: list[Gtk.MenuItem] = []
96106

@@ -112,7 +122,7 @@ def _on_toggle_hex(self, widget: Gtk.CheckMenuItem) -> None:
112122

113123
def _start_pick(self) -> None:
114124
"""Create fullscreen transparent overlay to capture a click."""
115-
if self._overlay:
125+
if self._overlay or self._screen_capture is None:
116126
return
117127

118128
self._overlay = create_capture_overlay(
@@ -130,7 +140,11 @@ def _on_overlay_click(self, _widget: Gtk.Window, event: Gdk.EventButton) -> bool
130140
"""Sample pixel at click position."""
131141
self._dismiss_overlay()
132142

133-
pixel = pick_pixel(x=int(event.x_root), y=int(event.y_root))
143+
pixel = (
144+
self._screen_capture.pick_color(x=int(event.x_root), y=int(event.y_root))
145+
if self._screen_capture is not None
146+
else None
147+
)
134148
if pixel:
135149
r, g, b = pixel
136150
self._r = r / 255.0

docking/applets/colorpicker/state.py

Lines changed: 0 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -15,23 +15,6 @@
1515

1616
from __future__ import annotations
1717

18-
import gi
19-
20-
gi.require_version("Gdk", "3.0")
21-
from gi.repository import Gdk
22-
23-
24-
def pick_pixel(x: int, y: int) -> tuple[int, int, int] | None:
25-
"""Read a single pixel from the root window at screen coordinates."""
26-
root = Gdk.get_default_root_window()
27-
if root is None:
28-
return None
29-
pb = Gdk.pixbuf_get_from_window(root, x, y, 1, 1)
30-
if pb is None:
31-
return None
32-
p = pb.get_pixels()
33-
return (p[0], p[1], p[2])
34-
3518

3619
def rgb_to_hex(r: int, g: int, b: int) -> str:
3720
"""Convert RGB to uppercase hex string."""

docking/applets/deskpresence/applet.py

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,6 @@
2828

2929
from docking.applets.base import Applet
3030
from docking.applets.deskpresence import meta
31-
from docking.applets.deskpresence.idle import get_idle_ms
3231
from docking.applets.deskpresence.render import render_icon
3332
from docking.applets.deskpresence.state import (
3433
DEFAULT_POLL_INTERVAL_S,
@@ -42,13 +41,15 @@
4241
state_from_prefs,
4342
)
4443
from docking.applets.menu import disabled_menu_item, menu_sections, radio_submenu
44+
from docking.applets.services import AppletServices
4545
from docking.core.math import clamp
4646
from docking.i18n import _
4747
from docking.log import get_logger, with_context
4848

4949
if TYPE_CHECKING:
5050
from docking.applets.deskpresence.state import PresenceState
5151
from docking.core.config import Config
52+
from docking.platform.backends.base import IdleService
5253

5354
log = with_context(get_logger(name="deskpresence"), applet_id=meta.id)
5455

@@ -70,7 +71,7 @@ def __init__(self, icon_size: int, config: Config | None = None) -> None:
7071
self._timer_id: int = 0
7172
self._pulse_timer_id: int = 0
7273
self._pulse_phase: float = 0.0
73-
self._idle_probe: Callable[[], int | None] = get_idle_ms
74+
self._idle_service: IdleService | None = None
7475

7576
prefs = prefs_from_mapping(
7677
config.applet_prefs.get(meta.id, {}) if config else None
@@ -80,6 +81,9 @@ def __init__(self, icon_size: int, config: Config | None = None) -> None:
8081
super().__init__(icon_size=icon_size, config=config)
8182
self.present()
8283

84+
def set_services(self, services: AppletServices) -> None:
85+
self._idle_service = services.idle
86+
8387
def create_icon(self, size: int) -> GdkPixbuf.Pixbuf | None:
8488
phase = self._pulse_phase if self._state.presence is Presence.AT_DESK else None
8589
return render_icon(
@@ -148,7 +152,12 @@ def _tick_once(self) -> bool:
148152
return False
149153

150154
def _tick(self) -> bool:
151-
idle_ms = self._idle_probe()
155+
idle_seconds = (
156+
self._idle_service.idle_seconds()
157+
if self._idle_service is not None
158+
else None
159+
)
160+
idle_ms = int(idle_seconds * 1000) if idle_seconds is not None else None
152161
apply_tick(
153162
state=self._state,
154163
idle_ms=idle_ms,

docking/applets/desktop/applet.py

Lines changed: 11 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -19,18 +19,18 @@
1919

2020
import gi
2121

22-
gi.require_version("Wnck", "3.0")
2322
gi.require_version("GdkPixbuf", "2.0")
24-
from gi.repository import GdkPixbuf, Wnck
23+
from gi.repository import GdkPixbuf
2524

2625
from docking.applets.base import Applet
2726
from docking.applets.desktop import meta
2827
from docking.applets.desktop.render import create_icon
29-
from docking.applets.desktop.state import next_showing_desktop
3028
from docking.i18n import _
3129

3230
if TYPE_CHECKING:
31+
from docking.applets.services import AppletServices
3332
from docking.core.config import Config
33+
from docking.platform.backends.base import DesktopActionService
3434

3535

3636
class DesktopApplet(Applet):
@@ -42,16 +42,18 @@ class DesktopApplet(Applet):
4242
supports_system_icon = True
4343

4444
def __init__(self, icon_size: int, config: Config | None = None) -> None:
45+
self._desktop_actions: DesktopActionService | None = None
4546
super().__init__(icon_size=icon_size, config=config)
4647
self.present()
4748

49+
def set_services(self, services: AppletServices) -> None:
50+
self._desktop_actions = services.desktop_actions
51+
4852
def create_docking_icon(self, size: int) -> GdkPixbuf.Pixbuf | None:
4953
return create_icon(size=size)
5054

5155
def on_clicked(self) -> None:
52-
"""Toggle show desktop via Wnck."""
53-
screen = Wnck.Screen.get_default()
54-
screen.force_update()
55-
screen.toggle_showing_desktop(
56-
next_showing_desktop(current=screen.get_showing_desktop())
57-
)
56+
"""Toggle show desktop through the selected backend service."""
57+
if self._desktop_actions is None:
58+
return
59+
self._desktop_actions.show_desktop()

docking/applets/services.py

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
# Author: Eduardo Mucelli Rezende Oliveira
2+
# E-mail: edumucelli@gmail.com
3+
#
4+
# This program is free software: you can redistribute it and/or modify
5+
# it under the terms of the GNU General Public License as published by
6+
# the Free Software Foundation, either version 3 of the License, or
7+
# (at your option) any later version.
8+
#
9+
# This program is distributed in the hope that it will be useful,
10+
# but WITHOUT ANY WARRANTY; without even the implied warranty of
11+
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12+
# GNU General Public License for more details.
13+
14+
"""Small backend services exposed to applets."""
15+
16+
from __future__ import annotations
17+
18+
from dataclasses import dataclass
19+
20+
from docking.platform.backends.base import (
21+
DesktopActionService,
22+
IdleService,
23+
ScreenCaptureService,
24+
WindowPickService,
25+
WorkspaceService,
26+
)
27+
28+
29+
@dataclass(frozen=True)
30+
class AppletServices:
31+
"""Optional backend services consumed by platform-sensitive applets."""
32+
33+
desktop_actions: DesktopActionService | None = None
34+
workspaces: WorkspaceService | None = None
35+
window_picker: WindowPickService | None = None
36+
idle: IdleService | None = None
37+
screen_capture: ScreenCaptureService | None = None

0 commit comments

Comments
 (0)