Skip to content

Commit 2db8e83

Browse files
authored
input sources: Allow a default xkb layout to be configured for (#13520)
Ibus input methods that support it. By default, the last-used layout (or en-us) is used when switching to a mozc or m17n-based im. This can be overridden at the time of their activation. Add a setting, and repurpose the 'configure' button in the keyboard layout settings to show a dialog that will allow the user to choose a default layout, as well as launch that engine's configuration program if one is available. This will be disabled for engines whose default layout is not 'default' (chewing, hangul, sunpinyin, libthai). Depends on linuxmint/cinnamon-desktop#263 ref: https://forums.linuxmint.com/viewtopic.php?t=461850
1 parent 1290c94 commit 2db8e83

4 files changed

Lines changed: 421 additions & 24 deletions

File tree

files/usr/share/cinnamon/cinnamon-settings/bin/AddKeyboardLayout.py

Lines changed: 16 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -44,9 +44,10 @@ def make_ibus_display_name(engine):
4444
LAYOUT_VARIANT_COLUMN = 4
4545

4646
class AddKeyboardLayoutDialog():
47-
def __init__(self, used_ids):
47+
def __init__(self, used_ids, xkb_only=False):
4848
self.input_source_settings = Gio.Settings(schema_id=INPUT_SOURCE_SETTINGS)
4949
self.original_used_ids = set(used_ids)
50+
self.xkb_only = xkb_only
5051

5152
builder = Gtk.Builder()
5253
builder.set_translation_domain('cinnamon')
@@ -84,12 +85,17 @@ def __init__(self, used_ids):
8485
column.pack_start(cell, True)
8586
column.add_attribute(cell, "text", LAYOUT_DISPLAY_NAME_COLUMN)
8687

87-
column = Gtk.TreeViewColumn(title=_("Input method"))
88-
column.set_sort_column_id(LAYOUT_TYPE_COLUMN)
89-
self.layouts_view.append_column(column)
88+
self.ibus_column = Gtk.TreeViewColumn(title=_("Input method"))
89+
self.ibus_column.set_sort_column_id(LAYOUT_TYPE_COLUMN)
90+
self.layouts_view.append_column(self.ibus_column)
9091
cell = Gtk.CellRendererText(xpad=10)
91-
column.pack_start(cell, False)
92-
column.set_cell_data_func(cell, self.layout_type_data_func)
92+
self.ibus_column.pack_start(cell, False)
93+
self.ibus_column.set_cell_data_func(cell, self.layout_type_data_func)
94+
95+
# Hide IBus column and change title when in XKB-only mode
96+
if self.xkb_only:
97+
self.ibus_column.set_visible(False)
98+
self.dialog.set_title(_("Choose a Layout"))
9399

94100
self.response_id = None
95101

@@ -124,6 +130,10 @@ def _on_ibus_connected(self, ibus, data=None):
124130
ibus.list_engines_async(5000, None, self._list_ibus_engines_completed)
125131

126132
def _list_ibus_engines_completed(self, ibus, res, data=None):
133+
# Skip IBus engines in XKB-only mode
134+
if self.xkb_only:
135+
return
136+
127137
try:
128138
engines = ibus.list_engines_async_finish(res)
129139
except GLib.Error as e:

files/usr/share/cinnamon/cinnamon-settings/bin/InputSources.py

Lines changed: 168 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -132,10 +132,7 @@ def on_model_updated(self, model, position, removed, added, data=None):
132132
def _update_selected_row(self, data=None):
133133
self.input_sources_list.handler_block(self.source_activate_handler)
134134

135-
for row in self.input_sources_list.get_children():
136-
source = row.get_child().input_source
137-
if source.active:
138-
self.input_sources_list.select_row(row)
135+
self.input_sources_list.unselect_all()
139136

140137
self.input_sources_list.handler_unblock(self.source_activate_handler)
141138
self.update_widgets()
@@ -178,8 +175,9 @@ def on_test_layout_clicked(self, button, data=None):
178175

179176
def on_engine_config_clicked(self, button, data=None):
180177
source = self._get_selected_source()
181-
182-
subprocess.Popen([source.preferences], shell=True)
178+
dialog = IBusConfigDialog(source, self.current_input_sources_model.input_source_settings)
179+
dialog.run()
180+
dialog.destroy()
183181

184182
def update_widgets(self):
185183
# Don't allow removal of last remaining layout
@@ -189,7 +187,8 @@ def update_widgets(self):
189187
source = self._get_selected_source()
190188
if source is not None:
191189
self.test_layout_button.set_sensitive(source.type == "xkb")
192-
self.engine_config_button.set_sensitive(source.type == "ibus" and source.preferences != '')
190+
# Enable Configure button for all IBus sources (not just those with preferences)
191+
self.engine_config_button.set_sensitive(source.type == "ibus")
193192
index = self.current_input_sources_model.get_item_index(source)
194193
self.move_layout_up_button.set_sensitive(index > 0)
195194
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):
201200
self.move_layout_down_button.set_sensitive(False)
202201
self.remove_layout_button.set_sensitive(False)
203202

