From 6a828a0807196729a86c6aab1c690c2456da4bd9 Mon Sep 17 00:00:00 2001 From: Fabio Fantoni Date: Fri, 17 Jul 2026 20:39:54 +0200 Subject: [PATCH 1/2] keyboard: fall back to tecla for the layout preview gkbd-keyboard-display comes from libgnomekbd, which is unmaintained and being removed from distributions (GNOME replaced it with tecla in 45, Debian is dropping libgnomekbd). It is also an X11-only GTK3 program: in a Wayland session it just aborts with "Error initializing GTK: Unable to open display", even when libgnomekbd is still installed, so the layout preview was unavailable there. cinnamon-settings already hid the layout preview button (in the "Add layout" dialog) and the "Test layout" button when gkbd-keyboard-display was missing, silently dropping the feature. Fall back to tecla when gkbd-keyboard-display is not installed, and hide the buttons only when neither program is available. gkbd-keyboard-display stays preferred where present, since tecla only renders a standard pc105 geometry (this addresses the TODO removed from InputSources.py). As tecla is a GTK4 program it also works under Wayland, restoring the preview there. On Wayland gkbd-keyboard-display is not considered at all: the preview uses tecla when available, and the preview/test buttons and the applet menu entry are hidden otherwise. Session detection uses Meta.is_wayland_compositor() in the applet and util.get_session_type() in cinnamon-settings. tecla is invoked with a single "layout+variant" argument: '+' is accepted as the layout/variant separator by every tecla version (space and tab only since tecla 48), so it is used unconditionally. The keyboard applet added its "Show Keyboard Layout" menu entry unconditionally, so with no preview program installed clicking it only produced an "Execution of 'gkbd-keyboard-display' failed" error notification. It now uses the same lookup and fallback, and the entry is hidden when no preview program is available. Assisted-by: Claude Code:claude-opus-5 --- .../applets/keyboard@cinnamon.org/applet.js | 37 ++++++++++++++++--- .../bin/AddKeyboardLayout.py | 31 ++++++++++++---- .../cinnamon-settings/bin/InputSources.py | 6 +-- 3 files changed, 57 insertions(+), 17 deletions(-) diff --git a/files/usr/share/cinnamon/applets/keyboard@cinnamon.org/applet.js b/files/usr/share/cinnamon/applets/keyboard@cinnamon.org/applet.js index 36ce02902a..c8028b33eb 100644 --- a/files/usr/share/cinnamon/applets/keyboard@cinnamon.org/applet.js +++ b/files/usr/share/cinnamon/applets/keyboard@cinnamon.org/applet.js @@ -4,6 +4,8 @@ const Main = imports.ui.main; const PopupMenu = imports.ui.popupMenu; const Util = imports.misc.util; const Gio = imports.gi.Gio; +const GLib = imports.gi.GLib; +const Meta = imports.gi.Meta; const Signals = imports.signals; const KeyboardManager = imports.ui.keyboardManager; const IBus = imports.gi.IBus; @@ -86,7 +88,9 @@ class CinnamonKeyboardApplet extends Applet.Applet { this._propSection = new PopupMenu.PopupMenuSection(); this.menu.addMenuItem(this._propSection); this.menu.addMenuItem(new PopupMenu.PopupSeparatorMenuItem()); - this.showLayoutAction = this.menu.addAction(_("Show Keyboard Layout"), () => this._showActiveLayout()); + this.showLayoutAction = null; + if (this._getLayoutDisplayProgram() !== null) + this.showLayoutAction = this.menu.addAction(_("Show Keyboard Layout"), () => this._showActiveLayout()); this.menu.addAction(_("Show Character Table"), () => { Main.overview.hide(); Util.spawn(['gucharmap']); @@ -104,16 +108,36 @@ class CinnamonKeyboardApplet extends Applet.Applet { } } + _getLayoutDisplayProgram() { + // gkbd-keyboard-display (libgnomekbd) is X11-only, so on Wayland only + // tecla is usable. On X11 keep gkbd-keyboard-display preferred where + // still available (tecla only shows a standard pc105 geometry). + const candidates = Meta.is_wayland_compositor() ? + ['tecla'] : ['gkbd-keyboard-display', 'tecla']; + for (const program of candidates) { + if (GLib.find_program_in_path(program)) + return program; + } + return null; + } + _showActiveLayout() { Main.overview.hide(); let source = this._inputSourcesManager.currentSource; let description = source.xkbLayout; - if (source.variant.length > 0) - description = '%s\t%s'.format(description, source.variant); - - Util.spawn(['gkbd-keyboard-display', '-l', description]); + if (this._getLayoutDisplayProgram() === 'tecla') { + // '+' is the layout/variant separator accepted by every tecla + // version (space and tab only since tecla 48) + if (source.variant.length > 0) + description = '%s+%s'.format(description, source.variant); + Util.spawn(['tecla', description]); + } else { + if (source.variant.length > 0) + description = '%s\t%s'.format(description, source.variant); + Util.spawn(['gkbd-keyboard-display', '-l', description]); + } } _onCurrentSourceChanged() { @@ -233,7 +257,8 @@ class CinnamonKeyboardApplet extends Applet.Applet { this.actor.hide(); } - this.showLayoutAction.setSensitive(selected.type === 'xkb'); + if (this.showLayoutAction !== null) + this.showLayoutAction.setSensitive(selected.type === 'xkb'); this._updatePropertySection(selected.properties); } diff --git a/files/usr/share/cinnamon/cinnamon-settings/bin/AddKeyboardLayout.py b/files/usr/share/cinnamon/cinnamon-settings/bin/AddKeyboardLayout.py index fd71a64f52..ddd3be3ded 100644 --- a/files/usr/share/cinnamon/cinnamon-settings/bin/AddKeyboardLayout.py +++ b/files/usr/share/cinnamon/cinnamon-settings/bin/AddKeyboardLayout.py @@ -1,7 +1,6 @@ #!/usr/bin/python3 import gettext -import os import subprocess import cairo @@ -12,6 +11,7 @@ gi.require_version('Pango', '1.0') from gi.repository import GLib, Gio, Gtk, GObject, CinnamonDesktop, IBus, Pango +from bin import util from bin.SettingsWidgets import Keybinding from xapp.SettingsWidgets import SettingsPage from xapp.GSettingsWidgets import PXGSettingsBackend, GSettingsSwitch @@ -21,11 +21,28 @@ INPUT_SOURCE_SETTINGS = "org.cinnamon.desktop.input-sources" SHOW_ALL_SOURCES_KEY="show-all-sources" -def make_gkbd_keyboard_args(layout, variant): - if variant: - return ["gkbd-keyboard-display", "-l", f"{layout}\t{variant}"] +def get_layout_preview_program(): + # gkbd-keyboard-display (libgnomekbd) is X11-only, so on Wayland only tecla + # is usable. On X11 keep gkbd-keyboard-display preferred where still + # available (tecla only shows a standard pc105 geometry). + if util.get_session_type() == "wayland": + candidates = ("tecla",) else: - return ["gkbd-keyboard-display", "-l", layout] + candidates = ("gkbd-keyboard-display", "tecla") + for program in candidates: + if GLib.find_program_in_path(program): + return program + return None + +def make_layout_preview_args(layout, variant): + program = get_layout_preview_program() + if program == "tecla": + # '+' is the layout/variant separator accepted by every tecla version + # (space and tab only since tecla 48) + return [program, f"{layout}+{variant}" if variant else layout] + if variant: + return [program, "-l", f"{layout}\t{variant}"] + return [program, "-l", layout] def make_ibus_display_name(engine): name = engine.get_longname() @@ -99,7 +116,7 @@ def __init__(self, used_ids, xkb_only=False): self.response_id = None - if not GLib.find_program_in_path("gkbd-keyboard-display"): + if get_layout_preview_program() is None: self.preview_button.set_visible(False) self._ibus = IBus.Bus.new_async() @@ -167,7 +184,7 @@ def _on_preview_button_clicked(self, button, data=None): display_name = self.layouts_sort_store.get_value(iter, LAYOUT_DISPLAY_NAME_COLUMN) layout_layout = self.layouts_sort_store.get_value(iter, LAYOUT_LAYOUT_COLUMN) layout_variant = self.layouts_sort_store.get_value(iter, LAYOUT_VARIANT_COLUMN) - args = make_gkbd_keyboard_args(layout_layout, layout_variant) + args = make_layout_preview_args(layout_layout, layout_variant) subprocess.Popen(args) def _on_all_sources_changed(self, button, data=None): diff --git a/files/usr/share/cinnamon/cinnamon-settings/bin/InputSources.py b/files/usr/share/cinnamon/cinnamon-settings/bin/InputSources.py index 1fb3ea6636..f9c6cb81d3 100644 --- a/files/usr/share/cinnamon/cinnamon-settings/bin/InputSources.py +++ b/files/usr/share/cinnamon/cinnamon-settings/bin/InputSources.py @@ -41,9 +41,7 @@ def __init__(self): self.test_layout_button = builder.get_object("test_layout") self.test_layout_button.connect("clicked", self.on_test_layout_clicked) - # TODO: maybe use tecla as an alternative for wayland, if we don't roll something ourselves. - # btw there's no plan for tecla to support different keyboard geometries than a standard pc105. - if not GLib.find_program_in_path("gkbd-keyboard-display"): + if AddKeyboardLayout.get_layout_preview_program() is None: self.test_layout_button.set_visible(False) self.engine_config_button = builder.get_object("engine_config_button") @@ -218,7 +216,7 @@ def on_move_layout_down_clicked(self, button, data=None): def on_test_layout_clicked(self, button, data=None): source = self._get_selected_source() - args = AddKeyboardLayout.make_gkbd_keyboard_args(source.xkb_layout, source.xkb_variant) + args = AddKeyboardLayout.make_layout_preview_args(source.xkb_layout, source.xkb_variant) subprocess.Popen(args) def on_engine_config_clicked(self, button, data=None): From f74370b0c6ae9c6c4e4df7673d0318b40e04a788 Mon Sep 17 00:00:00 2001 From: Fabio Fantoni Date: Fri, 17 Jul 2026 20:46:57 +0200 Subject: [PATCH 2/2] keyboard@cinnamon.org: hide "Show Character Table" when gucharmap is missing The applet added the "Show Character Table" menu entry unconditionally and spawned gucharmap on click. gucharmap is not necessarily installed (in Debian it is only a Recommends of cinnamon), and when missing the click only produced an "Execution of 'gucharmap' failed" error notification, the same failure mode just fixed for "Show Keyboard Layout". Hide the entry when gucharmap is not present. The separator above these two entries is now added only when at least one of them is shown, so it is not left dangling when neither program is available. Assisted-by: Claude Code:claude-opus-4-8 --- .../applets/keyboard@cinnamon.org/applet.js | 17 +++++++++++------ 1 file changed, 11 insertions(+), 6 deletions(-) diff --git a/files/usr/share/cinnamon/applets/keyboard@cinnamon.org/applet.js b/files/usr/share/cinnamon/applets/keyboard@cinnamon.org/applet.js index c8028b33eb..c4c2be6aca 100644 --- a/files/usr/share/cinnamon/applets/keyboard@cinnamon.org/applet.js +++ b/files/usr/share/cinnamon/applets/keyboard@cinnamon.org/applet.js @@ -87,14 +87,19 @@ class CinnamonKeyboardApplet extends Applet.Applet { this.menu.addMenuItem(this._propSeparator); this._propSection = new PopupMenu.PopupMenuSection(); this.menu.addMenuItem(this._propSection); - this.menu.addMenuItem(new PopupMenu.PopupSeparatorMenuItem()); this.showLayoutAction = null; - if (this._getLayoutDisplayProgram() !== null) + const haveLayoutDisplay = this._getLayoutDisplayProgram() !== null; + const haveCharmap = GLib.find_program_in_path('gucharmap') !== null; + if (haveLayoutDisplay || haveCharmap) + this.menu.addMenuItem(new PopupMenu.PopupSeparatorMenuItem()); + if (haveLayoutDisplay) this.showLayoutAction = this.menu.addAction(_("Show Keyboard Layout"), () => this._showActiveLayout()); - this.menu.addAction(_("Show Character Table"), () => { - Main.overview.hide(); - Util.spawn(['gucharmap']); - }); + if (haveCharmap) { + this.menu.addAction(_("Show Character Table"), () => { + Main.overview.hide(); + Util.spawn(['gucharmap']); + }); + } this._applet_context_menu.addSettingsAction(_("Manage keyboard layouts"), 'keyboard', "layouts"); this._inputSourcesManager = KeyboardManager.getInputSourceManager();