fix: rename D-Bus name to dev.edu4rdshl.Strata; drop recursive theme reload

Two changes for 0.7.0:

BREAKING (D-Bus): the service is renamed from org.gnome.Strata to
dev.edu4rdshl.Strata. The org.gnome.* namespace is reserved for official
GNOME software; Strata is third-party, so it now uses the reverse-DNS of its
own domain (matching the strata@edu4rdshl.dev UUID). Bus name, object path
(/dev/edu4rdshl/Strata), and interface (dev.edu4rdshl.Strata.Manager) all
change. Daemon and extension are updated together; only external busctl
scripts / non-GNOME front-ends that hard-coded the old name need updating.
The GSettings schema (org.gnome.shell.extensions.strata) is unchanged - that
namespace is correct for GNOME Shell extension settings.

Fix recursion: the theme-context 'changed' handler reloaded light.css, but
load_stylesheet itself emits 'changed', so it fed back into itself and hit
"too much recursion" - flooding the journal and spinning the CPU on screen
unlock (which restyles widgets and fires 'changed'). The subscription is
removed; light.css is loaded once. Dark/light switching is unaffected (it is
the panel's class toggle, not this load). A GNOME Shell theme switch no
longer auto-re-applies the sheet, which is recoverable by re-enabling.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
Eduard Tolosa 2026-05-26 19:20:12 -05:00
parent 3f32c19fd0
commit b41c9662cb
8 changed files with 40 additions and 44 deletions

View file

@ -72,7 +72,7 @@ ssh fedoradev 'glib-compile-schemas ~/.local/share/gnome-shell/extensions/strata
## D-Bus interface summary ## D-Bus interface summary
Service `org.gnome.Strata`, object `/org/gnome/Strata`, interface `org.gnome.Strata.Manager`. Service `dev.edu4rdshl.Strata`, object `/dev/edu4rdshl/Strata`, interface `dev.edu4rdshl.Strata.Manager`.
Methods: `GetHistory(offset u32, limit u32) -> json s`, Methods: `GetHistory(offset u32, limit u32) -> json s`,
`SearchHistory(query s, limit u32) -> json s`, `SearchHistory(query s, limit u32) -> json s`,

View file

@ -24,7 +24,7 @@ together and the trade-offs at each layer.
| | ItemAdded / ItemDeleted / HistoryCleared | | | ItemAdded / ItemDeleted / HistoryCleared |
+--------+-------------------------------------------------------------------------+ +--------+-------------------------------------------------------------------------+
| |
| session D-Bus (org.gnome.Strata) | session D-Bus (dev.edu4rdshl.Strata)
v v
+----------------------- strata-daemon (Rust process) --------------------------+ +----------------------- strata-daemon (Rust process) --------------------------+
| | | |
@ -68,7 +68,7 @@ hot ingest path (it fires `SubmitItem` and returns immediately).
### Startup ### Startup
The extension listens for `notify::g-name-owner` on the D-Bus proxy. When The extension listens for `notify::g-name-owner` on the D-Bus proxy. When
the daemon's `org.gnome.Strata` name becomes owned, the panel triggers the daemon's `dev.edu4rdshl.Strata` name becomes owned, the panel triggers
its initial fetch. No polling, no fixed delays. its initial fetch. No polling, no fixed delays.
## Ingest path ## Ingest path
@ -313,7 +313,7 @@ and it is small and isolated.
| App to history | Password leak | `x-kde-passwordManagerHint` opt-out | | App to history | Password leak | `x-kde-passwordManagerHint` opt-out |
| User to search | SQL injection | rusqlite `params![]`, FTS5 input bound as value | | User to search | SQL injection | rusqlite `params![]`, FTS5 input bound as value |
| Daemon to FS | Path traversal via id | Ids are server-generated UUID v4 | | Daemon to FS | Path traversal via id | Ids are server-generated UUID v4 |
| Daemon to extension | Signal spoofing | D-Bus enforces single owner of `org.gnome.Strata` | | Daemon to extension | Signal spoofing | D-Bus enforces single owner of `dev.edu4rdshl.Strata` |
| Stored item to paste | Command execution | No spawn, no launch_uri, no markup parsing | | Stored item to paste | Command execution | No spawn, no launch_uri, no markup parsing |
## Non-GNOME front-end ## Non-GNOME front-end

View file

@ -12,7 +12,7 @@ Strata is **two components**, and you need **both** for it to work:
| Component | Language | Role | | Component | Language | Role |
|---|---|---| |---|---|---|
| [`strata-daemon/`](strata-daemon/) | Rust + tokio + zbus | Storage (SQLite + FTS5), dedup, thumbnails, D-Bus service `org.gnome.Strata` | | [`strata-daemon/`](strata-daemon/) | Rust + tokio + zbus | Storage (SQLite + FTS5), dedup, thumbnails, D-Bus service `dev.edu4rdshl.Strata` |
| [`strata@edu4rdshl.dev/`](strata@edu4rdshl.dev/) | GJS (GNOME Shell extension) | Top-bar panel, search UI, paste-back, clipboard ingest | | [`strata@edu4rdshl.dev/`](strata@edu4rdshl.dev/) | GJS (GNOME Shell extension) | Top-bar panel, search UI, paste-back, clipboard ingest |
The extension auto-connects to the daemon on enable. If the daemon is managed The extension auto-connects to the daemon on enable. If the daemon is managed

View file

@ -24,9 +24,9 @@ manually if you're using a non-GNOME front-end.
## D-Bus interface ## D-Bus interface
- **Bus name:** `org.gnome.Strata` - **Bus name:** `dev.edu4rdshl.Strata`
- **Object path:** `/org/gnome/Strata` - **Object path:** `/dev/edu4rdshl/Strata`
- **Interface:** `org.gnome.Strata.Manager` - **Interface:** `dev.edu4rdshl.Strata.Manager`
| Member | Signature | Notes | | Member | Signature | Notes |
|---|---|---| |---|---|---|
@ -51,15 +51,15 @@ manually if you're using a non-GNOME front-end.
./target/release/strata-daemon ./target/release/strata-daemon
# In another terminal: # In another terminal:
busctl --user call org.gnome.Strata /org/gnome/Strata \ busctl --user call dev.edu4rdshl.Strata /dev/edu4rdshl/Strata \
org.gnome.Strata.Manager GetHistory uu 0 10 dev.edu4rdshl.Strata.Manager GetHistory uu 0 10
# Submit text: # Submit text:
busctl --user call org.gnome.Strata /org/gnome/Strata \ busctl --user call dev.edu4rdshl.Strata /dev/edu4rdshl/Strata \
org.gnome.Strata.Manager SubmitItem say "text/plain;charset=utf-8" 5 104 101 108 108 111 dev.edu4rdshl.Strata.Manager SubmitItem say "text/plain;charset=utf-8" 5 104 101 108 108 111
# Listen for new items: # Listen for new items:
busctl --user monitor org.gnome.Strata busctl --user monitor dev.edu4rdshl.Strata
``` ```
## Supported clipboard payloads ## Supported clipboard payloads

View file

@ -57,7 +57,7 @@ pub struct StrataManager {
pub shutdown_tx: tokio::sync::mpsc::UnboundedSender<()>, pub shutdown_tx: tokio::sync::mpsc::UnboundedSender<()>,
} }
#[interface(name = "org.gnome.Strata.Manager")] #[interface(name = "dev.edu4rdshl.Strata.Manager")]
impl StrataManager { impl StrataManager {
/// Called by the GJS extension when it detects a clipboard change via Meta.Selection. /// Called by the GJS extension when it detects a clipboard change via Meta.Selection.
/// `content` is the raw bytes of the clipboard payload (D-Bus `ay`). /// `content` is the raw bytes of the clipboard payload (D-Bus `ay`).

View file

@ -49,7 +49,7 @@ async fn main() -> Result<()> {
}; };
let dbus_conn = zbus::connection::Builder::session()? let dbus_conn = zbus::connection::Builder::session()?
.serve_at("/org/gnome/Strata", manager)? .serve_at("/dev/edu4rdshl/Strata", manager)?
.build() .build()
.await .await
.context("Building D-Bus connection")?; .context("Building D-Bus connection")?;
@ -61,14 +61,14 @@ async fn main() -> Result<()> {
// would be refused. // would be refused.
dbus_conn dbus_conn
.request_name_with_flags( .request_name_with_flags(
"org.gnome.Strata", "dev.edu4rdshl.Strata",
zbus::fdo::RequestNameFlags::ReplaceExisting zbus::fdo::RequestNameFlags::ReplaceExisting
| zbus::fdo::RequestNameFlags::AllowReplacement, | zbus::fdo::RequestNameFlags::AllowReplacement,
) )
.await .await
.context("Acquiring D-Bus name org.gnome.Strata")?; .context("Acquiring D-Bus name dev.edu4rdshl.Strata")?;
tracing::info!("D-Bus service registered as org.gnome.Strata"); tracing::info!("D-Bus service registered as dev.edu4rdshl.Strata");
// ----------------------------------------------------------------------- // -----------------------------------------------------------------------
// Clipboard monitor (Wayland protocols - optional, soft-fail on GNOME). // Clipboard monitor (Wayland protocols - optional, soft-fail on GNOME).
@ -312,7 +312,7 @@ async fn process_bytes(
if is_new { if is_new {
let iface = conn let iface = conn
.object_server() .object_server()
.interface::<_, StrataManager>("/org/gnome/Strata") .interface::<_, StrataManager>("/dev/edu4rdshl/Strata")
.await .await
.context("Getting StrataManager interface ref")?; .context("Getting StrataManager interface ref")?;

View file

@ -10,7 +10,7 @@ import Gio from 'gi://Gio';
const STRATA_IFACE_XML = ` const STRATA_IFACE_XML = `
<node> <node>
<interface name="org.gnome.Strata.Manager"> <interface name="dev.edu4rdshl.Strata.Manager">
<method name="GetHistory"> <method name="GetHistory">
<arg type="u" direction="in" name="offset"/> <arg type="u" direction="in" name="offset"/>
@ -75,5 +75,5 @@ const STRATA_IFACE_XML = `
export const StrataProxy = Gio.DBusProxy.makeProxyWrapper(STRATA_IFACE_XML); export const StrataProxy = Gio.DBusProxy.makeProxyWrapper(STRATA_IFACE_XML);
export const BUS_NAME = 'org.gnome.Strata'; export const BUS_NAME = 'dev.edu4rdshl.Strata';
export const OBJECT_PATH = '/org/gnome/Strata'; export const OBJECT_PATH = '/dev/edu4rdshl/Strata';

View file

@ -440,7 +440,7 @@ export default class StrataExtension extends Extension {
_connectSignals() { _connectSignals() {
this._itemAddedId = Gio.DBus.session.signal_subscribe( this._itemAddedId = Gio.DBus.session.signal_subscribe(
BUS_NAME, BUS_NAME,
'org.gnome.Strata.Manager', 'dev.edu4rdshl.Strata.Manager',
'ItemAdded', 'ItemAdded',
OBJECT_PATH, OBJECT_PATH,
null, null,
@ -450,7 +450,7 @@ export default class StrataExtension extends Extension {
this._itemDeletedId = Gio.DBus.session.signal_subscribe( this._itemDeletedId = Gio.DBus.session.signal_subscribe(
BUS_NAME, BUS_NAME,
'org.gnome.Strata.Manager', 'dev.edu4rdshl.Strata.Manager',
'ItemDeleted', 'ItemDeleted',
OBJECT_PATH, OBJECT_PATH,
null, null,
@ -473,7 +473,7 @@ export default class StrataExtension extends Extension {
this._historyClearedId = Gio.DBus.session.signal_subscribe( this._historyClearedId = Gio.DBus.session.signal_subscribe(
BUS_NAME, BUS_NAME,
'org.gnome.Strata.Manager', 'dev.edu4rdshl.Strata.Manager',
'HistoryCleared', 'HistoryCleared',
OBJECT_PATH, OBJECT_PATH,
null, null,
@ -600,22 +600,23 @@ export default class StrataExtension extends Extension {
} }
/** Load light.css into the global St theme context. Scoped under /** Load light.css into the global St theme context ONCE. It is scoped under
* `.strata-theme-light`, so it does nothing until the panel adds that * `.strata-theme-light`, so it stays inert until the panel adds that class
* class. Reloaded on theme-context changes (a Shell-theme switch drops * (dark/light switching is the panel's class toggle, independent of this).
* dynamically loaded stylesheets). */ *
* We deliberately do NOT subscribe to the theme context's 'changed'
* signal: load_stylesheet itself emits 'changed', so reloading on it feeds
* back into itself and hits "too much recursion" (it fired on screen
* unlock, which restyles widgets). The only thing a one-time load gives up
* is re-applying after a GNOME Shell *theme* switch (which replaces the
* St.Theme and drops this sheet) - a rare action, recoverable by toggling
* the extension. That trade is worth never touching the signal. */
_loadThemeStylesheet() { _loadThemeStylesheet() {
try { try {
const themeContext = St.ThemeContext.get_for_stage(global.stage); const themeContext = St.ThemeContext.get_for_stage(global.stage);
this._lightCssFile = this.dir.get_child('light.css'); this._lightCssFile = this.dir.get_child('light.css');
themeContext.get_theme().load_stylesheet(this._lightCssFile); this._stTheme = themeContext.get_theme();
this._stThemeChangedId = themeContext.connect('changed', () => { this._stTheme.load_stylesheet(this._lightCssFile);
try {
themeContext.get_theme().load_stylesheet(this._lightCssFile);
} catch (e) {
console.error('[Strata] light.css reload failed:', e);
}
});
} catch (e) { } catch (e) {
console.error('[Strata] Failed to load light.css:', e); console.error('[Strata] Failed to load light.css:', e);
} }
@ -623,16 +624,11 @@ export default class StrataExtension extends Extension {
_unloadThemeStylesheet() { _unloadThemeStylesheet() {
try { try {
const themeContext = St.ThemeContext.get_for_stage(global.stage); this._stTheme?.unload_stylesheet(this._lightCssFile);
if (this._stThemeChangedId) {
themeContext.disconnect(this._stThemeChangedId);
this._stThemeChangedId = null;
}
if (this._lightCssFile)
themeContext.get_theme().unload_stylesheet(this._lightCssFile);
} catch (e) { } catch (e) {
console.error('[Strata] Failed to unload light.css:', e); console.error('[Strata] Failed to unload light.css:', e);
} }
this._stTheme = null;
this._lightCssFile = null; this._lightCssFile = null;
} }
} }