From fd89468fd0721ff4b29c593a474aeecd64042477 Mon Sep 17 00:00:00 2001 From: Fabio Fantoni Date: Sun, 16 Aug 2026 16:59:38 +0200 Subject: [PATCH 1/2] printers@cinnamon.org: update the applet even when lpstat fails The initial printer list is fetched with Util.spawn_async(), which runs the command through cinnamon-subprocess-wrapper and pushes the result back over DBus. PushSubprocessResult() only invokes the callback when the command succeeded, so when lpstat is missing (cups-client is not installed, or CUPS is not installed at all) or exits non-zero, the callback is silently dropped and _updateApplet() is never reached. The applet then keeps the visibility it had at creation time: with the default "show icon when printers exist" setting and no printers, the icon stays in the panel forever. The same happened when lpstat succeeded but printed nothing, because the callback returned early on empty output. Use Util.spawnAsyncIO() instead: it reports the exit status and stderr to the callback, and throws right away if the binary cannot be executed at all, so both failure paths can log a warning and still call _updateApplet(). Also drop the hardcoded /usr/bin path so lpstat is looked up in PATH. Assisted-by: Claude Code:claude-opus-5 --- .../applets/printers@cinnamon.org/applet.js | 16 +++++++++++----- 1 file changed, 11 insertions(+), 5 deletions(-) diff --git a/files/usr/share/cinnamon/applets/printers@cinnamon.org/applet.js b/files/usr/share/cinnamon/applets/printers@cinnamon.org/applet.js index ae40344e3b..d3ae1f42ea 100644 --- a/files/usr/share/cinnamon/applets/printers@cinnamon.org/applet.js +++ b/files/usr/share/cinnamon/applets/printers@cinnamon.org/applet.js @@ -243,19 +243,25 @@ class CinnamonPrintersApplet extends Applet.TextIconApplet { _bootstrapPrinters() { try { - Util.spawn_async(['/usr/bin/lpstat', '-e'], (out) => { - if (!out || !this._cancellable || this._cancellable.is_cancelled()) + Util.spawnAsyncIO(['lpstat', '-e'], (out, err, exitCode) => { + if (!this._cancellable || this._cancellable.is_cancelled()) return; - for (const name of out.trim().split('\n')) { - if (name.trim()) - this._getOrCreatePrinter(name.trim()); + if (exitCode !== 0) { + global.logWarning(`printers@cinnamon.org: could not list printers: ${err ? err.trim() : `lpstat exited with ${exitCode}`}`); + } else if (out) { + for (const name of out.trim().split('\n')) { + if (name.trim()) + this._getOrCreatePrinter(name.trim()); + } } this._updateApplet(); }); } catch (e) { + // lpstat is missing (no cups client installed) - nothing to list. global.logWarning(`printers@cinnamon.org: could not list printers: ${e.message}`); + this._updateApplet(); } } From ef8d96930033598f6cb7e53876116edcc2e74dc5 Mon Sep 17 00:00:00 2001 From: Fabio Fantoni Date: Mon, 17 Aug 2026 21:48:15 +0200 Subject: [PATCH 2/2] printers@cinnamon.org: don't offer system-config-printer when it is missing The applet menu item shown when no printer is configured, the context menu entry and the activation of a printer row all spawn system-config-printer unconditionally. On a system installed without the printing stack that program is not there, so those actions can only produce an "Execution of 'system-config-printer' failed" error notification. Cinnamon's own settings already deal with this: the Printers entry of System Settings is a standalone module, and SAModule.process() only lists it when the executable is found in PATH. Look the binary up once at construction time with Cinnamon.find_program_in_path() and keep the result in this._hasConfigTool. The context menu entry is built as before but starts out hidden, and the callback only makes it visible again, so the menu keeps its usual order and the separator finalizeContextMenu() adds hides itself along with the entry when the program is missing. The callback also schedules a menu rebuild, so a menu built before the lookup completed picks up the result. When the program is missing the activation handler of the printer rows is not connected and the placeholder item is shown as insensitive, instead of pretending it can open something. Assisted-by: Claude Code:claude-opus-5 --- .../applets/printers@cinnamon.org/applet.js | 40 +++++++++++++++---- 1 file changed, 32 insertions(+), 8 deletions(-) diff --git a/files/usr/share/cinnamon/applets/printers@cinnamon.org/applet.js b/files/usr/share/cinnamon/applets/printers@cinnamon.org/applet.js index d3ae1f42ea..1b918d453f 100644 --- a/files/usr/share/cinnamon/applets/printers@cinnamon.org/applet.js +++ b/files/usr/share/cinnamon/applets/printers@cinnamon.org/applet.js @@ -1,4 +1,5 @@ const Applet = imports.ui.applet; +const Cinnamon = imports.gi.Cinnamon; const CinnamonDesktop = imports.gi.CinnamonDesktop; const GLib = imports.gi.GLib; const Gio = imports.gi.Gio; @@ -12,6 +13,8 @@ const Util = imports.misc.util; const PANEL_EDIT_MODE_KEY = "panel-edit-mode"; +const CONFIG_TOOL = 'system-config-printer'; + const PRINTER_STATE_STOPPED = 5; const JOB_STATE_PROCESSING = 5; @@ -141,9 +144,13 @@ class CinnamonPrintersApplet extends Applet.TextIconApplet { this.set_applet_tooltip(_("Printers")); this.set_applet_icon_symbolic_name('xsi-printer'); - let printersContextItem = new PopupMenu.PopupIconMenuItem(_("Printers"), 'xsi-printer', St.IconType.SYMBOLIC); - printersContextItem.connect('activate', () => Util.spawn(['system-config-printer'])); - this._applet_context_menu.addMenuItem(printersContextItem); + // Kept hidden until the asynchronous lookup below says the program is + // there; the separator finalizeContextMenu() adds hides itself along + // with it. + this._printersContextItem = new PopupMenu.PopupIconMenuItem(_("Printers"), 'xsi-printer', St.IconType.SYMBOLIC); + this._printersContextItem.connect('activate', () => Util.spawn([CONFIG_TOOL])); + this._applet_context_menu.addMenuItem(this._printersContextItem); + this._printersContextItem.actor.hide(); this.menu = new Applet.AppletPopupMenu(this, orientation); this.menu.connect('open-state-changed', (menu, open) => { @@ -163,6 +170,7 @@ class CinnamonPrintersApplet extends Applet.TextIconApplet { this._printers = new Map(); this._hasPrinterConfig = false; + this._hasConfigTool = false; this._cupsSubscription = null; this._rebuildId = 0; this._menuDirty = false; @@ -170,6 +178,16 @@ class CinnamonPrintersApplet extends Applet.TextIconApplet { this._wallClock = new CinnamonDesktop.WallClock(); this._cancellable = new Gio.Cancellable(); + + Cinnamon.find_program_in_path(CONFIG_TOOL, (path) => { + if (path == null || !this._cancellable || this._cancellable.is_cancelled()) + return; + + this._hasConfigTool = true; + this._printersContextItem.actor.show(); + this._scheduleMenuRebuild(); + }); + Gio.DBus.session.call( 'org.freedesktop.DBus', '/org/freedesktop/DBus', 'org.freedesktop.DBus', 'ListActivatableNames', @@ -480,8 +498,12 @@ class CinnamonPrintersApplet extends Applet.TextIconApplet { this.menu.removeAll(); if (this._printers.size === 0) { - let printersItem = new PopupMenu.PopupIconMenuItem(_("Printers"), 'xsi-printer', St.IconType.SYMBOLIC); - printersItem.connect('activate', () => Util.spawn(['system-config-printer'])); + let printersItem = new PopupMenu.PopupIconMenuItem(_("Printers"), 'xsi-printer', St.IconType.SYMBOLIC, + { reactive: this._hasConfigTool }); + if (this._hasConfigTool) + printersItem.connect('activate', () => Util.spawn([CONFIG_TOOL])); + else + printersItem.actor.add_style_class_name('popup-inactive-menu-item'); this.menu.addMenuItem(printersItem); } @@ -502,9 +524,11 @@ class CinnamonPrintersApplet extends Applet.TextIconApplet { printerItem.setButton(0, null, null, null); if (warning || printer.state === PRINTER_STATE_STOPPED || printer.jobs.size > 0) printerItem.label.add_style_class_name('popup-device-menu-item'); - printerItem.connect('activate', () => { - Util.spawn(['system-config-printer', '--show-jobs', name]); - }); + if (this._hasConfigTool) { + printerItem.connect('activate', () => { + Util.spawn([CONFIG_TOOL, '--show-jobs', name]); + }); + } if (this._hasPrinterConfig) { printerItem.setButton(1, 'xsi-emblem-system', _("Show properties"), () => {