diff --git a/files/usr/share/cinnamon/cinnamon-settings/bin/AddKeyboardLayout.py b/files/usr/share/cinnamon/cinnamon-settings/bin/AddKeyboardLayout.py
index baca3656f9..85f38f0067 100644
--- a/files/usr/share/cinnamon/cinnamon-settings/bin/AddKeyboardLayout.py
+++ b/files/usr/share/cinnamon/cinnamon-settings/bin/AddKeyboardLayout.py
@@ -44,9 +44,10 @@ def make_ibus_display_name(engine):
LAYOUT_VARIANT_COLUMN = 4
class AddKeyboardLayoutDialog():
- def __init__(self, used_ids):
+ def __init__(self, used_ids, xkb_only=False):
self.input_source_settings = Gio.Settings(schema_id=INPUT_SOURCE_SETTINGS)
self.original_used_ids = set(used_ids)
+ self.xkb_only = xkb_only
builder = Gtk.Builder()
builder.set_translation_domain('cinnamon')
@@ -84,12 +85,17 @@ def __init__(self, used_ids):
column.pack_start(cell, True)
column.add_attribute(cell, "text", LAYOUT_DISPLAY_NAME_COLUMN)
- column = Gtk.TreeViewColumn(title=_("Input method"))
- column.set_sort_column_id(LAYOUT_TYPE_COLUMN)
- self.layouts_view.append_column(column)
+ self.ibus_column = Gtk.TreeViewColumn(title=_("Input method"))
+ self.ibus_column.set_sort_column_id(LAYOUT_TYPE_COLUMN)
+ self.layouts_view.append_column(self.ibus_column)
cell = Gtk.CellRendererText(xpad=10)
- column.pack_start(cell, False)
- column.set_cell_data_func(cell, self.layout_type_data_func)
+ self.ibus_column.pack_start(cell, False)
+ self.ibus_column.set_cell_data_func(cell, self.layout_type_data_func)
+
+ # Hide IBus column and change title when in XKB-only mode
+ if self.xkb_only:
+ self.ibus_column.set_visible(False)
+ self.dialog.set_title(_("Choose a Layout"))
self.response_id = None
@@ -124,6 +130,10 @@ def _on_ibus_connected(self, ibus, data=None):
ibus.list_engines_async(5000, None, self._list_ibus_engines_completed)
def _list_ibus_engines_completed(self, ibus, res, data=None):
+ # Skip IBus engines in XKB-only mode
+ if self.xkb_only:
+ return
+
try:
engines = ibus.list_engines_async_finish(res)
except GLib.Error as e:
diff --git a/files/usr/share/cinnamon/cinnamon-settings/bin/InputSources.py b/files/usr/share/cinnamon/cinnamon-settings/bin/InputSources.py
index 76dc502fb8..e8a4caf00e 100644
--- a/files/usr/share/cinnamon/cinnamon-settings/bin/InputSources.py
+++ b/files/usr/share/cinnamon/cinnamon-settings/bin/InputSources.py
@@ -132,10 +132,7 @@ def on_model_updated(self, model, position, removed, added, data=None):
def _update_selected_row(self, data=None):
self.input_sources_list.handler_block(self.source_activate_handler)
- for row in self.input_sources_list.get_children():
- source = row.get_child().input_source
- if source.active:
- self.input_sources_list.select_row(row)
+ self.input_sources_list.unselect_all()
self.input_sources_list.handler_unblock(self.source_activate_handler)
self.update_widgets()
@@ -178,8 +175,9 @@ def on_test_layout_clicked(self, button, data=None):
def on_engine_config_clicked(self, button, data=None):
source = self._get_selected_source()
-
- subprocess.Popen([source.preferences], shell=True)
+ dialog = IBusConfigDialog(source, self.current_input_sources_model.input_source_settings)
+ dialog.run()
+ dialog.destroy()
def update_widgets(self):
# Don't allow removal of last remaining layout
@@ -189,7 +187,8 @@ def update_widgets(self):
source = self._get_selected_source()
if source is not None:
self.test_layout_button.set_sensitive(source.type == "xkb")
- self.engine_config_button.set_sensitive(source.type == "ibus" and source.preferences != '')
+ # Enable Configure button for all IBus sources (not just those with preferences)
+ self.engine_config_button.set_sensitive(source.type == "ibus")
index = self.current_input_sources_model.get_item_index(source)
self.move_layout_up_button.set_sensitive(index > 0)
self.move_layout_down_button.set_sensitive(index < self.current_input_sources_model.get_n_items() - 1)
@@ -201,6 +200,142 @@ def update_widgets(self):
self.move_layout_down_button.set_sensitive(False)
self.remove_layout_button.set_sensitive(False)
+
+class IBusConfigDialog():
+ def __init__(self, source, settings):
+ self.source = source
+ self.settings = settings
+ self.xkb_info = CinnamonDesktop.XkbInfo.new_with_extras()
+
+ # Check if this engine uses "default" layout or has a specific one
+ self.engine_layout = self._get_engine_layout()
+ self.allows_override = (self.engine_layout == "default")
+
+ builder = Gtk.Builder()
+ builder.set_translation_domain('cinnamon')
+ builder.add_from_file("/usr/share/cinnamon/cinnamon-settings/bin/input-sources-list.ui")
+
+ self.dialog = builder.get_object("ibus_config_dialog")
+
+ # Set up name label
+ name_label = builder.get_object("ibus_config_name_label")
+ name_label.set_text(source.display_name)
+
+ # Set up explanation label based on whether override is allowed
+ explanation_label = builder.get_object("ibus_config_explanation_label")
+ if not self.allows_override:
+ layout_display = self._get_engine_layout_display_name()
+ explanation_label.set_text(
+ _("This input method requires the \"%s\" keyboard layout to function correctly. "
+ "The layout is set automatically when you switch to this input method.") % layout_display
+ )
+
+ # Set up layout label
+ self.layout_label = builder.get_object("ibus_config_layout_label")
+
+ # Set up buttons
+ close_button = builder.get_object("ibus_config_close_button")
+ close_button.connect("clicked", self.on_close_clicked)
+
+ self.change_layout_button = builder.get_object("ibus_config_change_layout_button")
+ self.change_layout_button.connect("clicked", self.on_change_layout_clicked)
+ self.change_layout_button.set_sensitive(self.allows_override)
+
+ self.clear_override_button = builder.get_object("ibus_config_clear_override_button")
+ self.clear_override_button.connect("clicked", self.on_clear_override_clicked)
+
+ engine_settings_button = builder.get_object("ibus_config_engine_settings_button")
+ if source.preferences:
+ engine_settings_button.connect("clicked", self.on_engine_settings_clicked)
+ else:
+ engine_settings_button.set_visible(False)
+
+ self.update_layout_display()
+ self.dialog.show_all()
+
+ def _get_engine_layout(self):
+ ibus = IBus.Bus.new()
+ if ibus.is_connected():
+ engines = ibus.get_engines_by_names([self.source.id])
+ if engines:
+ return engines[0].get_layout() or "default"
+ return "default"
+
+ def _get_engine_layout_display_name(self):
+ if not self.engine_layout or self.engine_layout == "default":
+ return _("Default")
+ got, display_name, short_name, layout, variant = self.xkb_info.get_layout_info(self.engine_layout)
+ if got:
+ return f"{display_name} ({self.engine_layout})"
+ return self.engine_layout
+
+ def run(self):
+ return self.dialog.run()
+
+ def destroy(self):
+ self.dialog.destroy()
+
+ def on_close_clicked(self, button, data=None):
+ self.dialog.response(Gtk.ResponseType.CLOSE)
+
+ def get_current_override(self):
+ source_layouts = self.settings.get_value("source-layouts").unpack()
+ return source_layouts.get(self.source.id, None)
+
+ def get_layout_display_name(self, layout_id):
+ if layout_id is None:
+ return None
+ got, display_name, short_name, layout, variant = self.xkb_info.get_layout_info(layout_id)
+ if got:
+ return f"{display_name} ({layout_id})"
+ return layout_id
+
+ def update_layout_display(self):
+ if not self.allows_override:
+ # Engine has a fixed layout requirement
+ self.layout_label.set_text(self._get_engine_layout_display_name())
+ self.clear_override_button.set_sensitive(False)
+ return
+
+ override = self.get_current_override()
+ if override:
+ display_name = self.get_layout_display_name(override)
+ self.layout_label.set_text(display_name)
+ self.clear_override_button.set_sensitive(True)
+ else:
+ self.layout_label.set_text(_("Default"))
+ self.clear_override_button.set_sensitive(False)
+
+ def on_change_layout_clicked(self, button, data=None):
+ # Show the layout picker in XKB-only mode
+ add_dialog = AddKeyboardLayout.AddKeyboardLayoutDialog([], xkb_only=True)
+ add_dialog.dialog.set_transient_for(self.dialog)
+ add_dialog.dialog.show_all()
+ ret = add_dialog.dialog.run()
+ if ret == Gtk.ResponseType.OK:
+ layout_type, layout_id = add_dialog.response
+ self.set_layout_override(layout_id)
+ add_dialog.dialog.destroy()
+
+ def on_clear_override_clicked(self, button, data=None):
+ self.clear_layout_override()
+
+ def on_engine_settings_clicked(self, button, data=None):
+ subprocess.Popen([self.source.preferences], shell=True)
+
+ def set_layout_override(self, layout_id):
+ source_layouts = self.settings.get_value("source-layouts").unpack()
+ source_layouts[self.source.id] = layout_id
+ self.settings.set_value("source-layouts", GLib.Variant("a{ss}", source_layouts))
+ self.update_layout_display()
+
+ def clear_layout_override(self):
+ source_layouts = self.settings.get_value("source-layouts").unpack()
+ if self.source.id in source_layouts:
+ del source_layouts[self.source.id]
+ self.settings.set_value("source-layouts", GLib.Variant("a{ss}", source_layouts))
+ self.update_layout_display()
+
class LayoutIcon(Gtk.Overlay):
def __init__(self, file, dupe_id):
Gtk.Overlay.__init__(self)
@@ -267,6 +402,7 @@ def __init__(self):
self.interface_settings = Gio.Settings(schema_id="org.cinnamon.desktop.interface")
self.interface_settings.connect("changed", self.on_interface_settings_changed)
self.input_source_settings = Gio.Settings(schema_id="org.cinnamon.desktop.input-sources")
+ self.input_source_settings.connect("changed::source-layouts", self.on_source_layouts_changed)
try:
Gio.DBusProxy.new_for_bus(Gio.BusType.SESSION, Gio.DBusProxyFlags.NONE, None,
@@ -293,6 +429,16 @@ def live(self):
return False
return True
+ def _get_layout_override(self, engine_id):
+ source_layouts = self.input_source_settings.get_value("source-layouts").unpack()
+ return source_layouts.get(engine_id, None)
+
+ def _get_layout_display_name(self, layout_id):
+ got, display_name, short_name, layout, variant = self.xkb_info.get_layout_info(layout_id)
+ if got:
+ return display_name
+ return layout_id
+
def _on_ibus_connected(self, ibus, data=None):
if self._proxy is None:
self.refresh_input_source_list()
@@ -324,6 +470,9 @@ def on_interface_settings_changed(self, settings, key, data=None):
if key.startswith("keyboard-layout-"):
self.refresh_input_source_list()
+ def on_source_layouts_changed(self, settings, key, data=None):
+ self.refresh_input_source_list()
+
def refresh_input_source_list(self):
if self.live:
layouts = self._proxy.GetInputSources()
@@ -377,7 +526,18 @@ def show_add_layout_dialog(self):
def create_row(self, source, data=None):
row = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL, spacing=6)
- markup = f"{source.display_name}"
+
+ # Build display name, including layout override for IBus sources
+ display_name = GLib.markup_escape_text(source.display_name)
+ if source.type == "ibus":
+ layout_override = self._get_layout_override(source.id)
+ if layout_override:
+ layout_display = GLib.markup_escape_text(self._get_layout_display_name(layout_override))
+ markup = f"{display_name} | {layout_display}"
+ else:
+ markup = f"{display_name}"
+ else:
+ markup = f"{display_name}"
label = Gtk.Label(label=markup, xalign=0.0, use_markup=True, margin_start=4)
row.pack_start(label, True, True, 0)
diff --git a/files/usr/share/cinnamon/cinnamon-settings/bin/input-sources-list.ui b/files/usr/share/cinnamon/cinnamon-settings/bin/input-sources-list.ui
index c3bbfb49e8..3d883ff966 100644
--- a/files/usr/share/cinnamon/cinnamon-settings/bin/input-sources-list.ui
+++ b/files/usr/share/cinnamon/cinnamon-settings/bin/input-sources-list.ui
@@ -142,6 +142,205 @@
+
True
False
diff --git a/js/ui/keyboardManager.js b/js/ui/keyboardManager.js
index c4ed55a3ae..7c4d879ede 100644
--- a/js/ui/keyboardManager.js
+++ b/js/ui/keyboardManager.js
@@ -16,6 +16,7 @@ DESKTOP_INPUT_SOURCES_SCHEMA = 'org.cinnamon.desktop.input-sources';
KEY_INPUT_SOURCES = 'sources';
KEY_KEYBOARD_OPTIONS = 'xkb-options';
KEY_PER_WINDOW = 'per-window';
+KEY_SOURCE_LAYOUTS = 'source-layouts';
var INPUT_SOURCE_TYPE_XKB = 'xkb';
var INPUT_SOURCE_TYPE_IBUS = 'ibus';
@@ -176,7 +177,7 @@ var KeyboardManager = class {
};
var InputSource = class {
- constructor(type, id, displayName, shortName, flagName, xkbLayout, variant, prefs, index) {
+ constructor(type, id, displayName, shortName, flagName, xkbLayout, variant, prefs, index, layoutOverride) {
this.type = type;
this.id = id;
this.displayName = displayName;
@@ -187,6 +188,7 @@ var InputSource = class {
this.xkbLayout = xkbLayout;
this.variant = variant;
this.preferences = prefs;
+ this._layoutOverride = layoutOverride;
this.properties = null;
@@ -207,6 +209,10 @@ var InputSource = class {
}
_getXkbId() {
+ // Use layout override if set
+ if (this._layoutOverride)
+ return this._layoutOverride;
+
let engineDesc = IBusManager.getIBusManager().getEngineDesc(this.id);
if (!engineDesc)
return this.id;
@@ -411,6 +417,7 @@ var InputSourceSettings = class {
this._settings.connect('changed::%s'.format(KEY_INPUT_SOURCES), this._emitInputSourcesChanged.bind(this));
this._settings.connect('changed::%s'.format(KEY_KEYBOARD_OPTIONS), this._emitKeyboardOptionsChanged.bind(this));
this._settings.connect('changed::%s'.format(KEY_PER_WINDOW), this._emitPerWindowChanged.bind(this));
+ this._settings.connect('changed::%s'.format(KEY_SOURCE_LAYOUTS), this._emitInputSourcesChanged.bind(this));
let sources = this._settings.get_value(KEY_INPUT_SOURCES);
if (sources.n_children() == 0) {
@@ -463,6 +470,10 @@ var InputSourceSettings = class {
get perWindow() {
return this._settings.get_boolean(KEY_PER_WINDOW);
}
+
+ get sourceLayouts() {
+ return this._settings.get_value(KEY_SOURCE_LAYOUTS).deep_unpack();
+ }
};
Signals.addSignalMethods(InputSourceSettings.prototype);
@@ -731,17 +742,34 @@ var InputSourceManager = class {
}
let inputSourcesDupeTracker = {};
+ let sourceLayouts = this._settings.sourceLayouts;
for (let i = 0; i < infosList.length; i++) {
- let is = new InputSource(infosList[i].type,
- infosList[i].id,
- infosList[i].displayName,
- infosList[i].shortName,
- infosList[i].flagName,
- infosList[i].xkbLayout,
- infosList[i].variant,
- infosList[i].prefs,
- i);
+ let info = infosList[i];
+ let layoutOverride = null;
+
+ // Only apply layout overrides for IBus engines that use "default" layout
+ if (info.type == INPUT_SOURCE_TYPE_IBUS && info.id in sourceLayouts) {
+ let engineDesc = this._ibusManager.getEngineDesc(info.id);
+ let engineLayout = engineDesc ? engineDesc.get_layout() : null;
+
+ if (!engineLayout || engineLayout == 'default') {
+ layoutOverride = sourceLayouts[info.id];
+ } else {
+ global.logWarning('Ignoring layout override for IBus engine "%s": engine requires layout "%s"'.format(info.id, engineLayout));
+ }
+ }
+
+ let is = new InputSource(info.type,
+ info.id,
+ info.displayName,
+ info.shortName,
+ info.flagName,
+ info.xkbLayout,
+ info.variant,
+ info.prefs,
+ i,
+ layoutOverride);
is.connect('activate', this.activateInputSource.bind(this));
let key = is.shortName;