203+
204+
class IBusConfigDialog():
205+
def __init__(self, source, settings):
206+
self.source = source
207+
self.settings = settings
208+
self.xkb_info = CinnamonDesktop.XkbInfo.new_with_extras()
209+
210+
# Check if this engine uses "default" layout or has a specific one
211+
self.engine_layout = self._get_engine_layout()
212+
self.allows_override = (self.engine_layout == "default")
213+
214+
builder = Gtk.Builder()
215+
builder.set_translation_domain('cinnamon')
216+
builder.add_from_file("/usr/share/cinnamon/cinnamon-settings/bin/input-sources-list.ui")
217+
218+
self.dialog = builder.get_object("ibus_config_dialog")
219+
220+
# Set up name label
221+
name_label = builder.get_object("ibus_config_name_label")
222+
name_label.set_text(source.display_name)
223+
224+
# Set up explanation label based on whether override is allowed
225+
explanation_label = builder.get_object("ibus_config_explanation_label")
226+
if not self.allows_override:
227+
layout_display = self._get_engine_layout_display_name()
228+
explanation_label.set_text(
229+
_("This input method requires the \"%s\" keyboard layout to function correctly. "
230+
"The layout is set automatically when you switch to this input method.") % layout_display
231+
)
232+
233+
# Set up layout label
234+
self.layout_label = builder.get_object("ibus_config_layout_label")
235+
236+
# Set up buttons
237+
close_button = builder.get_object("ibus_config_close_button")
238+
close_button.connect("clicked", self.on_close_clicked)
239+
240+
self.change_layout_button = builder.get_object("ibus_config_change_layout_button")
241+
self.change_layout_button.connect("clicked", self.on_change_layout_clicked)
242+
self.change_layout_button.set_sensitive(self.allows_override)
243+
244+
self.clear_override_button = builder.get_object("ibus_config_clear_override_button")
245+
self.clear_override_button.connect("clicked", self.on_clear_override_clicked)
246+
247+
engine_settings_button = builder.get_object("ibus_config_engine_settings_button")
248+
if source.preferences:
249+
engine_settings_button.connect("clicked", self.on_engine_settings_clicked)
250+
else:
251+
engine_settings_button.set_visible(False)
252+
253+
self.update_layout_display()
254+
self.dialog.show_all()
255+
256+
def _get_engine_layout(self):
257+
ibus = IBus.Bus.new()
258+
if ibus.is_connected():
259+
engines = ibus.get_engines_by_names([self.source.id])
260+
if engines:
261+
return engines[0].get_layout() or "default"
262+
return "default"
263+
264+
def _get_engine_layout_display_name(self):
265+
if not self.engine_layout or self.engine_layout == "default":
266+
return _("Default")
267+
got, display_name, short_name, layout, variant = self.xkb_info.get_layout_info(self.engine_layout)
268+
if got:
269+
return f"{display_name} ({self.engine_layout})"
270+
return self.engine_layout
271+
272+
def run(self):
273+
return self.dialog.run()
274+
275+
def destroy(self):
276+
self.dialog.destroy()
277+
278+
def on_close_clicked(self, button, data=None):
279+
self.dialog.response(Gtk.ResponseType.CLOSE)
280+
281+
def get_current_override(self):
282+
source_layouts = self.settings.get_value("source-layouts").unpack()
283+
return source_layouts.get(self.source.id, None)
284+
285+
def get_layout_display_name(self, layout_id):
286+
if layout_id is None:
287+
return None
288+
got, display_name, short_name, layout, variant = self.xkb_info.get_layout_info(layout_id)
289+
if got:
290+
return f"{display_name} ({layout_id})"
291+
return layout_id
292+
293+
def update_layout_display(self):
294+
if not self.allows_override:
295+
# Engine has a fixed layout requirement
296+
self.layout_label.set_text(self._get_engine_layout_display_name())
297+
self.clear_override_button.set_sensitive(False)
298+
return
299+
300+
override = self.get_current_override()
301+
if override:
302+
display_name = self.get_layout_display_name(override)
303+
self.layout_label.set_text(display_name)
304+
self.clear_override_button.set_sensitive(True)
305+
else:
306+
self.layout_label.set_text(_("Default"))
307+
self.clear_override_button.set_sensitive(False)
308+
309+
def on_change_layout_clicked(self, button, data=None):
310+
# Show the layout picker in XKB-only mode
311+
add_dialog = AddKeyboardLayout.AddKeyboardLayoutDialog([], xkb_only=True)
312+
add_dialog.dialog.set_transient_for(self.dialog)
313+
add_dialog.dialog.show_all()
314+
ret = add_dialog.dialog.run()
315+
if ret == Gtk.ResponseType.OK:
316+
layout_type, layout_id = add_dialog.response
317+
self.set_layout_override(layout_id)
318+
add_dialog.dialog.destroy()
319+
320+
def on_clear_override_clicked(self, button, data=None):
321+
self.clear_layout_override()
322+
323+
def on_engine_settings_clicked(self, button, data=None):
324+
subprocess.Popen([self.source.preferences], shell=True)
325+
326+
def set_layout_override(self, layout_id):
327+
source_layouts = self.settings.get_value("source-layouts").unpack()
328+
source_layouts[self.source.id] = layout_id
329+
self.settings.set_value("source-layouts", GLib.Variant("a{ss}", source_layouts))
330+
self.update_layout_display()
331+
332+
def clear_layout_override(self):
333+
source_layouts = self.settings.get_value("source-layouts").unpack()
334+
if self.source.id in source_layouts:
335+
del source_layouts[self.source.id]
336+
self.settings.set_value("source-layouts", GLib.Variant("a{ss}", source_layouts))
337+
self.update_layout_display()
338+
204339
class LayoutIcon(Gtk.Overlay):
205340
def __init__(self, file, dupe_id):
206341
Gtk.Overlay.__init__(self)
@@ -267,6 +402,7 @@ def __init__(self):
267402
self.interface_settings = Gio.Settings(schema_id="org.cinnamon.desktop.interface")
268403
self.interface_settings.connect("changed", self.on_interface_settings_changed)
269404
self.input_source_settings = Gio.Settings(schema_id="org.cinnamon.desktop.input-sources")
405+
self.input_source_settings.connect("changed::source-layouts", self.on_source_layouts_changed)
270406

