solstone-linux 0.4.1__tar.gz → 0.4.3__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {solstone_linux-0.4.1 → solstone_linux-0.4.3}/AGENTS.md +13 -1
- {solstone_linux-0.4.1 → solstone_linux-0.4.3}/CHANGELOG.md +23 -0
- {solstone_linux-0.4.1 → solstone_linux-0.4.3}/INSTALL.md +23 -10
- {solstone_linux-0.4.1 → solstone_linux-0.4.3}/Makefile +1 -1
- {solstone_linux-0.4.1 → solstone_linux-0.4.3}/PKG-INFO +10 -4
- {solstone_linux-0.4.1 → solstone_linux-0.4.3}/README.md +8 -2
- solstone_linux-0.4.3/contrib/icons/hicolor/128x128/apps/solstone-observer.png +0 -0
- solstone_linux-0.4.3/contrib/icons/hicolor/16x16/apps/solstone-observer.png +0 -0
- solstone_linux-0.4.3/contrib/icons/hicolor/24x24/apps/solstone-observer.png +0 -0
- solstone_linux-0.4.3/contrib/icons/hicolor/256x256/apps/solstone-observer.png +0 -0
- solstone_linux-0.4.3/contrib/icons/hicolor/32x32/apps/solstone-observer.png +0 -0
- solstone_linux-0.4.3/contrib/icons/hicolor/48x48/apps/solstone-observer.png +0 -0
- solstone_linux-0.4.3/contrib/icons/hicolor/512x512/apps/solstone-observer.png +0 -0
- solstone_linux-0.4.3/contrib/icons/hicolor/64x64/apps/solstone-observer.png +0 -0
- solstone_linux-0.4.3/contrib/icons/hicolor/scalable/apps/solstone-observer.svg +18 -0
- {solstone_linux-0.4.1 → solstone_linux-0.4.3}/contrib/icons/hicolor/scalable/status/solstone-error.svg +1 -1
- {solstone_linux-0.4.1 → solstone_linux-0.4.3}/contrib/icons/hicolor/scalable/status/solstone-paused.svg +1 -1
- {solstone_linux-0.4.1/src/solstone_linux → solstone_linux-0.4.3/contrib}/icons/hicolor/scalable/status/solstone-recording.svg +1 -1
- {solstone_linux-0.4.1/src/solstone_linux → solstone_linux-0.4.3/contrib}/icons/hicolor/scalable/status/solstone-syncing.svg +5 -5
- {solstone_linux-0.4.1 → solstone_linux-0.4.3}/pyproject.toml +2 -2
- {solstone_linux-0.4.1 → solstone_linux-0.4.3}/scripts/release.sh +10 -0
- {solstone_linux-0.4.1 → solstone_linux-0.4.3}/src/solstone_linux/__init__.py +1 -1
- {solstone_linux-0.4.1 → solstone_linux-0.4.3}/src/solstone_linux/activity.py +65 -36
- solstone_linux-0.4.3/src/solstone_linux/audio_detect.py +66 -0
- {solstone_linux-0.4.1 → solstone_linux-0.4.3}/src/solstone_linux/audio_recorder.py +59 -4
- solstone_linux-0.4.3/src/solstone_linux/capture_stats.py +88 -0
- {solstone_linux-0.4.1 → solstone_linux-0.4.3}/src/solstone_linux/chat_bridge.py +122 -100
- {solstone_linux-0.4.1 → solstone_linux-0.4.3}/src/solstone_linux/cli.py +33 -19
- {solstone_linux-0.4.1 → solstone_linux-0.4.3}/src/solstone_linux/config.py +41 -16
- {solstone_linux-0.4.1 → solstone_linux-0.4.3}/src/solstone_linux/dbus_service.py +5 -34
- {solstone_linux-0.4.1 → solstone_linux-0.4.3}/src/solstone_linux/dbusmenu.py +3 -3
- {solstone_linux-0.4.1 → solstone_linux-0.4.3}/src/solstone_linux/doctor.py +13 -3
- solstone_linux-0.4.3/src/solstone_linux/event_sender.py +123 -0
- solstone_linux-0.4.3/src/solstone_linux/icons/hicolor/128x128/apps/solstone-observer.png +0 -0
- solstone_linux-0.4.3/src/solstone_linux/icons/hicolor/16x16/apps/solstone-observer.png +0 -0
- solstone_linux-0.4.3/src/solstone_linux/icons/hicolor/24x24/apps/solstone-observer.png +0 -0
- solstone_linux-0.4.3/src/solstone_linux/icons/hicolor/256x256/apps/solstone-observer.png +0 -0
- solstone_linux-0.4.3/src/solstone_linux/icons/hicolor/32x32/apps/solstone-observer.png +0 -0
- solstone_linux-0.4.3/src/solstone_linux/icons/hicolor/48x48/apps/solstone-observer.png +0 -0
- solstone_linux-0.4.3/src/solstone_linux/icons/hicolor/512x512/apps/solstone-observer.png +0 -0
- solstone_linux-0.4.3/src/solstone_linux/icons/hicolor/64x64/apps/solstone-observer.png +0 -0
- solstone_linux-0.4.3/src/solstone_linux/icons/hicolor/scalable/apps/solstone-observer.svg +18 -0
- {solstone_linux-0.4.1 → solstone_linux-0.4.3}/src/solstone_linux/icons/hicolor/scalable/status/solstone-error.svg +1 -1
- {solstone_linux-0.4.1 → solstone_linux-0.4.3}/src/solstone_linux/icons/hicolor/scalable/status/solstone-paused.svg +1 -1
- {solstone_linux-0.4.1/contrib → solstone_linux-0.4.3/src/solstone_linux}/icons/hicolor/scalable/status/solstone-recording.svg +1 -1
- {solstone_linux-0.4.1/contrib → solstone_linux-0.4.3/src/solstone_linux}/icons/hicolor/scalable/status/solstone-syncing.svg +5 -5
- {solstone_linux-0.4.1 → solstone_linux-0.4.3}/src/solstone_linux/observer.py +100 -80
- {solstone_linux-0.4.1 → solstone_linux-0.4.3}/src/solstone_linux/recovery.py +36 -5
- {solstone_linux-0.4.1 → solstone_linux-0.4.3}/src/solstone_linux/screencast.py +209 -54
- {solstone_linux-0.4.1 → solstone_linux-0.4.3}/src/solstone_linux/sni.py +7 -7
- {solstone_linux-0.4.1 → solstone_linux-0.4.3}/src/solstone_linux/solstone-linux.service.in +3 -2
- {solstone_linux-0.4.1 → solstone_linux-0.4.3}/src/solstone_linux/sync.py +211 -20
- {solstone_linux-0.4.1 → solstone_linux-0.4.3}/src/solstone_linux/sync_health.py +21 -21
- {solstone_linux-0.4.1 → solstone_linux-0.4.3}/src/solstone_linux/tray.py +24 -59
- {solstone_linux-0.4.1 → solstone_linux-0.4.3}/src/solstone_linux/upload.py +60 -10
- solstone_linux-0.4.3/tests/fixtures/introspection/dbusmenu.xml +58 -0
- solstone_linux-0.4.3/tests/fixtures/introspection/observer1.xml +35 -0
- solstone_linux-0.4.3/tests/fixtures/introspection/status_notifier_item.xml +51 -0
- {solstone_linux-0.4.1 → solstone_linux-0.4.3}/tests/test_activity.py +114 -96
- solstone_linux-0.4.3/tests/test_audio_detect.py +75 -0
- solstone_linux-0.4.3/tests/test_audio_recorder.py +366 -0
- solstone_linux-0.4.3/tests/test_capture_stats.py +40 -0
- {solstone_linux-0.4.1 → solstone_linux-0.4.3}/tests/test_chat_bridge.py +283 -9
- {solstone_linux-0.4.1 → solstone_linux-0.4.3}/tests/test_cli.py +55 -3
- {solstone_linux-0.4.1 → solstone_linux-0.4.3}/tests/test_config.py +62 -1
- solstone_linux-0.4.3/tests/test_dbus_introspection.py +98 -0
- {solstone_linux-0.4.1 → solstone_linux-0.4.3}/tests/test_dbus_service.py +34 -18
- {solstone_linux-0.4.1 → solstone_linux-0.4.3}/tests/test_dbusmenu.py +1 -1
- solstone_linux-0.4.3/tests/test_docs_mirror.py +57 -0
- {solstone_linux-0.4.1 → solstone_linux-0.4.3}/tests/test_doctor.py +39 -7
- solstone_linux-0.4.3/tests/test_event_sender.py +121 -0
- solstone_linux-0.4.3/tests/test_observer.py +575 -0
- {solstone_linux-0.4.1 → solstone_linux-0.4.3}/tests/test_observer_emits_stream_silent_event.py +5 -4
- solstone_linux-0.4.3/tests/test_observer_health_beacon.py +168 -0
- solstone_linux-0.4.3/tests/test_screencast.py +837 -0
- {solstone_linux-0.4.1 → solstone_linux-0.4.3}/tests/test_sync.py +544 -35
- {solstone_linux-0.4.1 → solstone_linux-0.4.3}/tests/test_sync_health_surfaces.py +7 -8
- {solstone_linux-0.4.1 → solstone_linux-0.4.3}/tests/test_tray.py +37 -34
- {solstone_linux-0.4.1 → solstone_linux-0.4.3}/tests/test_upload.py +107 -5
- solstone_linux-0.4.3/tests/test_version_match.py +27 -0
- solstone_linux-0.4.1/src/solstone_linux/audio_detect.py +0 -79
- solstone_linux-0.4.1/tests/test_observer.py +0 -149
- solstone_linux-0.4.1/tests/test_screencast.py +0 -411
- {solstone_linux-0.4.1 → solstone_linux-0.4.3}/.gitignore +0 -0
- {solstone_linux-0.4.1 → solstone_linux-0.4.3}/CLAUDE.md +0 -0
- {solstone_linux-0.4.1 → solstone_linux-0.4.3}/LICENSE +0 -0
- {solstone_linux-0.4.1 → solstone_linux-0.4.3}/scripts/extract_changelog.sh +0 -0
- {solstone_linux-0.4.1 → solstone_linux-0.4.3}/src/solstone_linux/audio_mute.py +0 -0
- {solstone_linux-0.4.1 → solstone_linux-0.4.3}/src/solstone_linux/install_guard.py +0 -0
- {solstone_linux-0.4.1 → solstone_linux-0.4.3}/src/solstone_linux/monitor_positions.py +0 -0
- {solstone_linux-0.4.1 → solstone_linux-0.4.3}/src/solstone_linux/session_env.py +0 -0
- {solstone_linux-0.4.1 → solstone_linux-0.4.3}/src/solstone_linux/streams.py +0 -0
- {solstone_linux-0.4.1 → solstone_linux-0.4.3}/tests/__init__.py +0 -0
- {solstone_linux-0.4.1 → solstone_linux-0.4.3}/tests/conftest.py +0 -0
- {solstone_linux-0.4.1 → solstone_linux-0.4.3}/tests/test_extract_changelog.py +0 -0
- {solstone_linux-0.4.1 → solstone_linux-0.4.3}/tests/test_install_guard.py +0 -0
- {solstone_linux-0.4.1 → solstone_linux-0.4.3}/tests/test_monitor_positions.py +0 -0
- {solstone_linux-0.4.1 → solstone_linux-0.4.3}/tests/test_screencast_stop_filters_silent_streams.py +0 -0
- {solstone_linux-0.4.1 → solstone_linux-0.4.3}/tests/test_session_env.py +0 -0
- {solstone_linux-0.4.1 → solstone_linux-0.4.3}/tests/test_streams.py +0 -0
- {solstone_linux-0.4.1 → solstone_linux-0.4.3}/tests/test_sync_health.py +0 -0
|
@@ -16,7 +16,10 @@ src/solstone_linux/
|
|
|
16
16
|
cli.py CLI entry point (run, setup, settings, install-service, status)
|
|
17
17
|
solstone-linux.service.in Systemd unit template (rendered by install-service)
|
|
18
18
|
config.py Config loading/persistence (config under ~/.config/solstone-linux/)
|
|
19
|
+
doctor.py Install prerequisite checks for the doctor command
|
|
20
|
+
install_guard.py Install ownership guard for pipx-managed service installs
|
|
19
21
|
observer.py Main capture loop — state machine (idle/screencast), audio + video
|
|
22
|
+
capture_stats.py Shared capture cache statistics
|
|
20
23
|
screencast.py Portal-based multi-monitor recording (xdg-desktop-portal + GStreamer)
|
|
21
24
|
audio_recorder.py Stereo audio recording (mic + system via soundcard)
|
|
22
25
|
audio_detect.py Audio device detection via ultrasonic tone
|
|
@@ -25,9 +28,16 @@ src/solstone_linux/
|
|
|
25
28
|
monitor_positions.py Monitor position assignment from geometry
|
|
26
29
|
session_env.py Desktop session environment checks and recovery
|
|
27
30
|
streams.py Stream name derivation (hostname-based)
|
|
31
|
+
event_sender.py Background sender for observer event relay
|
|
28
32
|
sync.py Background sync service — uploads completed segments to server
|
|
33
|
+
sync_health.py Sync health facts, derivation, persistence, and surface copy
|
|
29
34
|
upload.py HTTP upload client for solstone ingest server
|
|
30
35
|
recovery.py Crash recovery for orphaned .incomplete segments
|
|
36
|
+
chat_bridge.py Server-initiated chat event bridge to local notifications
|
|
37
|
+
dbus_service.py Observer status/control D-Bus service interface
|
|
38
|
+
dbusmenu.py D-Bus menu protocol implementation for tray menus
|
|
39
|
+
sni.py StatusNotifierItem D-Bus interface for tray icons
|
|
40
|
+
tray.py In-process D-Bus SNI tray icon, menu, and tooltip
|
|
31
41
|
|
|
32
42
|
tests/ pytest test suite
|
|
33
43
|
contrib/ Reference icons for development fallback
|
|
@@ -47,6 +57,8 @@ State machine has two modes: `screencast` (screen active, recording video) and `
|
|
|
47
57
|
|
|
48
58
|
The capture loop never makes network calls. It writes locally; sync handles all uploads.
|
|
49
59
|
|
|
60
|
+
The `observe/status` heartbeat carries top-level diagnostics-only health-beacon fields for registered observers; these contain no captured content, paths, URLs, tokens, titles, or labels. Missing or legacy beacons are liveness-only and not failures; journal-side ingest rejections (`health.ingest_rejection`) are separate and are not produced by the observer.
|
|
61
|
+
|
|
50
62
|
## Commands
|
|
51
63
|
|
|
52
64
|
```bash
|
|
@@ -121,7 +133,7 @@ Python packages (in pyproject.toml):
|
|
|
121
133
|
- `numpy` — Audio buffer manipulation and RMS computation
|
|
122
134
|
- `soundfile` — FLAC encoding
|
|
123
135
|
- `soundcard` — Audio device enumeration and recording
|
|
124
|
-
- `dbus-
|
|
136
|
+
- `dbus-fast` — Async DBus client for portal and activity detection
|
|
125
137
|
- `PyGObject` — GDK monitor geometry (installed from system)
|
|
126
138
|
|
|
127
139
|
## Data Paths
|
|
@@ -4,6 +4,29 @@ All notable changes to solstone-linux are documented here.
|
|
|
4
4
|
The format is based on Keep a Changelog (https://keepachangelog.com/),
|
|
5
5
|
and this project adheres to Semantic Versioning.
|
|
6
6
|
|
|
7
|
+
## [0.4.3] - 2026-07-03
|
|
8
|
+
|
|
9
|
+
### Changed
|
|
10
|
+
- the app now calls itself sol everywhere you see it — the launcher, tray, menus, status, and notifications. your journal is the memory it keeps, and solstone is the platform underneath. the command you run stays `solstone-linux`; nothing about what it does changed, only what it's called.
|
|
11
|
+
- segments your journal rejected or sol couldn't recover are now held for 30 days before they're removed, instead of being dropped silently. `status` and `doctor` show the count, so you can see when any are waiting.
|
|
12
|
+
|
|
13
|
+
### Fixed
|
|
14
|
+
- local cleanup now deletes a synced segment only after your journal confirms, file by file, that it holds everything in it. previously a segment could be cleaned up while your journal was missing part of it. if your journal can't yet confirm file by file, cleanup holds off and keeps the local copy.
|
|
15
|
+
- sol now recovers on its own from situations that used to leave it quietly stalled or stopped: the first screen-share dialog being dismissed, a journal that's slow to respond or offline, and speakers muted at startup (it goes on without audio and picks it back up when a device is available). an accidental second copy now declines to run rather than disturb the one already going. a round of smaller stability improvements rides along.
|
|
16
|
+
- closing the lid on a docked KDE laptop no longer makes sol go idle.
|
|
17
|
+
- chat notifications now come back on their own after a network drop, and dismissing a notification no longer counts as opening it.
|
|
18
|
+
|
|
19
|
+
## [0.4.2] - 2026-06-29
|
|
20
|
+
|
|
21
|
+
### Added
|
|
22
|
+
- this observer now sends your journal a small, diagnostics-only health note alongside its regular check-in, covering its name, version, how long it's been running, and whether syncing is keeping up. it carries none of what it experiences with you: no screen, audio, file paths, or titles. just enough for you to see at a glance that an observer is alive and in good health.
|
|
23
|
+
|
|
24
|
+
### Changed
|
|
25
|
+
- this observer now carries the sol mark across your desktop, in the app launcher and menus. the tray status icons are unchanged.
|
|
26
|
+
|
|
27
|
+
### Fixed
|
|
28
|
+
- installing solstone-linux now works cleanly on current debian and ubuntu. the earlier steps could fail while rebuilding the desktop graphics libraries from scratch; the updated install reuses the ones already on your system, so it goes through.
|
|
29
|
+
|
|
7
30
|
## [0.4.1] - 2026-06-17
|
|
8
31
|
|
|
9
32
|
### Added
|
|
@@ -4,7 +4,9 @@ these instructions are for a coding agent and human working together. solstone-l
|
|
|
4
4
|
|
|
5
5
|
solstone must already be installed and running. if it isn't, start there: https://solstone.app/install
|
|
6
6
|
|
|
7
|
-
> **most users install solstone-linux from PyPI in three commands** on the machine that will host the observer: `pipx install solstone-linux`, `solstone-linux install-service`, then `solstone-linux setup` (which registers against your journal over the local `http://localhost:5015` link — no URL to type). if the observer machine reaches your solstone host directly instead, run `solstone-linux setup --server-url <journal-url>`. the instructions below are for developers building from source or troubleshooting the install.
|
|
7
|
+
> **most users install solstone-linux from PyPI in three commands** on the machine that will host the observer: `pipx install --system-site-packages solstone-linux`, `solstone-linux install-service`, then `solstone-linux setup` (which registers against your journal over the local `http://localhost:5015` link — no URL to type). if the observer machine reaches your solstone host directly instead, run `solstone-linux setup --server-url <journal-url>`. the instructions below are for developers building from source or troubleshooting the install.
|
|
8
|
+
>
|
|
9
|
+
> the `--system-site-packages` flag is **required**: it lets pipx's virtualenv reuse your distro's system PyGObject, pycairo, and GStreamer bindings (the `python3-gi` / `python3-cairo` packages installed below) instead of rebuilding PyGObject from source. a plain `pipx install solstone-linux` rebuilds PyGObject in an isolated venv, which needs the full GObject-Introspection build toolchain (`libgirepository-2.0-dev`) — and that dev package isn't even available on every supported distro (Debian 12 stable doesn't ship it). `--system-site-packages` is the path that works on every distro and skips the compile entirely.
|
|
8
10
|
|
|
9
11
|
## before you begin
|
|
10
12
|
|
|
@@ -34,7 +36,7 @@ By default the observer registers over the local `http://localhost:5015` link, s
|
|
|
34
36
|
|
|
35
37
|
## install sequence
|
|
36
38
|
|
|
37
|
-
this is the developer/from-source path; most installs should use the `pipx install solstone-linux` + `solstone-linux install-service` + `solstone-linux setup` flow described in the callout above.
|
|
39
|
+
this is the developer/from-source path; most installs should use the `pipx install --system-site-packages solstone-linux` + `solstone-linux install-service` + `solstone-linux setup` flow described in the callout above.
|
|
38
40
|
|
|
39
41
|
1. install system dependencies for your distro, including `pipx`. if you need sudo, walk your human through it.
|
|
40
42
|
|
|
@@ -50,7 +52,7 @@ this is the developer/from-source path; most installs should use the `pipx insta
|
|
|
50
52
|
|
|
51
53
|
**arch:**
|
|
52
54
|
```
|
|
53
|
-
sudo pacman -S python-gobject gtk4 gstreamer gst-plugin-pipewire libpulse alsa-lib xdg-desktop-portal pipx
|
|
55
|
+
sudo pacman -S python-gobject gtk4 gstreamer gst-plugin-pipewire gst-plugins-good libpulse alsa-lib xdg-desktop-portal python-pipx uv python-cairo
|
|
54
56
|
```
|
|
55
57
|
|
|
56
58
|
**opensuse:**
|
|
@@ -62,7 +64,7 @@ this is the developer/from-source path; most installs should use the `pipx insta
|
|
|
62
64
|
```
|
|
63
65
|
note: package names diverge from Fedora — `typelib-1_0-Gtk-4_0` (not `gtk4`), `gstreamer-plugin-pipewire` (singular), and `alsa-devel` (not `alsa-lib-devel`).
|
|
64
66
|
|
|
65
|
-
the `cairo` headers + `gcc` + Python dev headers in the Fedora/Debian lines
|
|
67
|
+
with the recommended `pipx install --system-site-packages solstone-linux`, the system `python3-gi` and `python3-cairo` packages above satisfy PyGObject and pycairo, so **neither builds from source**. the `cairo` headers + `gcc` + Python dev headers in the Fedora/Debian lines are kept as a fallback for any other pure-Python dependency that lacks a prebuilt wheel on your platform — and they're what an *isolated*-venv install (a plain `pipx install` without `--system-site-packages`) needs to compile pycairo from source. (an isolated-venv install also needs `libgirepository-2.0-dev`; see Troubleshooting.)
|
|
66
68
|
|
|
67
69
|
`uv` / `pipx`: Fedora packages both (`sudo dnf install uv pipx`); Debian/Ubuntu package `pipx` but not `uv`. the PyPI install flow only needs `pipx` — `uv` is optional and used by the from-source dev workflow in the Makefile.
|
|
68
70
|
|
|
@@ -86,6 +88,15 @@ this is the developer/from-source path; most installs should use the `pipx insta
|
|
|
86
88
|
systemctl --user status solstone-linux
|
|
87
89
|
```
|
|
88
90
|
|
|
91
|
+
## updating from PyPI
|
|
92
|
+
|
|
93
|
+
```
|
|
94
|
+
pipx upgrade solstone-linux
|
|
95
|
+
systemctl --user restart solstone-linux
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
the systemd unit template and icons only refresh when you re-run `solstone-linux install-service`, so after upgrading across a release that changed the unit, re-run `solstone-linux install-service`.
|
|
99
|
+
|
|
89
100
|
## updating after a code change
|
|
90
101
|
|
|
91
102
|
```
|
|
@@ -94,7 +105,7 @@ git pull && make install-service
|
|
|
94
105
|
|
|
95
106
|
## notes
|
|
96
107
|
|
|
97
|
-
-
|
|
108
|
+
- Activity detection uses screen-lock and power-save signals to notice when you step away. Coverage varies by desktop: GNOME provides both signals; KDE (Wayland) provides screen lock only; any X11 session also provides DPMS power save; other Wayland desktops provide screen lock where the compositor exposes it. Where neither signal is available, solstone-linux still experiences your screen and audio, but activity-based segment boundaries won't trigger.
|
|
98
109
|
- the tray icon uses the StatusNotifierItem (SNI) D-Bus protocol. it works on KDE natively and GNOME with the AppIndicator extension. if no SNI host is available, the observer runs normally without a tray icon.
|
|
99
110
|
|
|
100
111
|
## appendix: GNOME tray support
|
|
@@ -140,11 +151,13 @@ Common install-time errors and their fixes:
|
|
|
140
151
|
- arch: `sudo pacman -S pkgconf cairo`
|
|
141
152
|
- opensuse: `sudo zypper install pkgconf-pkg-config cairo-devel`
|
|
142
153
|
|
|
143
|
-
- **`girepository-2.0` missing or `pygobject` build failure**
|
|
144
|
-
-
|
|
145
|
-
-
|
|
146
|
-
|
|
147
|
-
|
|
154
|
+
- **`girepository-2.0` missing or `pygobject` build failure** — only hit when installing into an *isolated* venv (a plain `pipx install` **without** `--system-site-packages`), which rebuilds PyGObject from PyPI from source. the recommended `pipx install --system-site-packages solstone-linux` uses your system `python3-gi` and skips this build entirely.
|
|
155
|
+
- **first, retry with `--system-site-packages`.** `pipx install --system-site-packages solstone-linux` needs no build toolchain. this is the fix on Debian 12 (stable) and other distros that don't package the `-2.0` dev headers at all.
|
|
156
|
+
- if you must build from source: PyPI's PyGObject (3.50+, Sept 2024 onward) needs the girepository-**2.0** dev headers, not the old 1.0 package:
|
|
157
|
+
- fedora: `sudo dnf install gobject-introspection-devel`
|
|
158
|
+
- debian/ubuntu: `sudo apt install libgirepository-2.0-dev` (Debian 13 / Ubuntu 24.04+; older releases that ship PyGObject < 3.50 use `libgirepository1.0-dev`)
|
|
159
|
+
- arch: `sudo pacman -S gobject-introspection`
|
|
160
|
+
- opensuse: `sudo zypper install gobject-introspection-devel`
|
|
148
161
|
|
|
149
162
|
- **`Python.h: No such file or directory`**
|
|
150
163
|
- fedora: `sudo dnf install python3-devel`
|
|
@@ -156,7 +156,7 @@ versions: .installed
|
|
|
156
156
|
$(PYTHON) --version
|
|
157
157
|
@echo ""
|
|
158
158
|
@echo "=== Installed packages ==="
|
|
159
|
-
@$(UV) pip list | grep -E "^(pytest|ruff|requests|numpy|soundfile|soundcard|dbus-
|
|
159
|
+
@$(UV) pip list | grep -E "^(pytest|ruff|requests|numpy|soundfile|soundcard|dbus-fast|PyGObject)" || true
|
|
160
160
|
|
|
161
161
|
release: ## Publish solstone-linux to PyPI (production)
|
|
162
162
|
@bash scripts/release.sh
|
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: solstone-linux
|
|
3
|
-
Version: 0.4.
|
|
3
|
+
Version: 0.4.3
|
|
4
4
|
Summary: Standalone Linux desktop observer for solstone
|
|
5
5
|
License-Expression: AGPL-3.0-only
|
|
6
6
|
License-File: LICENSE
|
|
7
7
|
Requires-Python: >=3.10
|
|
8
|
-
Requires-Dist: dbus-
|
|
8
|
+
Requires-Dist: dbus-fast>=5.0
|
|
9
9
|
Requires-Dist: numpy
|
|
10
10
|
Requires-Dist: pygobject
|
|
11
11
|
Requires-Dist: requests
|
|
@@ -17,7 +17,7 @@ Description-Content-Type: text/markdown
|
|
|
17
17
|
|
|
18
18
|
Standalone Linux desktop observer for [solstone](https://solpbc.org). Experiences your screen and audio along with you on a GNOME Wayland session, stores segments locally, and syncs to your solstone journal.
|
|
19
19
|
|
|
20
|
-
**Note:** Activity detection (
|
|
20
|
+
**Note:** Activity detection uses screen-lock and power-save signals to notice when you step away. Coverage varies by desktop: GNOME provides both signals; KDE (Wayland) provides screen lock only; any X11 session also provides DPMS power save; other Wayland desktops provide screen lock where the compositor exposes it. Where neither signal is available, solstone-linux still experiences your screen and audio, but activity-based segment boundaries won't trigger.
|
|
21
21
|
|
|
22
22
|
## System Dependencies
|
|
23
23
|
|
|
@@ -48,11 +48,13 @@ solstone (the journal) must already be installed and running on the host this ob
|
|
|
48
48
|
On the machine that will host the observer:
|
|
49
49
|
|
|
50
50
|
```bash
|
|
51
|
-
pipx install solstone-linux
|
|
51
|
+
pipx install --system-site-packages solstone-linux
|
|
52
52
|
solstone-linux install-service
|
|
53
53
|
solstone-linux setup
|
|
54
54
|
```
|
|
55
55
|
|
|
56
|
+
The `--system-site-packages` flag is required: it lets pipx reuse your distro's system PyGObject/pycairo/GStreamer bindings (the `python3-gi` / `python3-cairo` packages from System Dependencies above) instead of rebuilding PyGObject from source — which needs the GObject-Introspection build toolchain and isn't packaged on every distro. See `INSTALL.md` if a plain `pipx install` failed with a `girepository-2.0` build error.
|
|
57
|
+
|
|
56
58
|
`setup` registers the observer against your journal over the local `http://localhost:5015` link, so there's no URL to type. If this machine reaches your solstone host directly instead, run `solstone-linux setup --server-url <journal-url>`. (Legacy fallback: mint a key on the journal host with `journal observer create <name>` and paste it during setup.)
|
|
57
59
|
|
|
58
60
|
### Developers building from source
|
|
@@ -85,6 +87,10 @@ solstone-linux run
|
|
|
85
87
|
solstone-linux status
|
|
86
88
|
```
|
|
87
89
|
|
|
90
|
+
Registered observers also include a diagnostics-only status beacon in the
|
|
91
|
+
journal: identity, version, uptime, and sync liveness counts only, with no
|
|
92
|
+
captured or experienced content.
|
|
93
|
+
|
|
88
94
|
## License
|
|
89
95
|
|
|
90
96
|
AGPL-3.0-only — Copyright (c) 2026 sol pbc
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
Standalone Linux desktop observer for [solstone](https://solpbc.org). Experiences your screen and audio along with you on a GNOME Wayland session, stores segments locally, and syncs to your solstone journal.
|
|
4
4
|
|
|
5
|
-
**Note:** Activity detection (
|
|
5
|
+
**Note:** Activity detection uses screen-lock and power-save signals to notice when you step away. Coverage varies by desktop: GNOME provides both signals; KDE (Wayland) provides screen lock only; any X11 session also provides DPMS power save; other Wayland desktops provide screen lock where the compositor exposes it. Where neither signal is available, solstone-linux still experiences your screen and audio, but activity-based segment boundaries won't trigger.
|
|
6
6
|
|
|
7
7
|
## System Dependencies
|
|
8
8
|
|
|
@@ -33,11 +33,13 @@ solstone (the journal) must already be installed and running on the host this ob
|
|
|
33
33
|
On the machine that will host the observer:
|
|
34
34
|
|
|
35
35
|
```bash
|
|
36
|
-
pipx install solstone-linux
|
|
36
|
+
pipx install --system-site-packages solstone-linux
|
|
37
37
|
solstone-linux install-service
|
|
38
38
|
solstone-linux setup
|
|
39
39
|
```
|
|
40
40
|
|
|
41
|
+
The `--system-site-packages` flag is required: it lets pipx reuse your distro's system PyGObject/pycairo/GStreamer bindings (the `python3-gi` / `python3-cairo` packages from System Dependencies above) instead of rebuilding PyGObject from source — which needs the GObject-Introspection build toolchain and isn't packaged on every distro. See `INSTALL.md` if a plain `pipx install` failed with a `girepository-2.0` build error.
|
|
42
|
+
|
|
41
43
|
`setup` registers the observer against your journal over the local `http://localhost:5015` link, so there's no URL to type. If this machine reaches your solstone host directly instead, run `solstone-linux setup --server-url <journal-url>`. (Legacy fallback: mint a key on the journal host with `journal observer create <name>` and paste it during setup.)
|
|
42
44
|
|
|
43
45
|
### Developers building from source
|
|
@@ -70,6 +72,10 @@ solstone-linux run
|
|
|
70
72
|
solstone-linux status
|
|
71
73
|
```
|
|
72
74
|
|
|
75
|
+
Registered observers also include a diagnostics-only status beacon in the
|
|
76
|
+
journal: identity, version, uptime, and sync liveness counts only, with no
|
|
77
|
+
captured or experienced content.
|
|
78
|
+
|
|
73
79
|
## License
|
|
74
80
|
|
|
75
81
|
AGPL-3.0-only — Copyright (c) 2026 sol pbc
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 1024 1024" role="img" aria-label="solstone">
|
|
2
|
+
<title>solstone</title>
|
|
3
|
+
<!-- canonical sol app icon (transparent) — UNIFIED WORDMARK direction (locked 2026-06-25, founder-approved).
|
|
4
|
+
mark: the sol wordmark (sun ring + "sol" Comfortaa Bold) from sol-wordmark.svg, at full extents.
|
|
5
|
+
ground: NONE (transparent), open center — the background shows through the ring.
|
|
6
|
+
color: #E8923A sol orange (ring + "sol" glyph) + #FFCF33 sol gold rays — one recipe on every ground.
|
|
7
|
+
the mark is a logotype (WCAG-exempt), so the brand orange holds on light and dark alike
|
|
8
|
+
(palette simplification 2026-06-29, records/decisions/260629-cmo-sol-mark-palette-simplification.md).
|
|
9
|
+
geometry: identical mark + scale (1.185x, 35.9529) to sol-app-icon-cream.svg; only the ground differs.
|
|
10
|
+
use: Windows + Linux (and any controlled/dark surface — docs, dark UI, marketing on dark).
|
|
11
|
+
decision: records/decisions/260625-cmo-sol-app-icon-unified-wordmark.md
|
|
12
|
+
RENDERING RULE: render from this SVG at every target size; never downsample. -->
|
|
13
|
+
<g transform="translate(512,512) scale(35.9529) translate(-16,-16)">
|
|
14
|
+
<path fill="#FFCF33" d="M16.0 2.5 L18.6 7.3 A9.1 9.1 0 0 0 13.4 7.3 Z M23.9 5.1 L23.2 10.5 A9.1 9.1 0 0 0 19.0 7.4 Z M28.8 11.8 L25.1 15.8 A9.1 9.1 0 0 0 23.5 10.9 Z M28.8 20.2 L23.5 21.1 A9.1 9.1 0 0 0 25.1 16.2 Z M23.9 26.9 L19.0 24.6 A9.1 9.1 0 0 0 23.2 21.5 Z M16.0 29.5 L13.4 24.7 A9.1 9.1 0 0 0 18.6 24.7 Z M8.1 26.9 L8.8 21.5 A9.1 9.1 0 0 0 13.0 24.6 Z M3.2 20.2 L6.9 16.2 A9.1 9.1 0 0 0 8.5 21.1 Z M3.2 11.8 L8.5 10.9 A9.1 9.1 0 0 0 6.9 15.8 Z M8.1 5.1 L13.0 7.4 A9.1 9.1 0 0 0 8.8 10.5 Z"/>
|
|
15
|
+
<circle cx="16" cy="16" r="8.0" fill="none" stroke="#E8923A" stroke-width="1.2"/>
|
|
16
|
+
<path fill="#E8923A" fill-rule="evenodd" d="M12.079 18.795C13.489 18.795 14.229 18.065 14.229 17.155C14.229 16.365 13.729 15.835 12.229 15.535C11.149 15.315 10.939 15.095 10.939 14.725C10.939 14.345 11.399 14.135 11.989 14.135C12.499 14.135 12.859 14.235 13.199 14.555C13.399 14.745 13.729 14.815 13.949 14.665C14.159 14.505 14.169 14.255 13.989 14.035C13.589 13.545 12.889 13.245 12.009 13.245C10.989 13.245 9.959 13.735 9.959 14.755C9.959 15.525 10.529 16.075 11.879 16.335C12.919 16.525 13.249 16.815 13.239 17.215C13.229 17.615 12.809 17.895 12.039 17.895C11.429 17.895 10.889 17.625 10.659 17.375C10.469 17.175 10.189 17.125 9.929 17.335C9.699 17.515 9.659 17.825 9.859 18.035C10.299 18.475 11.149 18.795 12.079 18.795Z M16.999 18.795C18.609 18.795 19.749 17.645 19.749 16.025C19.739 14.395 18.599 13.245 16.999 13.245C15.379 13.245 14.239 14.395 14.239 16.025C14.239 17.645 15.379 18.795 16.999 18.795ZM16.999 17.895C15.959 17.895 15.219 17.125 15.219 16.025C15.219 14.925 15.959 14.145 16.999 14.145C18.039 14.145 18.769 14.925 18.769 16.025C18.769 17.125 18.039 17.895 16.999 17.895Z M21.569 18.755H21.589C21.989 18.755 22.269 18.545 22.269 18.255C22.269 17.965 22.079 17.755 21.819 17.755H21.569C21.279 17.755 21.069 17.405 21.069 16.905V11.445C21.069 11.155 20.859 10.945 20.569 10.945C20.279 10.945 20.069 11.155 20.069 11.445V16.905C20.069 17.985 20.689 18.755 21.569 18.755Z"/>
|
|
17
|
+
</g>
|
|
18
|
+
</svg>
|
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
</mask>
|
|
10
10
|
</defs>
|
|
11
11
|
<g mask="url(#ixko)">
|
|
12
|
-
<path fill="#
|
|
12
|
+
<path fill="#FFCF33" d="M16.0 2.5 L18.6 7.3 A9.1 9.1 0 0 0 13.4 7.3 Z M23.9 5.1 L23.2 10.5 A9.1 9.1 0 0 0 19.0 7.4 Z M28.8 11.8 L25.1 15.8 A9.1 9.1 0 0 0 23.5 10.9 Z M28.8 20.2 L23.5 21.1 A9.1 9.1 0 0 0 25.1 16.2 Z M23.9 26.9 L19.0 24.6 A9.1 9.1 0 0 0 23.2 21.5 Z M16.0 29.5 L13.4 24.7 A9.1 9.1 0 0 0 18.6 24.7 Z M8.1 26.9 L8.8 21.5 A9.1 9.1 0 0 0 13.0 24.6 Z M3.2 20.2 L6.9 16.2 A9.1 9.1 0 0 0 8.5 21.1 Z M3.2 11.8 L8.5 10.9 A9.1 9.1 0 0 0 6.9 15.8 Z M8.1 5.1 L13.0 7.4 A9.1 9.1 0 0 0 8.8 10.5 Z"/>
|
|
13
13
|
<circle cx="16.0" cy="16.0" r="6.5" fill="none" stroke="#E8923A" stroke-width="1.7"/>
|
|
14
14
|
</g>
|
|
15
15
|
<path d="M7 7 L25 25 M25 7 L7 25" fill="none" stroke="#E8923A"
|
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
</mask>
|
|
10
10
|
</defs>
|
|
11
11
|
<g mask="url(#icko)">
|
|
12
|
-
<path fill="#
|
|
12
|
+
<path fill="#FFCF33" d="M16.0 2.5 L18.6 7.3 A9.1 9.1 0 0 0 13.4 7.3 Z M23.9 5.1 L23.2 10.5 A9.1 9.1 0 0 0 19.0 7.4 Z M28.8 11.8 L25.1 15.8 A9.1 9.1 0 0 0 23.5 10.9 Z M28.8 20.2 L23.5 21.1 A9.1 9.1 0 0 0 25.1 16.2 Z M23.9 26.9 L19.0 24.6 A9.1 9.1 0 0 0 23.2 21.5 Z M16.0 29.5 L13.4 24.7 A9.1 9.1 0 0 0 18.6 24.7 Z M8.1 26.9 L8.8 21.5 A9.1 9.1 0 0 0 13.0 24.6 Z M3.2 20.2 L6.9 16.2 A9.1 9.1 0 0 0 8.5 21.1 Z M3.2 11.8 L8.5 10.9 A9.1 9.1 0 0 0 6.9 15.8 Z M8.1 5.1 L13.0 7.4 A9.1 9.1 0 0 0 8.8 10.5 Z"/>
|
|
13
13
|
<circle cx="16.0" cy="16.0" r="6.5" fill="none" stroke="#E8923A" stroke-width="1.7"/>
|
|
14
14
|
</g>
|
|
15
15
|
<path d="M 8.50 21.50 A 4.5 4.5 0 0 1 12.02 18.00 A 5.2 5.2 0 0 1 21.35 13.65 A 4.0 4.0 0 0 1 26.91 18.89 C 28 22, 27.5 25.5, 23 25.5 C 20 28.8, 16 28.8, 13 25.5 C 10 26.5, 8 23.5, 8.50 21.50 Z" fill="none" stroke="#999"
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="2.5 2.5 27 27" role="img" aria-label="solstone icon observing state">
|
|
2
2
|
<title>solstone — observing</title>
|
|
3
3
|
<!-- Sun rays: 10 floating wedges with curved inner arc matching the ring -->
|
|
4
|
-
<path fill="#
|
|
4
|
+
<path fill="#FFCF33" d="M16.0 2.5 L18.6 7.3 A9.1 9.1 0 0 0 13.4 7.3 Z M23.9 5.1 L23.2 10.5 A9.1 9.1 0 0 0 19.0 7.4 Z M28.8 11.8 L25.1 15.8 A9.1 9.1 0 0 0 23.5 10.9 Z M28.8 20.2 L23.5 21.1 A9.1 9.1 0 0 0 25.1 16.2 Z M23.9 26.9 L19.0 24.6 A9.1 9.1 0 0 0 23.2 21.5 Z M16.0 29.5 L13.4 24.7 A9.1 9.1 0 0 0 18.6 24.7 Z M8.1 26.9 L8.8 21.5 A9.1 9.1 0 0 0 13.0 24.6 Z M3.2 20.2 L6.9 16.2 A9.1 9.1 0 0 0 8.5 21.1 Z M3.2 11.8 L8.5 10.9 A9.1 9.1 0 0 0 6.9 15.8 Z M8.1 5.1 L13.0 7.4 A9.1 9.1 0 0 0 8.8 10.5 Z"/>
|
|
5
5
|
<!-- Sun ring: open annulus -->
|
|
6
6
|
<circle cx="16" cy="16" r="6.5" fill="none" stroke="#E8923A" stroke-width="1.7"/>
|
|
7
7
|
</svg>
|
|
@@ -2,11 +2,11 @@
|
|
|
2
2
|
role="img" aria-label="solstone icon syncing state">
|
|
3
3
|
<title>solstone — syncing</title>
|
|
4
4
|
<!-- Top 5 rays only (no clip path needed) -->
|
|
5
|
-
<path fill="#
|
|
6
|
-
<path fill="#
|
|
7
|
-
<path fill="#
|
|
8
|
-
<path fill="#
|
|
9
|
-
<path fill="#
|
|
5
|
+
<path fill="#FFCF33" d="M16.0 2.5 L18.6 7.3 A9.1 9.1 0 0 0 13.4 7.3 Z"/>
|
|
6
|
+
<path fill="#FFCF33" d="M23.9 5.1 L23.2 10.5 A9.1 9.1 0 0 0 19.0 7.4 Z"/>
|
|
7
|
+
<path fill="#FFCF33" d="M28.8 11.8 L25.1 15.8 A9.1 9.1 0 0 0 23.5 10.9 Z"/>
|
|
8
|
+
<path fill="#FFCF33" d="M3.2 11.8 L8.5 10.9 A9.1 9.1 0 0 0 6.9 15.8 Z"/>
|
|
9
|
+
<path fill="#FFCF33" d="M8.1 5.1 L13.0 7.4 A9.1 9.1 0 0 0 8.8 10.5 Z"/>
|
|
10
10
|
<!-- Top arc of the ring -->
|
|
11
11
|
<path fill="none" stroke="#E8923A" stroke-width="1.7"
|
|
12
12
|
d="M 9.5 16.0 A 6.5 6.5 0 0 1 22.5 16.0"/>
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "solstone-linux"
|
|
3
|
-
version = "0.4.
|
|
3
|
+
version = "0.4.3"
|
|
4
4
|
description = "Standalone Linux desktop observer for solstone"
|
|
5
5
|
readme = "README.md"
|
|
6
6
|
license = "AGPL-3.0-only"
|
|
@@ -10,7 +10,7 @@ dependencies = [
|
|
|
10
10
|
"numpy",
|
|
11
11
|
"soundfile",
|
|
12
12
|
"soundcard",
|
|
13
|
-
"dbus-
|
|
13
|
+
"dbus-fast>=5.0",
|
|
14
14
|
"PyGObject",
|
|
15
15
|
]
|
|
16
16
|
|
|
@@ -75,6 +75,16 @@ SDIST_NAME=$(basename "${SDISTS[0]}")
|
|
|
75
75
|
VERSION="${SDIST_NAME#solstone_linux-}"
|
|
76
76
|
VERSION="${VERSION%.tar.gz}"
|
|
77
77
|
|
|
78
|
+
INIT_VERSION=$(grep -E '^__version__ = "[^"]+"' src/solstone_linux/__init__.py | sed 's/^__version__ = "\([^"]*\)".*/\1/' || true)
|
|
79
|
+
if [[ -z "$INIT_VERSION" ]]; then
|
|
80
|
+
echo "error: could not read __version__ from src/solstone_linux/__init__.py" >&2
|
|
81
|
+
exit 1
|
|
82
|
+
fi
|
|
83
|
+
if [[ "$INIT_VERSION" != "$VERSION" ]]; then
|
|
84
|
+
echo "error: version mismatch: sdist version ${VERSION}, src/solstone_linux/__init__.py __version__ ${INIT_VERSION}" >&2
|
|
85
|
+
exit 1
|
|
86
|
+
fi
|
|
87
|
+
|
|
78
88
|
uvx twine check dist/*
|
|
79
89
|
|
|
80
90
|
# Pre-flight: verify the CHANGELOG block exists before publishing.
|
|
@@ -16,9 +16,9 @@ import re
|
|
|
16
16
|
import shutil
|
|
17
17
|
import subprocess
|
|
18
18
|
|
|
19
|
-
from
|
|
20
|
-
from
|
|
21
|
-
from
|
|
19
|
+
from dbus_fast import Variant
|
|
20
|
+
from dbus_fast.aio import MessageBus
|
|
21
|
+
from dbus_fast.errors import (
|
|
22
22
|
DBusError,
|
|
23
23
|
InvalidIntrospectionError,
|
|
24
24
|
InvalidMemberNameError,
|
|
@@ -28,6 +28,10 @@ logger = logging.getLogger(__name__)
|
|
|
28
28
|
|
|
29
29
|
_DBUS_PROBE_TIMEOUT_SEC = 2.0
|
|
30
30
|
_POWER_SAVE_WARNED_BACKENDS: set[str] = set()
|
|
31
|
+
# One session bus lives for the whole process, so id(bus) is stable and
|
|
32
|
+
# never recycled — a plain dict needs no eviction. Keyed by bus identity
|
|
33
|
+
# because dbus-fast's cython MessageBus cannot be weak-referenced.
|
|
34
|
+
_PROXY_CACHE: dict = {}
|
|
31
35
|
|
|
32
36
|
_SERVICE_MISSING_ERRORS = (
|
|
33
37
|
"org.freedesktop.DBus.Error.ServiceUnknown",
|
|
@@ -62,10 +66,6 @@ DISPLAY_CONFIG_BUS = "org.gnome.Mutter.DisplayConfig"
|
|
|
62
66
|
DISPLAY_CONFIG_PATH = "/org/gnome/Mutter/DisplayConfig"
|
|
63
67
|
DISPLAY_CONFIG_IFACE = "org.gnome.Mutter.DisplayConfig"
|
|
64
68
|
|
|
65
|
-
KDE_POWER_BUS = "org.kde.Solid.PowerManagement"
|
|
66
|
-
KDE_POWER_PATH = "/org/kde/Solid/PowerManagement"
|
|
67
|
-
KDE_POWER_IFACE = "org.kde.Solid.PowerManagement"
|
|
68
|
-
|
|
69
69
|
# DBus service constants — monitor geometry (KDE)
|
|
70
70
|
KSCREEN_BUS = "org.kde.KScreen"
|
|
71
71
|
KSCREEN_PATH = "/backend"
|
|
@@ -88,6 +88,23 @@ def _is_gnome_desktop() -> bool:
|
|
|
88
88
|
)
|
|
89
89
|
|
|
90
90
|
|
|
91
|
+
async def _cached_interface(bus: MessageBus, service: str, path: str, iface_name: str):
|
|
92
|
+
key = (id(bus), service, path, iface_name)
|
|
93
|
+
if key in _PROXY_CACHE:
|
|
94
|
+
return _PROXY_CACHE[key]
|
|
95
|
+
intro = await bus.introspect(service, path)
|
|
96
|
+
obj = bus.get_proxy_object(service, path, intro)
|
|
97
|
+
iface = obj.get_interface(iface_name)
|
|
98
|
+
_PROXY_CACHE[key] = iface
|
|
99
|
+
return iface
|
|
100
|
+
|
|
101
|
+
|
|
102
|
+
def _invalidate_interface(
|
|
103
|
+
bus: MessageBus, service: str, path: str, iface_name: str
|
|
104
|
+
) -> None:
|
|
105
|
+
_PROXY_CACHE.pop((id(bus), service, path, iface_name), None)
|
|
106
|
+
|
|
107
|
+
|
|
91
108
|
async def _name_has_owner(bus: MessageBus, bus_name: str) -> bool:
|
|
92
109
|
"""Ask the bus daemon whether a well-known name is currently owned.
|
|
93
110
|
|
|
@@ -199,7 +216,6 @@ async def probe_activity_services(bus: MessageBus) -> dict[str, bool]:
|
|
|
199
216
|
"fdo_screensaver": FDO_SCREENSAVER_BUS,
|
|
200
217
|
"gnome_screensaver": GNOME_SCREENSAVER_BUS,
|
|
201
218
|
"gnome_display_config": DISPLAY_CONFIG_BUS,
|
|
202
|
-
"kde_power": KDE_POWER_BUS,
|
|
203
219
|
"kscreen": KSCREEN_BUS,
|
|
204
220
|
}
|
|
205
221
|
results = {}
|
|
@@ -212,7 +228,7 @@ async def probe_activity_services(bus: MessageBus) -> dict[str, bool]:
|
|
|
212
228
|
|
|
213
229
|
# Log grouped by function
|
|
214
230
|
lock_backends = ["fdo_screensaver", "gnome_screensaver"]
|
|
215
|
-
power_backends = ["gnome_display_config"
|
|
231
|
+
power_backends = ["gnome_display_config"]
|
|
216
232
|
monitor_backends = ["kscreen"]
|
|
217
233
|
|
|
218
234
|
def _status(keys):
|
|
@@ -253,9 +269,12 @@ async def is_screen_locked(bus: MessageBus) -> bool:
|
|
|
253
269
|
if not _is_gnome_desktop():
|
|
254
270
|
# Try freedesktop.org ScreenSaver first (KDE kwin and other non-GNOME desktops)
|
|
255
271
|
try:
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
272
|
+
iface = await _cached_interface(
|
|
273
|
+
bus,
|
|
274
|
+
FDO_SCREENSAVER_BUS,
|
|
275
|
+
FDO_SCREENSAVER_PATH,
|
|
276
|
+
FDO_SCREENSAVER_IFACE,
|
|
277
|
+
)
|
|
259
278
|
return bool(await iface.call_get_active())
|
|
260
279
|
except (
|
|
261
280
|
DBusError,
|
|
@@ -263,6 +282,12 @@ async def is_screen_locked(bus: MessageBus) -> bool:
|
|
|
263
282
|
InvalidIntrospectionError,
|
|
264
283
|
OSError,
|
|
265
284
|
) as exc:
|
|
285
|
+
_invalidate_interface(
|
|
286
|
+
bus,
|
|
287
|
+
FDO_SCREENSAVER_BUS,
|
|
288
|
+
FDO_SCREENSAVER_PATH,
|
|
289
|
+
FDO_SCREENSAVER_IFACE,
|
|
290
|
+
)
|
|
266
291
|
if not _is_service_missing(exc):
|
|
267
292
|
logger.warning(
|
|
268
293
|
"is_screen_locked FDO backend failed: service=%s path=%s: %s: %s",
|
|
@@ -274,9 +299,12 @@ async def is_screen_locked(bus: MessageBus) -> bool:
|
|
|
274
299
|
|
|
275
300
|
# Fall back to GNOME ScreenSaver
|
|
276
301
|
try:
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
302
|
+
iface = await _cached_interface(
|
|
303
|
+
bus,
|
|
304
|
+
GNOME_SCREENSAVER_BUS,
|
|
305
|
+
GNOME_SCREENSAVER_PATH,
|
|
306
|
+
GNOME_SCREENSAVER_IFACE,
|
|
307
|
+
)
|
|
280
308
|
return bool(await iface.call_get_active())
|
|
281
309
|
except (
|
|
282
310
|
DBusError,
|
|
@@ -284,6 +312,12 @@ async def is_screen_locked(bus: MessageBus) -> bool:
|
|
|
284
312
|
InvalidIntrospectionError,
|
|
285
313
|
OSError,
|
|
286
314
|
) as exc:
|
|
315
|
+
_invalidate_interface(
|
|
316
|
+
bus,
|
|
317
|
+
GNOME_SCREENSAVER_BUS,
|
|
318
|
+
GNOME_SCREENSAVER_PATH,
|
|
319
|
+
GNOME_SCREENSAVER_IFACE,
|
|
320
|
+
)
|
|
287
321
|
if not _is_service_missing(exc):
|
|
288
322
|
logger.warning(
|
|
289
323
|
"is_screen_locked GNOME backend failed: service=%s path=%s: %s: %s",
|
|
@@ -296,9 +330,10 @@ async def is_screen_locked(bus: MessageBus) -> bool:
|
|
|
296
330
|
|
|
297
331
|
|
|
298
332
|
async def is_power_save_active(bus: MessageBus) -> bool:
|
|
299
|
-
"""
|
|
333
|
+
"""Return True when the session reports a power-saving/display-off state.
|
|
300
334
|
|
|
301
|
-
|
|
335
|
+
Checks GNOME Mutter PowerSaveMode, then falls back to X11 DPMS when
|
|
336
|
+
XDG_SESSION_TYPE is x11; degrades to False when no backend is available.
|
|
302
337
|
"""
|
|
303
338
|
|
|
304
339
|
def log_backend_failure_once(backend: str, bus_name: str, path: str, exc) -> None:
|
|
@@ -318,9 +353,12 @@ async def is_power_save_active(bus: MessageBus) -> bool:
|
|
|
318
353
|
|
|
319
354
|
# Try GNOME Mutter DisplayConfig first
|
|
320
355
|
try:
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
356
|
+
iface = await _cached_interface(
|
|
357
|
+
bus,
|
|
358
|
+
DISPLAY_CONFIG_BUS,
|
|
359
|
+
DISPLAY_CONFIG_PATH,
|
|
360
|
+
"org.freedesktop.DBus.Properties",
|
|
361
|
+
)
|
|
324
362
|
mode_variant = await iface.call_get(DISPLAY_CONFIG_IFACE, "PowerSaveMode")
|
|
325
363
|
mode = int(mode_variant.value)
|
|
326
364
|
return mode != 0
|
|
@@ -330,6 +368,12 @@ async def is_power_save_active(bus: MessageBus) -> bool:
|
|
|
330
368
|
InvalidIntrospectionError,
|
|
331
369
|
OSError,
|
|
332
370
|
) as exc:
|
|
371
|
+
_invalidate_interface(
|
|
372
|
+
bus,
|
|
373
|
+
DISPLAY_CONFIG_BUS,
|
|
374
|
+
DISPLAY_CONFIG_PATH,
|
|
375
|
+
"org.freedesktop.DBus.Properties",
|
|
376
|
+
)
|
|
333
377
|
if not _is_service_missing(exc):
|
|
334
378
|
log_backend_failure_once(
|
|
335
379
|
"Mutter",
|
|
@@ -338,21 +382,6 @@ async def is_power_save_active(bus: MessageBus) -> bool:
|
|
|
338
382
|
exc,
|
|
339
383
|
)
|
|
340
384
|
|
|
341
|
-
# Fall back to KDE Solid PowerManagement
|
|
342
|
-
try:
|
|
343
|
-
intro = await bus.introspect(KDE_POWER_BUS, KDE_POWER_PATH)
|
|
344
|
-
obj = bus.get_proxy_object(KDE_POWER_BUS, KDE_POWER_PATH, intro)
|
|
345
|
-
iface = obj.get_interface(KDE_POWER_IFACE)
|
|
346
|
-
return bool(await iface.call_is_lid_closed())
|
|
347
|
-
except (
|
|
348
|
-
DBusError,
|
|
349
|
-
InvalidMemberNameError,
|
|
350
|
-
InvalidIntrospectionError,
|
|
351
|
-
OSError,
|
|
352
|
-
) as exc:
|
|
353
|
-
if not _is_service_missing(exc):
|
|
354
|
-
log_backend_failure_once("KDE", KDE_POWER_BUS, KDE_POWER_PATH, exc)
|
|
355
|
-
|
|
356
385
|
# X11-only fallback: DPMS via xset
|
|
357
386
|
if os.environ.get("XDG_SESSION_TYPE", "").lower() == "x11":
|
|
358
387
|
return await is_dpms_active()
|
|
@@ -407,7 +436,7 @@ def get_monitor_geometries() -> list[dict]:
|
|
|
407
436
|
|
|
408
437
|
|
|
409
438
|
def _unwrap_variants(obj):
|
|
410
|
-
"""Recursively unwrap dbus-
|
|
439
|
+
"""Recursively unwrap dbus-fast Variants in nested DBus structures."""
|
|
411
440
|
if isinstance(obj, Variant):
|
|
412
441
|
return _unwrap_variants(obj.value)
|
|
413
442
|
if isinstance(obj, dict):
|