solstone-linux 0.4.1__tar.gz → 0.4.2__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.2}/AGENTS.md +2 -0
- {solstone_linux-0.4.1 → solstone_linux-0.4.2}/CHANGELOG.md +11 -0
- {solstone_linux-0.4.1 → solstone_linux-0.4.2}/INSTALL.md +12 -8
- {solstone_linux-0.4.1 → solstone_linux-0.4.2}/PKG-INFO +8 -2
- {solstone_linux-0.4.1 → solstone_linux-0.4.2}/README.md +7 -1
- solstone_linux-0.4.2/contrib/icons/hicolor/128x128/apps/solstone-observer.png +0 -0
- solstone_linux-0.4.2/contrib/icons/hicolor/16x16/apps/solstone-observer.png +0 -0
- solstone_linux-0.4.2/contrib/icons/hicolor/24x24/apps/solstone-observer.png +0 -0
- solstone_linux-0.4.2/contrib/icons/hicolor/256x256/apps/solstone-observer.png +0 -0
- solstone_linux-0.4.2/contrib/icons/hicolor/32x32/apps/solstone-observer.png +0 -0
- solstone_linux-0.4.2/contrib/icons/hicolor/48x48/apps/solstone-observer.png +0 -0
- solstone_linux-0.4.2/contrib/icons/hicolor/512x512/apps/solstone-observer.png +0 -0
- solstone_linux-0.4.2/contrib/icons/hicolor/64x64/apps/solstone-observer.png +0 -0
- solstone_linux-0.4.2/contrib/icons/hicolor/scalable/apps/solstone-observer.svg +18 -0
- {solstone_linux-0.4.1 → solstone_linux-0.4.2}/pyproject.toml +1 -1
- {solstone_linux-0.4.1 → solstone_linux-0.4.2}/src/solstone_linux/__init__.py +1 -1
- {solstone_linux-0.4.1 → solstone_linux-0.4.2}/src/solstone_linux/cli.py +14 -0
- solstone_linux-0.4.2/src/solstone_linux/icons/hicolor/128x128/apps/solstone-observer.png +0 -0
- solstone_linux-0.4.2/src/solstone_linux/icons/hicolor/16x16/apps/solstone-observer.png +0 -0
- solstone_linux-0.4.2/src/solstone_linux/icons/hicolor/24x24/apps/solstone-observer.png +0 -0
- solstone_linux-0.4.2/src/solstone_linux/icons/hicolor/256x256/apps/solstone-observer.png +0 -0
- solstone_linux-0.4.2/src/solstone_linux/icons/hicolor/32x32/apps/solstone-observer.png +0 -0
- solstone_linux-0.4.2/src/solstone_linux/icons/hicolor/48x48/apps/solstone-observer.png +0 -0
- solstone_linux-0.4.2/src/solstone_linux/icons/hicolor/512x512/apps/solstone-observer.png +0 -0
- solstone_linux-0.4.2/src/solstone_linux/icons/hicolor/64x64/apps/solstone-observer.png +0 -0
- solstone_linux-0.4.2/src/solstone_linux/icons/hicolor/scalable/apps/solstone-observer.svg +18 -0
- {solstone_linux-0.4.1 → solstone_linux-0.4.2}/src/solstone_linux/observer.py +23 -11
- {solstone_linux-0.4.1 → solstone_linux-0.4.2}/src/solstone_linux/sync.py +23 -1
- {solstone_linux-0.4.1 → solstone_linux-0.4.2}/src/solstone_linux/upload.py +6 -1
- solstone_linux-0.4.2/tests/test_observer_health_beacon.py +159 -0
- {solstone_linux-0.4.1 → solstone_linux-0.4.2}/.gitignore +0 -0
- {solstone_linux-0.4.1 → solstone_linux-0.4.2}/CLAUDE.md +0 -0
- {solstone_linux-0.4.1 → solstone_linux-0.4.2}/LICENSE +0 -0
- {solstone_linux-0.4.1 → solstone_linux-0.4.2}/Makefile +0 -0
- {solstone_linux-0.4.1 → solstone_linux-0.4.2}/contrib/icons/hicolor/scalable/status/solstone-error.svg +0 -0
- {solstone_linux-0.4.1 → solstone_linux-0.4.2}/contrib/icons/hicolor/scalable/status/solstone-paused.svg +0 -0
- {solstone_linux-0.4.1 → solstone_linux-0.4.2}/contrib/icons/hicolor/scalable/status/solstone-recording.svg +0 -0
- {solstone_linux-0.4.1 → solstone_linux-0.4.2}/contrib/icons/hicolor/scalable/status/solstone-syncing.svg +0 -0
- {solstone_linux-0.4.1 → solstone_linux-0.4.2}/scripts/extract_changelog.sh +0 -0
- {solstone_linux-0.4.1 → solstone_linux-0.4.2}/scripts/release.sh +0 -0
- {solstone_linux-0.4.1 → solstone_linux-0.4.2}/src/solstone_linux/activity.py +0 -0
- {solstone_linux-0.4.1 → solstone_linux-0.4.2}/src/solstone_linux/audio_detect.py +0 -0
- {solstone_linux-0.4.1 → solstone_linux-0.4.2}/src/solstone_linux/audio_mute.py +0 -0
- {solstone_linux-0.4.1 → solstone_linux-0.4.2}/src/solstone_linux/audio_recorder.py +0 -0
- {solstone_linux-0.4.1 → solstone_linux-0.4.2}/src/solstone_linux/chat_bridge.py +0 -0
- {solstone_linux-0.4.1 → solstone_linux-0.4.2}/src/solstone_linux/config.py +0 -0
- {solstone_linux-0.4.1 → solstone_linux-0.4.2}/src/solstone_linux/dbus_service.py +0 -0
- {solstone_linux-0.4.1 → solstone_linux-0.4.2}/src/solstone_linux/dbusmenu.py +0 -0
- {solstone_linux-0.4.1 → solstone_linux-0.4.2}/src/solstone_linux/doctor.py +0 -0
- {solstone_linux-0.4.1 → solstone_linux-0.4.2}/src/solstone_linux/icons/hicolor/scalable/status/solstone-error.svg +0 -0
- {solstone_linux-0.4.1 → solstone_linux-0.4.2}/src/solstone_linux/icons/hicolor/scalable/status/solstone-paused.svg +0 -0
- {solstone_linux-0.4.1 → solstone_linux-0.4.2}/src/solstone_linux/icons/hicolor/scalable/status/solstone-recording.svg +0 -0
- {solstone_linux-0.4.1 → solstone_linux-0.4.2}/src/solstone_linux/icons/hicolor/scalable/status/solstone-syncing.svg +0 -0
- {solstone_linux-0.4.1 → solstone_linux-0.4.2}/src/solstone_linux/install_guard.py +0 -0
- {solstone_linux-0.4.1 → solstone_linux-0.4.2}/src/solstone_linux/monitor_positions.py +0 -0
- {solstone_linux-0.4.1 → solstone_linux-0.4.2}/src/solstone_linux/recovery.py +0 -0
- {solstone_linux-0.4.1 → solstone_linux-0.4.2}/src/solstone_linux/screencast.py +0 -0
- {solstone_linux-0.4.1 → solstone_linux-0.4.2}/src/solstone_linux/session_env.py +0 -0
- {solstone_linux-0.4.1 → solstone_linux-0.4.2}/src/solstone_linux/sni.py +0 -0
- {solstone_linux-0.4.1 → solstone_linux-0.4.2}/src/solstone_linux/solstone-linux.service.in +0 -0
- {solstone_linux-0.4.1 → solstone_linux-0.4.2}/src/solstone_linux/streams.py +0 -0
- {solstone_linux-0.4.1 → solstone_linux-0.4.2}/src/solstone_linux/sync_health.py +0 -0
- {solstone_linux-0.4.1 → solstone_linux-0.4.2}/src/solstone_linux/tray.py +0 -0
- {solstone_linux-0.4.1 → solstone_linux-0.4.2}/tests/__init__.py +0 -0
- {solstone_linux-0.4.1 → solstone_linux-0.4.2}/tests/conftest.py +0 -0
- {solstone_linux-0.4.1 → solstone_linux-0.4.2}/tests/test_activity.py +0 -0
- {solstone_linux-0.4.1 → solstone_linux-0.4.2}/tests/test_chat_bridge.py +0 -0
- {solstone_linux-0.4.1 → solstone_linux-0.4.2}/tests/test_cli.py +0 -0
- {solstone_linux-0.4.1 → solstone_linux-0.4.2}/tests/test_config.py +0 -0
- {solstone_linux-0.4.1 → solstone_linux-0.4.2}/tests/test_dbus_service.py +0 -0
- {solstone_linux-0.4.1 → solstone_linux-0.4.2}/tests/test_dbusmenu.py +0 -0
- {solstone_linux-0.4.1 → solstone_linux-0.4.2}/tests/test_doctor.py +0 -0
- {solstone_linux-0.4.1 → solstone_linux-0.4.2}/tests/test_extract_changelog.py +0 -0
- {solstone_linux-0.4.1 → solstone_linux-0.4.2}/tests/test_install_guard.py +0 -0
- {solstone_linux-0.4.1 → solstone_linux-0.4.2}/tests/test_monitor_positions.py +0 -0
- {solstone_linux-0.4.1 → solstone_linux-0.4.2}/tests/test_observer.py +0 -0
- {solstone_linux-0.4.1 → solstone_linux-0.4.2}/tests/test_observer_emits_stream_silent_event.py +0 -0
- {solstone_linux-0.4.1 → solstone_linux-0.4.2}/tests/test_screencast.py +0 -0
- {solstone_linux-0.4.1 → solstone_linux-0.4.2}/tests/test_screencast_stop_filters_silent_streams.py +0 -0
- {solstone_linux-0.4.1 → solstone_linux-0.4.2}/tests/test_session_env.py +0 -0
- {solstone_linux-0.4.1 → solstone_linux-0.4.2}/tests/test_streams.py +0 -0
- {solstone_linux-0.4.1 → solstone_linux-0.4.2}/tests/test_sync.py +0 -0
- {solstone_linux-0.4.1 → solstone_linux-0.4.2}/tests/test_sync_health.py +0 -0
- {solstone_linux-0.4.1 → solstone_linux-0.4.2}/tests/test_sync_health_surfaces.py +0 -0
- {solstone_linux-0.4.1 → solstone_linux-0.4.2}/tests/test_tray.py +0 -0
- {solstone_linux-0.4.1 → solstone_linux-0.4.2}/tests/test_upload.py +0 -0
|
@@ -47,6 +47,8 @@ State machine has two modes: `screencast` (screen active, recording video) and `
|
|
|
47
47
|
|
|
48
48
|
The capture loop never makes network calls. It writes locally; sync handles all uploads.
|
|
49
49
|
|
|
50
|
+
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.
|
|
51
|
+
|
|
50
52
|
## Commands
|
|
51
53
|
|
|
52
54
|
```bash
|
|
@@ -4,6 +4,17 @@ 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.2] - 2026-06-29
|
|
8
|
+
|
|
9
|
+
### Added
|
|
10
|
+
- 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.
|
|
11
|
+
|
|
12
|
+
### Changed
|
|
13
|
+
- this observer now carries the sol mark across your desktop, in the app launcher and menus. the tray status icons are unchanged.
|
|
14
|
+
|
|
15
|
+
### Fixed
|
|
16
|
+
- 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.
|
|
17
|
+
|
|
7
18
|
## [0.4.1] - 2026-06-17
|
|
8
19
|
|
|
9
20
|
### 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
|
|
|
@@ -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
|
|
|
@@ -140,11 +142,13 @@ Common install-time errors and their fixes:
|
|
|
140
142
|
- arch: `sudo pacman -S pkgconf cairo`
|
|
141
143
|
- opensuse: `sudo zypper install pkgconf-pkg-config cairo-devel`
|
|
142
144
|
|
|
143
|
-
- **`girepository-2.0` missing or `pygobject` build failure**
|
|
144
|
-
-
|
|
145
|
-
-
|
|
146
|
-
|
|
147
|
-
|
|
145
|
+
- **`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.
|
|
146
|
+
- **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.
|
|
147
|
+
- 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:
|
|
148
|
+
- fedora: `sudo dnf install gobject-introspection-devel`
|
|
149
|
+
- 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`)
|
|
150
|
+
- arch: `sudo pacman -S gobject-introspection`
|
|
151
|
+
- opensuse: `sudo zypper install gobject-introspection-devel`
|
|
148
152
|
|
|
149
153
|
- **`Python.h: No such file or directory`**
|
|
150
154
|
- fedora: `sudo dnf install python3-devel`
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: solstone-linux
|
|
3
|
-
Version: 0.4.
|
|
3
|
+
Version: 0.4.2
|
|
4
4
|
Summary: Standalone Linux desktop observer for solstone
|
|
5
5
|
License-Expression: AGPL-3.0-only
|
|
6
6
|
License-File: LICENSE
|
|
@@ -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
|
|
@@ -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: #B06A1A accessible orange (ring + "sol" glyph) — bumped from native #E8923A so it holds on
|
|
7
|
+
light surfaces too; clears 3:1 on light, and on dark the #F5C740 gold rays carry brightness
|
|
8
|
+
while #B06A1A still reads (~4:1). #F5C740 gold rays.
|
|
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="#F5C740" 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="#B06A1A" stroke-width="1.2"/>
|
|
16
|
+
<path fill="#B06A1A" 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>
|
|
@@ -322,6 +322,7 @@ def cmd_install_service(args: argparse.Namespace) -> int:
|
|
|
322
322
|
"Exec=/bin/sh -c 'systemctl --user import-environment"
|
|
323
323
|
" DISPLAY XAUTHORITY XDG_SESSION_TYPE 2>/dev/null;"
|
|
324
324
|
" systemctl --user start solstone-linux.service'\n"
|
|
325
|
+
"Icon=solstone-observer\n"
|
|
325
326
|
"StartupNotify=false\n"
|
|
326
327
|
"X-GNOME-Autostart-enabled=true\n"
|
|
327
328
|
"Hidden=false\n"
|
|
@@ -365,6 +366,19 @@ def cmd_install_service(args: argparse.Namespace) -> int:
|
|
|
365
366
|
shutil.copy2(svg, status_dir / svg.name)
|
|
366
367
|
print(f"Installed {status_dir / svg.name}")
|
|
367
368
|
|
|
369
|
+
# Application icon — the unified sol app icon (wordmark, transparent
|
|
370
|
+
# ground), installed into the hicolor *apps* context under the name
|
|
371
|
+
# "solstone-observer" (matches the SNI app_id and the .desktop Icon=).
|
|
372
|
+
# Mirrors every freedesktop size dir the repo ships (PNG ladder +
|
|
373
|
+
# scalable SVG). Distinct from the status/ tray icons above.
|
|
374
|
+
for ctx_dir in sorted(icon_source.glob("*/apps")):
|
|
375
|
+
dest_ctx = icon_dest / ctx_dir.parent.name / "apps"
|
|
376
|
+
dest_ctx.mkdir(parents=True, exist_ok=True)
|
|
377
|
+
for asset in sorted(ctx_dir.iterdir()):
|
|
378
|
+
if asset.suffix in (".png", ".svg"):
|
|
379
|
+
shutil.copy2(asset, dest_ctx / asset.name)
|
|
380
|
+
print(f"Installed {dest_ctx / asset.name}")
|
|
381
|
+
|
|
368
382
|
# Self-heal: earlier installs copied a solstone index.theme into this
|
|
369
383
|
# shared hicolor dir. Because the user icon dir out-ranks
|
|
370
384
|
# /usr/share/icons, that file shadowed the system hicolor index (which
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
@@ -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: #B06A1A accessible orange (ring + "sol" glyph) — bumped from native #E8923A so it holds on
|
|
7
|
+
light surfaces too; clears 3:1 on light, and on dark the #F5C740 gold rays carry brightness
|
|
8
|
+
while #B06A1A still reads (~4:1). #F5C740 gold rays.
|
|
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="#F5C740" 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="#B06A1A" stroke-width="1.2"/>
|
|
16
|
+
<path fill="#B06A1A" 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>
|
|
@@ -32,6 +32,7 @@ import numpy as np
|
|
|
32
32
|
from dbus_next.aio import MessageBus
|
|
33
33
|
from dbus_next.constants import BusType
|
|
34
34
|
|
|
35
|
+
from . import __version__
|
|
35
36
|
from .activity import (
|
|
36
37
|
is_power_save_active,
|
|
37
38
|
is_screen_locked,
|
|
@@ -44,7 +45,7 @@ from .config import Config
|
|
|
44
45
|
from .recovery import write_segment_metadata
|
|
45
46
|
from .screencast import Screencaster, SilentStream, StreamInfo, X11Screencaster
|
|
46
47
|
from .sync import SyncService
|
|
47
|
-
from .upload import UploadClient
|
|
48
|
+
from .upload import STREAM_TYPE, UploadClient
|
|
48
49
|
|
|
49
50
|
logger = logging.getLogger(__name__)
|
|
50
51
|
|
|
@@ -475,16 +476,27 @@ class Observer:
|
|
|
475
476
|
"power_save": self.cached_power_save,
|
|
476
477
|
}
|
|
477
478
|
|
|
478
|
-
|
|
479
|
-
"
|
|
480
|
-
"
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
479
|
+
status_fields = {
|
|
480
|
+
"mode": self.current_mode,
|
|
481
|
+
"screencast": screencast_info,
|
|
482
|
+
"audio": audio_info,
|
|
483
|
+
"activity": activity_info,
|
|
484
|
+
"host": HOST,
|
|
485
|
+
"platform": PLATFORM,
|
|
486
|
+
}
|
|
487
|
+
if self._client.is_registered and self.stream:
|
|
488
|
+
status_fields.update(
|
|
489
|
+
{
|
|
490
|
+
"name": self.stream,
|
|
491
|
+
"stream_type": STREAM_TYPE,
|
|
492
|
+
"version": __version__,
|
|
493
|
+
"uptime": elapsed,
|
|
494
|
+
}
|
|
495
|
+
)
|
|
496
|
+
if self._sync is not None:
|
|
497
|
+
status_fields.update(self._sync.health_beacon_fields())
|
|
498
|
+
|
|
499
|
+
self._client.relay_event("observe", "status", **status_fields)
|
|
488
500
|
|
|
489
501
|
def _refresh_tray(self):
|
|
490
502
|
"""Refresh the SNI tray UI. Safe when tray is unavailable; disables on failure."""
|
|
@@ -25,7 +25,7 @@ import shutil
|
|
|
25
25
|
import time
|
|
26
26
|
from datetime import datetime, timedelta
|
|
27
27
|
from pathlib import Path
|
|
28
|
-
from typing import Callable
|
|
28
|
+
from typing import Any, Callable
|
|
29
29
|
|
|
30
30
|
from .config import Config
|
|
31
31
|
from .sync_health import (
|
|
@@ -98,6 +98,28 @@ class SyncService:
|
|
|
98
98
|
def progress(self) -> str:
|
|
99
99
|
return self._facts.progress
|
|
100
100
|
|
|
101
|
+
def health_beacon_fields(self) -> dict[str, Any]:
|
|
102
|
+
"""Diagnostics-only sync fields: counts, epoch seconds, and error class."""
|
|
103
|
+
last_successful_sync = self._facts.last_successful_sync
|
|
104
|
+
last_error_class = self._facts.last_error_class
|
|
105
|
+
last_error_code = self._facts.last_error_code
|
|
106
|
+
|
|
107
|
+
if last_error_class is None:
|
|
108
|
+
last_error_reason = None
|
|
109
|
+
elif last_error_code is not None:
|
|
110
|
+
last_error_reason = f"{last_error_class.value}:{last_error_code}"
|
|
111
|
+
else:
|
|
112
|
+
last_error_reason = last_error_class.value
|
|
113
|
+
|
|
114
|
+
return {
|
|
115
|
+
"last_successful_sync": int(last_successful_sync)
|
|
116
|
+
if last_successful_sync is not None
|
|
117
|
+
else None,
|
|
118
|
+
"pending_queue_depth": self._facts.pending_confirmed,
|
|
119
|
+
"recent_error_count": min(99, max(0, self._consecutive_failures)),
|
|
120
|
+
"last_error_reason": last_error_reason,
|
|
121
|
+
}
|
|
122
|
+
|
|
101
123
|
def _synced_days_path(self) -> Path:
|
|
102
124
|
return self._config.state_dir / "synced_days.json"
|
|
103
125
|
|
|
@@ -30,6 +30,7 @@ logger = logging.getLogger(__name__)
|
|
|
30
30
|
|
|
31
31
|
UPLOAD_TIMEOUT = 300
|
|
32
32
|
EVENT_TIMEOUT = 30
|
|
33
|
+
STREAM_TYPE = "desktop"
|
|
33
34
|
|
|
34
35
|
|
|
35
36
|
def _auth_headers(key: str) -> dict[str, str]:
|
|
@@ -65,6 +66,10 @@ class UploadClient:
|
|
|
65
66
|
def is_revoked(self) -> bool:
|
|
66
67
|
return self._revoked
|
|
67
68
|
|
|
69
|
+
@property
|
|
70
|
+
def is_registered(self) -> bool:
|
|
71
|
+
return bool(self._key)
|
|
72
|
+
|
|
68
73
|
def _persist_registration(self, config: Config, key: str, stream: str) -> None:
|
|
69
74
|
"""Persist the server-issued handle and locked stream back to config."""
|
|
70
75
|
from .config import save_config
|
|
@@ -86,7 +91,7 @@ class UploadClient:
|
|
|
86
91
|
descriptor: dict[str, Any] = {
|
|
87
92
|
"platform": platform.system().lower(),
|
|
88
93
|
"hostname": socket.gethostname(),
|
|
89
|
-
"stream_type":
|
|
94
|
+
"stream_type": STREAM_TYPE,
|
|
90
95
|
"version": __version__,
|
|
91
96
|
}
|
|
92
97
|
if self._stream:
|
|
@@ -0,0 +1,159 @@
|
|
|
1
|
+
# SPDX-License-Identifier: AGPL-3.0-only
|
|
2
|
+
# Copyright (c) 2026 sol pbc
|
|
3
|
+
|
|
4
|
+
import time
|
|
5
|
+
from unittest.mock import MagicMock
|
|
6
|
+
|
|
7
|
+
from solstone_linux import __version__
|
|
8
|
+
from solstone_linux.config import Config
|
|
9
|
+
from solstone_linux.observer import MODE_SCREENCAST, Observer
|
|
10
|
+
from solstone_linux.screencast import StreamInfo
|
|
11
|
+
from solstone_linux.sync import SyncService
|
|
12
|
+
from solstone_linux.sync_health import ErrorType
|
|
13
|
+
from solstone_linux.upload import STREAM_TYPE
|
|
14
|
+
|
|
15
|
+
FIXED_EPOCH = 1_798_888_123.5
|
|
16
|
+
HEALTH_KEYS = {
|
|
17
|
+
"name",
|
|
18
|
+
"stream_type",
|
|
19
|
+
"version",
|
|
20
|
+
"uptime",
|
|
21
|
+
"last_successful_sync",
|
|
22
|
+
"pending_queue_depth",
|
|
23
|
+
"recent_error_count",
|
|
24
|
+
"last_error_reason",
|
|
25
|
+
}
|
|
26
|
+
BASE_STATUS_KEYS = {
|
|
27
|
+
"mode",
|
|
28
|
+
"screencast",
|
|
29
|
+
"audio",
|
|
30
|
+
"activity",
|
|
31
|
+
"host",
|
|
32
|
+
"platform",
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
def _observer(tmp_path, registered: bool = True) -> Observer:
|
|
37
|
+
config = Config(base_dir=tmp_path)
|
|
38
|
+
observer = Observer(config)
|
|
39
|
+
observer._client = MagicMock()
|
|
40
|
+
observer._client.is_registered = registered
|
|
41
|
+
observer.stream = "desk-host"
|
|
42
|
+
observer.start_at_mono = time.monotonic() - 12
|
|
43
|
+
observer._sync = SyncService(config, MagicMock(), now=lambda: FIXED_EPOCH)
|
|
44
|
+
return observer
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
def _status_kwargs(observer: Observer) -> dict:
|
|
48
|
+
observer.emit_status()
|
|
49
|
+
args, kwargs = observer._client.relay_event.call_args
|
|
50
|
+
assert args == ("observe", "status")
|
|
51
|
+
return kwargs
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
def test_registered_first_emit_includes_all_health_fields_top_level(tmp_path):
|
|
55
|
+
observer = _observer(tmp_path)
|
|
56
|
+
|
|
57
|
+
kwargs = _status_kwargs(observer)
|
|
58
|
+
|
|
59
|
+
assert HEALTH_KEYS.issubset(kwargs)
|
|
60
|
+
assert "health" not in kwargs
|
|
61
|
+
assert kwargs["name"] == "desk-host"
|
|
62
|
+
assert kwargs["stream_type"] == STREAM_TYPE
|
|
63
|
+
assert kwargs["version"] == __version__
|
|
64
|
+
assert isinstance(kwargs["uptime"], int)
|
|
65
|
+
assert kwargs["uptime"] >= 0
|
|
66
|
+
assert kwargs["last_successful_sync"] is None
|
|
67
|
+
assert kwargs["pending_queue_depth"] is None
|
|
68
|
+
assert kwargs["recent_error_count"] == 0
|
|
69
|
+
assert kwargs["last_error_reason"] is None
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
def test_periodic_reemit_carries_same_health_fields(tmp_path):
|
|
73
|
+
observer = _observer(tmp_path)
|
|
74
|
+
|
|
75
|
+
first = _status_kwargs(observer)
|
|
76
|
+
second = _status_kwargs(observer)
|
|
77
|
+
|
|
78
|
+
assert HEALTH_KEYS.issubset(first)
|
|
79
|
+
assert HEALTH_KEYS.issubset(second)
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
def test_health_fields_exclude_captured_content_and_extra_health_keys(tmp_path):
|
|
83
|
+
observer = _observer(tmp_path)
|
|
84
|
+
observer.current_mode = MODE_SCREENCAST
|
|
85
|
+
observer.current_streams = [
|
|
86
|
+
StreamInfo(
|
|
87
|
+
node_id=42,
|
|
88
|
+
position="left",
|
|
89
|
+
connector="HDMI-SECRET",
|
|
90
|
+
x=0,
|
|
91
|
+
y=0,
|
|
92
|
+
width=1920,
|
|
93
|
+
height=1080,
|
|
94
|
+
file_path="/captured/private/window-title-meeting.webm",
|
|
95
|
+
)
|
|
96
|
+
]
|
|
97
|
+
observer.threshold_hits = 4
|
|
98
|
+
observer.cached_is_active = True
|
|
99
|
+
observer.cached_screen_locked = False
|
|
100
|
+
observer.cached_is_muted = True
|
|
101
|
+
observer.cached_power_save = False
|
|
102
|
+
|
|
103
|
+
kwargs = _status_kwargs(observer)
|
|
104
|
+
|
|
105
|
+
assert set(kwargs) - BASE_STATUS_KEYS == HEALTH_KEYS
|
|
106
|
+
forbidden = (
|
|
107
|
+
"/captured/private",
|
|
108
|
+
"window-title",
|
|
109
|
+
"meeting",
|
|
110
|
+
"HDMI-SECRET",
|
|
111
|
+
"threshold_hits",
|
|
112
|
+
"sink_muted",
|
|
113
|
+
)
|
|
114
|
+
health_values = [kwargs[key] for key in HEALTH_KEYS]
|
|
115
|
+
for value in health_values:
|
|
116
|
+
assert not any(token in str(value) for token in forbidden)
|
|
117
|
+
|
|
118
|
+
|
|
119
|
+
def test_successful_no_work_sync_reflected_in_health_beacon(tmp_path):
|
|
120
|
+
observer = _observer(tmp_path)
|
|
121
|
+
observer._sync._commit_pass_result(True)
|
|
122
|
+
|
|
123
|
+
kwargs = _status_kwargs(observer)
|
|
124
|
+
|
|
125
|
+
assert kwargs["last_successful_sync"] == int(FIXED_EPOCH)
|
|
126
|
+
assert kwargs["pending_queue_depth"] == 0
|
|
127
|
+
assert kwargs["recent_error_count"] == 0
|
|
128
|
+
assert kwargs["last_error_reason"] is None
|
|
129
|
+
|
|
130
|
+
|
|
131
|
+
def test_failed_delivery_return_false_is_nonfatal_for_status_emit(tmp_path):
|
|
132
|
+
observer = _observer(tmp_path)
|
|
133
|
+
observer._client.relay_event.return_value = False
|
|
134
|
+
|
|
135
|
+
observer.emit_status()
|
|
136
|
+
observer.emit_status()
|
|
137
|
+
|
|
138
|
+
assert observer._client.relay_event.call_count == 2
|
|
139
|
+
|
|
140
|
+
|
|
141
|
+
def test_unregistered_observer_emits_base_status_without_health_fields(tmp_path):
|
|
142
|
+
observer = _observer(tmp_path, registered=False)
|
|
143
|
+
|
|
144
|
+
kwargs = _status_kwargs(observer)
|
|
145
|
+
|
|
146
|
+
assert BASE_STATUS_KEYS.issubset(kwargs)
|
|
147
|
+
assert HEALTH_KEYS.isdisjoint(kwargs)
|
|
148
|
+
|
|
149
|
+
|
|
150
|
+
def test_failure_count_clamps_and_last_error_reason_is_safe(tmp_path):
|
|
151
|
+
config = Config(base_dir=tmp_path)
|
|
152
|
+
sync = SyncService(config, MagicMock(), now=lambda: FIXED_EPOCH)
|
|
153
|
+
|
|
154
|
+
for _ in range(150):
|
|
155
|
+
sync._record_failure(ErrorType.TRANSIENT, 503)
|
|
156
|
+
|
|
157
|
+
fields = sync.health_beacon_fields()
|
|
158
|
+
assert fields["recent_error_count"] == 99
|
|
159
|
+
assert fields["last_error_reason"] == "transient:503"
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{solstone_linux-0.4.1 → solstone_linux-0.4.2}/tests/test_observer_emits_stream_silent_event.py
RENAMED
|
File without changes
|
|
File without changes
|
{solstone_linux-0.4.1 → solstone_linux-0.4.2}/tests/test_screencast_stop_filters_silent_streams.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|