271407
try:
272408
Gio.DBusProxy.new_for_bus(Gio.BusType.SESSION, Gio.DBusProxyFlags.NONE, None,
@@ -293,6 +429,16 @@ def live(self):
293429
return False
294430
return True
295431

432+
def _get_layout_override(self, engine_id):
433+
source_layouts = self.input_source_settings.get_value("source-layouts").unpack()
434+
return source_layouts.get(engine_id, None)
435+
436+
def _get_layout_display_name(self, layout_id):
437+
got, display_name, short_name, layout, variant = self.xkb_info.get_layout_info(layout_id)
438+
if got:
439+
return display_name
440+
return layout_id
441+
296442
def _on_ibus_connected(self, ibus, data=None):
297443
if self._proxy is None:
298444
self.refresh_input_source_list()
@@ -324,6 +470,9 @@ def on_interface_settings_changed(self, settings, key, data=None):
324470
if key.startswith("keyboard-layout-"):
325471
self.refresh_input_source_list()
326472

473+
def on_source_layouts_changed(self, settings, key, data=None):
474+
self.refresh_input_source_list()
475+
327476
def refresh_input_source_list(self):
328477
if self.live:
329478
layouts = self._proxy.GetInputSources()
@@ -377,7 +526,18 @@ def show_add_layout_dialog(self):
377526

378527
def create_row(self, source, data=None):
379528
row = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL, spacing=6)
380-
markup = f"<b>{source.display_name}</b>"
529+
530+
# Build display name, including layout override for IBus sources
531+
display_name = GLib.markup_escape_text(source.display_name)
532+
if source.type == "ibus":
533+
layout_override = self._get_layout_override(source.id)
534+
if layout_override:
535+
layout_display = GLib.markup_escape_text(self._get_layout_display_name(layout_override))
536+
markup = f"<b>{display_name}</b> <small>| {layout_display}</small>"
537+
else:
538+
markup = f"<b>{display_name}</b>"
539+
else:
540+
markup = f"<b>{display_name}</b>"
381541
label = Gtk.Label(label=markup, xalign=0.0, use_markup=True, margin_start=4)
382542
row.pack_start(label, True, True, 0)
383543

0 commit comments

Comments
 (0)