extension: cleanup before any future EGO submission

Remove narration breadcrumb comments, the unused _strataPreview field, the
self-referencing this.actor alias, and three non-error console.log calls.
Rename #busy to _busy for consistency with the rest of the class. Tighten a
couple of over-long docstrings.
This commit is contained in:
Eduard Tolosa 2026-06-25 15:30:29 -05:00
parent 3fff367364
commit 43a68e7970
5 changed files with 16 additions and 83 deletions

View file

@ -58,7 +58,7 @@ export default class StrataExtension extends Extension {
_pendingSignalId = null; _pendingSignalId = null;
/** @type {boolean} Re-entrancy guard for signal processing */ /** @type {boolean} Re-entrancy guard for signal processing */
#busy = false; _busy = false;
/** @type {number | null} Keyboard shortcut binding ID */ /** @type {number | null} Keyboard shortcut binding ID */
_shortcutId = null; _shortcutId = null;
@ -90,22 +90,11 @@ export default class StrataExtension extends Extension {
// inert until the panel toggles the `.strata-theme-light` class (panel.js). // inert until the panel toggles the `.strata-theme-light` class (panel.js).
this._loadThemeStylesheet(); this._loadThemeStylesheet();
// 1. Top-bar indicator icon.
this._addIndicator(); this._addIndicator();
// 2. Spawn the Rust daemon.
this._spawnDaemon(); this._spawnDaemon();
// 3. Connect D-Bus proxy (async - doesn't block if daemon isn't ready yet).
this._connectProxy(); this._connectProxy();
// 4. Track focused window (lightweight - no clipboard I/O).
this._connectFocusTracking(); this._connectFocusTracking();
// 5. Monitor clipboard via Meta.Selection (GNOME-native, no Wayland protocol needed).
this._connectClipboardMonitor(); this._connectClipboardMonitor();
// 6. Register keyboard shortcut.
this._registerShortcut(); this._registerShortcut();
} }
@ -261,9 +250,7 @@ export default class StrataExtension extends Extension {
]; ];
for (const want of PREFERRED) for (const want of PREFERRED)
if (mimes.includes(want)) return want; if (mimes.includes(want)) return want;
// Allowlist only: see comment in pick_mime (daemon). Reading unknown // Allowlist only: see pick_mime in the daemon. Unknown mime types are skipped.
// mime types could pull a 1 GB blob into Shell memory before we can
// size-check it.
return null; return null;
} }
@ -288,10 +275,8 @@ export default class StrataExtension extends Extension {
if (this._shuttingDown) return; if (this._shuttingDown) return;
try { try {
_conn.call_finish(result); _conn.call_finish(result);
// Name already owned - daemon managed externally (systemd etc). // Name already owned (e.g. systemd user service) - don't spawn a second instance.
console.log('[Strata] daemon already running, skipping spawn');
} catch (_) { } catch (_) {
// Name not owned - spawn it ourselves.
this._doSpawnDaemon(); this._doSpawnDaemon();
} }
} }
@ -351,10 +336,7 @@ export default class StrataExtension extends Extension {
const lifetimeMs = (GLib.get_monotonic_time() / 1000) - this._daemonSpawnTime; const lifetimeMs = (GLib.get_monotonic_time() / 1000) - this._daemonSpawnTime;
this._daemon = null; this._daemon = null;
if (this._shuttingDown) { if (this._shuttingDown) return;
console.log(`[Strata] daemon exited cleanly during shutdown (status=${exit})`);
return;
}
// Reset attempt counter if the daemon ran long enough to be considered healthy. // Reset attempt counter if the daemon ran long enough to be considered healthy.
if (lifetimeMs >= 5000) { if (lifetimeMs >= 5000) {
@ -380,7 +362,6 @@ export default class StrataExtension extends Extension {
if (this._shuttingDown) return; if (this._shuttingDown) return;
// Exponential backoff: 1s, 2s, 4s, 8s, 16s // Exponential backoff: 1s, 2s, 4s, 8s, 16s
const backoffMs = 1000 * Math.pow(2, Math.max(0, this._daemonRestartAttempts - 1)); const backoffMs = 1000 * Math.pow(2, Math.max(0, this._daemonRestartAttempts - 1));
console.log(`[Strata] respawning daemon in ${backoffMs}ms`);
this._daemonRestartTimerId = GLib.timeout_add(GLib.PRIORITY_DEFAULT, backoffMs, () => { this._daemonRestartTimerId = GLib.timeout_add(GLib.PRIORITY_DEFAULT, backoffMs, () => {
this._daemonRestartTimerId = null; this._daemonRestartTimerId = null;
this._spawnDaemon(); this._spawnDaemon();
@ -549,8 +530,8 @@ export default class StrataExtension extends Extension {
} }
async _processItemAdded(params) { async _processItemAdded(params) {
if (this.#busy) return; if (this._busy) return;
this.#busy = true; this._busy = true;
try { try {
const [id, mimeType, preview] = params.deepUnpack(); const [id, mimeType, preview] = params.deepUnpack();
@ -570,7 +551,7 @@ export default class StrataExtension extends Extension {
return GLib.SOURCE_REMOVE; return GLib.SOURCE_REMOVE;
}); });
} finally { } finally {
this.#busy = false; this._busy = false;
} }
} }
@ -614,17 +595,9 @@ export default class StrataExtension extends Extension {
} }
/** Load light.css into the global St theme context ONCE. It is scoped under /** Load light.css once. It is scoped under `.strata-theme-light` and stays
* `.strata-theme-light`, so it stays inert until the panel adds that class * inert until the panel adds that class. We do not subscribe to the theme
* (dark/light switching is the panel's class toggle, independent of this). * context's 'changed' signal because load_stylesheet itself emits it. */
*
* 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);

View file

@ -1,6 +1,6 @@
{ {
"name": "Strata", "name": "Strata",
"description": "A fast, stutter-free clipboard manager. All I/O runs in a Rust daemon - GNOME Shell is never blocked.", "description": "A fast clipboard manager. Storage, search and image decoding run in a separate Rust daemon.",
"uuid": "strata@edu4rdshl.dev", "uuid": "strata@edu4rdshl.dev",
"version": 8, "version": 8,
"shell-version": ["45", "46", "47", "48", "49", "50"], "shell-version": ["45", "46", "47", "48", "49", "50"],

View file

@ -1,10 +1,4 @@
/** /* prefs.js - Strata preferences window. */
* prefs.js - Strata preferences window (GNOME 45+ / Adw).
*
* Pages:
* General: max-history SpinRow, keyboard-shortcut ShortcutRow
* Privacy: excluded-apps ExpanderRow + StringList (one entry per line)
*/
import Adw from 'gi://Adw'; import Adw from 'gi://Adw';
import Gtk from 'gi://Gtk'; import Gtk from 'gi://Gtk';
@ -22,17 +16,12 @@ export default class StrataPreferences extends ExtensionPreferences {
window.add(this._buildPrivacyPage(settings)); window.add(this._buildPrivacyPage(settings));
} }
// -------------------------------------------------------------------------
// General page
// -------------------------------------------------------------------------
_buildGeneralPage(settings) { _buildGeneralPage(settings) {
const page = new Adw.PreferencesPage({ const page = new Adw.PreferencesPage({
title: 'General', title: 'General',
icon_name: 'preferences-system-symbolic', icon_name: 'preferences-system-symbolic',
}); });
// ── History ──────────────────────────────────────────────────────────
const historyGroup = new Adw.PreferencesGroup({ title: 'History' }); const historyGroup = new Adw.PreferencesGroup({ title: 'History' });
const maxHistoryRow = new Adw.SpinRow({ const maxHistoryRow = new Adw.SpinRow({
@ -89,7 +78,6 @@ export default class StrataPreferences extends ExtensionPreferences {
page.add(historyGroup); page.add(historyGroup);
// ── Appearance ───────────────────────────────────────────────────────
const appearanceGroup = new Adw.PreferencesGroup({ title: 'Appearance' }); const appearanceGroup = new Adw.PreferencesGroup({ title: 'Appearance' });
const themes = [ const themes = [
@ -180,7 +168,6 @@ export default class StrataPreferences extends ExtensionPreferences {
page.add(appearanceGroup); page.add(appearanceGroup);
// ── Keyboard ─────────────────────────────────────────────────────────
const kbGroup = new Adw.PreferencesGroup({ title: 'Keyboard' }); const kbGroup = new Adw.PreferencesGroup({ title: 'Keyboard' });
const shortcutRow = new Adw.ActionRow({ const shortcutRow = new Adw.ActionRow({
@ -208,10 +195,6 @@ export default class StrataPreferences extends ExtensionPreferences {
return page; return page;
} }
// -------------------------------------------------------------------------
// Privacy page
// -------------------------------------------------------------------------
_buildPrivacyPage(settings) { _buildPrivacyPage(settings) {
const page = new Adw.PreferencesPage({ const page = new Adw.PreferencesPage({
title: 'Privacy', title: 'Privacy',
@ -223,7 +206,6 @@ export default class StrataPreferences extends ExtensionPreferences {
description: 'Items copied while these apps have focus will not be stored in history. Enter a partial app name (case-insensitive).', description: 'Items copied while these apps have focus will not be stored in history. Enter a partial app name (case-insensitive).',
}); });
// We use a StringList model bound to excluded-apps.
const model = new Gtk.StringList(); const model = new Gtk.StringList();
const currentApps = settings.get_strv('excluded-apps'); const currentApps = settings.get_strv('excluded-apps');
for (const app of currentApps) for (const app of currentApps)
@ -239,7 +221,6 @@ export default class StrataPreferences extends ExtensionPreferences {
settings.set_strv('excluded-apps', apps); settings.set_strv('excluded-apps', apps);
}; };
// Each item in the list: an EditableLabel + Remove button.
const listBox = new Gtk.ListBox({ const listBox = new Gtk.ListBox({
selection_mode: Gtk.SelectionMode.NONE, selection_mode: Gtk.SelectionMode.NONE,
css_classes: ['boxed-list'], css_classes: ['boxed-list'],
@ -280,7 +261,6 @@ export default class StrataPreferences extends ExtensionPreferences {
listBox.append(row); listBox.append(row);
} }
// Add-new row.
const addRow = new Adw.ActionRow({ activatable: false }); const addRow = new Adw.ActionRow({ activatable: false });
const addEntry = new Gtk.Entry({ const addEntry = new Gtk.Entry({
placeholder_text: 'App name…', placeholder_text: 'App name…',
@ -314,10 +294,6 @@ export default class StrataPreferences extends ExtensionPreferences {
return page; return page;
} }
// -------------------------------------------------------------------------
// Keyboard shortcut dialog
// -------------------------------------------------------------------------
_showShortcutDialog(parent, settings) { _showShortcutDialog(parent, settings) {
const dialog = new Adw.MessageDialog({ const dialog = new Adw.MessageDialog({
heading: 'Set Keyboard Shortcut', heading: 'Set Keyboard Shortcut',

View file

@ -48,13 +48,8 @@ export const ClipboardItem = GObject.registerClass({
this._id = id; this._id = id;
this._mimeType = mimeType; this._mimeType = mimeType;
/** D-Bus proxy used for lazy thumbnail fetch. */
this._proxy = opts.proxy ?? null; this._proxy = opts.proxy ?? null;
/** Optional shared LRU cache (Map<id, string filePath>) - populated on first fetch. */
this._thumbCache = opts.thumbCache ?? null; this._thumbCache = opts.thumbCache ?? null;
/** Stored on the actor so panel.js can filter by it. */
this.actor = this;
this._strataPreview = mimeType.startsWith('image/') ? '' : preview;
const row = new St.BoxLayout({ const row = new St.BoxLayout({
style_class: 'strata-item-row', style_class: 'strata-item-row',
@ -112,10 +107,6 @@ export const ClipboardItem = GObject.registerClass({
return icon; return icon;
} }
/** Build an image thumbnail container.
* - If already in shared cache, apply immediately.
* - Else fetch via GetThumbnailRemote, write to ~/.cache/strata/thumbnails/{id}.png,
* then apply. The decode happens off-thread (CSS background-image loader). */
_buildThumbnail(id) { _buildThumbnail(id) {
const container = new St.Widget({ const container = new St.Widget({
width: THUMB_SIZE, width: THUMB_SIZE,
@ -135,18 +126,15 @@ export const ClipboardItem = GObject.registerClass({
}; };
try { try {
// Fast path: already cached this session.
if (this._thumbCache?.has(id)) { if (this._thumbCache?.has(id)) {
applyStyle(); applyStyle();
return container; return container;
} }
// Second fast path: file exists on disk from a previous session.
if (GLib.file_test(cachePath, GLib.FileTest.EXISTS)) { if (GLib.file_test(cachePath, GLib.FileTest.EXISTS)) {
this._thumbCache?.set(id, cachePath); this._thumbCache?.set(id, cachePath);
applyStyle(); applyStyle();
return container; return container;
} }
// Slow path: ask the daemon for the bytes, then write file async.
GLib.mkdir_with_parents(cacheDir, 0o755); GLib.mkdir_with_parents(cacheDir, 0o755);
if (!this._proxy) { if (!this._proxy) {
this._fallbackIcon(container); this._fallbackIcon(container);

View file

@ -298,7 +298,7 @@ export class StrataPanel {
const widget = this._makeItemWidget(id, mimeType, preview); const widget = this._makeItemWidget(id, mimeType, preview);
this._widgets.set(id, widget); this._widgets.set(id, widget);
this._itemList.insert_child_at_index(widget.actor, 0); this._itemList.insert_child_at_index(widget, 0);
this._setActiveWidget(widget); this._setActiveWidget(widget);
} catch (e) { } catch (e) {
console.error(`[Strata] prependItem failed for id=${id} mime=${mimeType}:`, e); console.error(`[Strata] prependItem failed for id=${id} mime=${mimeType}:`, e);
@ -425,7 +425,7 @@ export class StrataPanel {
this._items.push({ id, mimeType, preview }); this._items.push({ id, mimeType, preview });
const widget = this._makeItemWidget(id, mimeType, preview); const widget = this._makeItemWidget(id, mimeType, preview);
this._widgets.set(id, widget); this._widgets.set(id, widget);
this._itemList.add_child(widget.actor); this._itemList.add_child(widget);
} catch (e) { } catch (e) {
console.error(`[Strata] _appendItemFromMeta failed for id=${meta?.id}:`, e); console.error(`[Strata] _appendItemFromMeta failed for id=${meta?.id}:`, e);
} }
@ -475,12 +475,8 @@ export class StrataPanel {
for (let j = i; j < e; j++) for (let j = i; j < e; j++)
this._appendItemFromMeta(this._searchResults[j]); this._appendItemFromMeta(this._searchResults[j]);
}; };
// The very first chunk of a fresh render (start === 0, right after // First chunk runs synchronously so the list never paints empty
// _clearListDom) is rendered synchronously, in the same frame as // between clear and first append. Later chunks yield to idle.
// the clear, so the list never paints empty in between - that
// empty frame is the "blink" seen while typing a search. Later
// chunks (and scroll-driven pages) yield to idle to keep frames
// light.
if (i === 0) { if (i === 0) {
renderChunk(); renderChunk();
} else { } else {