From a3860e39a6572b41a0d877567183f8447e93633f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Danielle=20For=C3=A9?= Date: Wed, 30 Apr 2025 13:23:58 -0700 Subject: [PATCH 1/4] Create LayoutManager --- meson.build | 2 + src/Indicator.vala | 20 ++++--- src/InputSource.vala | 120 +++++++++++++++++++++++++++++++++++++++++ src/LayoutManager.vala | 50 +++++++++++++++++ 4 files changed, 185 insertions(+), 7 deletions(-) create mode 100644 src/InputSource.vala create mode 100644 src/LayoutManager.vala diff --git a/meson.build b/meson.build index a32c369..7acc275 100644 --- a/meson.build +++ b/meson.build @@ -47,7 +47,9 @@ shared_module( meson.project_name(), gresource, 'src/Indicator.vala', + 'src/InputSource.vala', 'src/LayoutButton.vala', + 'src/LayoutManager.vala', 'src/PopoverWidget.vala', config_file, dependencies: [ diff --git a/src/Indicator.vala b/src/Indicator.vala index ee3e7de..ff365ce 100644 --- a/src/Indicator.vala +++ b/src/Indicator.vala @@ -85,6 +85,8 @@ public class Keyboard.Indicator : Wingpanel.Indicator { update_visibility (); }); + update_visibility (); + indicator_box.button_press_event.connect ((e) => { if (e.button == Gdk.BUTTON_MIDDLE) { popover_widget.next (); @@ -93,21 +95,21 @@ public class Keyboard.Indicator : Wingpanel.Indicator { return Gdk.EVENT_PROPAGATE; }); - popover_widget = new Keyboard.Widgets.PopoverWidget (server_type); - popover_widget.updated.connect (() => { - update_visibility (); + var layout_manager = LayoutManager.get_default (); + + layout_manager.notify["current-lang-code"].connect (() => { + layouts_icon.label = layout_manager.current_lang_code[0:2]; }); - popover_widget.updated (); + layout_manager.input_sources.items_changed.connect (() => { + layouts_revealer.reveal_child = layout_manager.input_sources.n_items > 0 || settings.get_boolean ("always-show-layout"); + }); } return indicator_box; } private void update_visibility () { - layouts_icon.label = popover_widget.current_language_code[0:2]; - layouts_revealer.reveal_child = popover_widget.has_multiple_layouts () || settings.get_boolean ("always-show-layout"); - numlock_revealer.reveal_child = keymap.get_num_lock_state () && settings.get_boolean ("numlock"); capslock_revealer.reveal_child = keymap.get_caps_lock_state () && settings.get_boolean ("capslock"); @@ -128,6 +130,10 @@ public class Keyboard.Indicator : Wingpanel.Indicator { } public override Gtk.Widget? get_widget () { + if (popover_widget == null) { + popover_widget = new Keyboard.Widgets.PopoverWidget (server_type); + } + return popover_widget; } diff --git a/src/InputSource.vala b/src/InputSource.vala new file mode 100644 index 0000000..0353bd0 --- /dev/null +++ b/src/InputSource.vala @@ -0,0 +1,120 @@ +/* +* Copyright 2017-2020 elementary, Inc. (https://elementary.io) +* +* This program is free software; you can redistribute it and/or +* modify it under the terms of the GNU General Public +* License as published by the Free Software Foundation; either +* version 2 of the License, or (at your option) any later version. +* +* This program is distributed in the hope that it will be useful, +* but WITHOUT ANY WARRANTY; without even the implied warranty of +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +* General Public License for more details. +* +* You should have received a copy of the GNU General Public +* License along with this program; if not, write to the +* Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, +* Boston, MA 02110-1301 USA +*/ + +/** + * Type of a keyboard-InputSource as described in the description of + * "org.gnome.desktop.input-sources sources". + */ +public enum Keyboard.LayoutType { IBUS, XKB } + +/** + * Immutable class that respresents a keyboard-InputSource according to + * "org.gnome.desktop.input-sources sources". + * This means that the enum parameter @layout_type equals the first string in the + * tupel of strings, and the @name parameter equals the second string. + */ +public class Keyboard.InputSource : Object { + public static InputSource? new_from_variant (Variant? variant) { + if (variant.is_of_type (new VariantType ("(ss)"))) { + unowned string type; + unowned string name; + + variant.get ("(&s&s)", out type, out name); + + if (name != "") { + if (type == "xkb") { + return new InputSource (LayoutType.XKB, name); + } else if (type == "ibus") { + return new InputSource (LayoutType.IBUS, name); + } + } else { + critical ("Attempt to create invalid InputSource name %s", name); + } + + } else { + critical ("Ignoring attempt to create InputSource from invalid VariantType"); + } + + return null; + } + + public LayoutType layout_type { get; construct; } + // Name of input source as stored in settings e.g. "gb" (xkb) or "xkb:gb:extd:eng" (ibus) or "mozc-jp" (ibus) + // These names are used both in org/gnome/desktop/input-sources and desktop/ibus/general/preload-engines + public string name { get; construct; } + + private InputSource (LayoutType layout_type, string name) { + Object ( + layout_type: layout_type, + name: name + ); + } + + public bool equal (InputSource other) { + return this.layout_type == other.layout_type && this.name == other.name; + } + + public string get_lang_code () { + switch (layout_type) { + case XKB: + if ("+" in name) { + var layouts = name.split ("+", 2); + return layouts[0]; + } else { + return name; + } + case IBUS: + // if (engines == null) { + // continue; + // } + + // foreach (var engine in engines) { + // if (engine != null && engine.name == source) { + // current_lang_code = engine.get_language (); + // } + // } + break; + } + + return "?"; + } + + /** + * GSettings saves values in the form of GLib.Variant and this + * function creates a Variant representing this object. + */ + public GLib.Variant to_variant () requires (name != "") { + string type_name = ""; + switch (layout_type) { + case LayoutType.IBUS: + type_name = "ibus"; + break; + case LayoutType.XKB: + type_name = "xkb"; + break; + default: + assert_not_reached (); + } + GLib.Variant first = new GLib.Variant.string (type_name); + GLib.Variant second = new GLib.Variant.string (name); + GLib.Variant result = new GLib.Variant.tuple ({first, second}); + + return result; + } +} diff --git a/src/LayoutManager.vala b/src/LayoutManager.vala new file mode 100644 index 0000000..e53a612 --- /dev/null +++ b/src/LayoutManager.vala @@ -0,0 +1,50 @@ +/* + * Copyright 2025 elementary, Inc. (https://elementary.io) + * SPDX-License-Identifier: LGPL-2.1-or-later + */ + +public class Keyboard.LayoutManager : Object { + public string current_lang_code { get; private set; } + public GLib.ListStore input_sources { get; private set; } + + private static GLib.Once layout_manager; + public static LayoutManager get_default () { + return layout_manager.once (() => { + return new LayoutManager (); + }); + } + + private GLib.Settings settings; + + private LayoutManager () {} + + construct { + input_sources = new GLib.ListStore (typeof (InputSource)); + + settings = new Settings ("org.gnome.desktop.input-sources"); + // FIXME: current key is deprecated https://github.com/elementary/gala/issues/2367 + settings.changed["current"].connect (update_current); + settings.changed["sources"].connect (update_sources); + } + + private void update_current () { + var current_input_source = (InputSource) input_sources.get_item (settings.get_uint ("current")); + current_lang_code = current_input_source.get_lang_code (); + } + + private void update_sources () { + input_sources.remove_all (); + + var sources = settings.get_value ("sources"); + for (size_t i = 0; i < sources.n_children (); i++) { + var input_source = InputSource.new_from_variant (sources.get_child_value (i)); + + uint pos = -1; + if (!input_sources.find (input_source, out pos)) { + input_sources.append (input_source); + } + } + + update_current (); + } +} From 30314dafe248273f38211c40ce145f9c91c9217a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Danielle=20For=C3=A9?= Date: Wed, 30 Apr 2025 15:18:35 -0700 Subject: [PATCH 2/4] Modernize InputSource --- src/InputSource.vala | 103 ++++++++++++++----------------------------- 1 file changed, 34 insertions(+), 69 deletions(-) diff --git a/src/InputSource.vala b/src/InputSource.vala index 0353bd0..1174124 100644 --- a/src/InputSource.vala +++ b/src/InputSource.vala @@ -1,60 +1,25 @@ /* -* Copyright 2017-2020 elementary, Inc. (https://elementary.io) -* -* This program is free software; you can redistribute it and/or -* modify it under the terms of the GNU General Public -* License as published by the Free Software Foundation; either -* version 2 of the License, or (at your option) any later version. -* -* This program is distributed in the hope that it will be useful, -* but WITHOUT ANY WARRANTY; without even the implied warranty of -* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU -* General Public License for more details. -* -* You should have received a copy of the GNU General Public -* License along with this program; if not, write to the -* Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, -* Boston, MA 02110-1301 USA -*/ - -/** - * Type of a keyboard-InputSource as described in the description of - * "org.gnome.desktop.input-sources sources". + * Copyright 2017-2025 elementary, Inc. (https://elementary.io) + * SPDX-License-Identifier: GPL-2.0-or-later */ -public enum Keyboard.LayoutType { IBUS, XKB } /** - * Immutable class that respresents a keyboard-InputSource according to - * "org.gnome.desktop.input-sources sources". + * Respresents a keyboard-InputSource according to "org.gnome.desktop.input-sources sources". * This means that the enum parameter @layout_type equals the first string in the * tupel of strings, and the @name parameter equals the second string. */ public class Keyboard.InputSource : Object { - public static InputSource? new_from_variant (Variant? variant) { - if (variant.is_of_type (new VariantType ("(ss)"))) { - unowned string type; - unowned string name; - - variant.get ("(&s&s)", out type, out name); - - if (name != "") { - if (type == "xkb") { - return new InputSource (LayoutType.XKB, name); - } else if (type == "ibus") { - return new InputSource (LayoutType.IBUS, name); - } - } else { - critical ("Attempt to create invalid InputSource name %s", name); - } - - } else { - critical ("Ignoring attempt to create InputSource from invalid VariantType"); - } - - return null; + /** + * Type of a keyboard-InputSource as described in the description of + * "org.gnome.desktop.input-sources sources". + */ + public enum LayoutType { + IBUS, + XKB } public LayoutType layout_type { get; construct; } + // Name of input source as stored in settings e.g. "gb" (xkb) or "xkb:gb:extd:eng" (ibus) or "mozc-jp" (ibus) // These names are used both in org/gnome/desktop/input-sources and desktop/ibus/general/preload-engines public string name { get; construct; } @@ -66,33 +31,33 @@ public class Keyboard.InputSource : Object { ); } - public bool equal (InputSource other) { - return this.layout_type == other.layout_type && this.name == other.name; - } - public string get_lang_code () { - switch (layout_type) { - case XKB: - if ("+" in name) { - var layouts = name.split ("+", 2); - return layouts[0]; - } else { - return name; - } - case IBUS: - // if (engines == null) { - // continue; - // } + public static InputSource? new_from_variant (Variant? variant) { + if (!variant.is_of_type (new VariantType ("(ss)"))) { + critical ("Ignoring attempt to create InputSource from invalid VariantType"); + return null; + } - // foreach (var engine in engines) { - // if (engine != null && engine.name == source) { - // current_lang_code = engine.get_language (); - // } - // } - break; + unowned string type; + unowned string name; + variant.get ("(&s&s)", out type, out name); + + if (name == "") { + critical ("Attempt to create invalid InputSource name %s", name); + return null; } - return "?"; + if (type == "xkb") { + return new InputSource (LayoutType.XKB, name); + } else if (type == "ibus") { + return new InputSource (LayoutType.IBUS, name); + } + + return null; + } + + public bool equal (InputSource other) { + return layout_type == other.layout_type && name == other.name; } /** From 68959609568dbc68750f796abcb06b624b85bfed Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Danielle=20For=C3=A9?= Date: Wed, 30 Apr 2025 15:24:31 -0700 Subject: [PATCH 3/4] Scope down to managing the list --- src/Indicator.vala | 19 +++++++++---------- src/LayoutManager.vala | 9 --------- src/PopoverWidget.vala | 7 +++---- 3 files changed, 12 insertions(+), 23 deletions(-) diff --git a/src/Indicator.vala b/src/Indicator.vala index ff365ce..5b8707b 100644 --- a/src/Indicator.vala +++ b/src/Indicator.vala @@ -85,8 +85,6 @@ public class Keyboard.Indicator : Wingpanel.Indicator { update_visibility (); }); - update_visibility (); - indicator_box.button_press_event.connect ((e) => { if (e.button == Gdk.BUTTON_MIDDLE) { popover_widget.next (); @@ -95,12 +93,15 @@ public class Keyboard.Indicator : Wingpanel.Indicator { return Gdk.EVENT_PROPAGATE; }); - var layout_manager = LayoutManager.get_default (); - - layout_manager.notify["current-lang-code"].connect (() => { - layouts_icon.label = layout_manager.current_lang_code[0:2]; + popover_widget = new Keyboard.Widgets.PopoverWidget (server_type); + popover_widget.updated.connect (() => { + update_visibility (); }); + update_visibility (); + + var layout_manager = LayoutManager.get_default (); + layout_manager.input_sources.items_changed.connect (() => { layouts_revealer.reveal_child = layout_manager.input_sources.n_items > 0 || settings.get_boolean ("always-show-layout"); }); @@ -110,6 +111,8 @@ public class Keyboard.Indicator : Wingpanel.Indicator { } private void update_visibility () { + layouts_icon.label = popover_widget.current_language_code[0:2]; + numlock_revealer.reveal_child = keymap.get_num_lock_state () && settings.get_boolean ("numlock"); capslock_revealer.reveal_child = keymap.get_caps_lock_state () && settings.get_boolean ("capslock"); @@ -130,10 +133,6 @@ public class Keyboard.Indicator : Wingpanel.Indicator { } public override Gtk.Widget? get_widget () { - if (popover_widget == null) { - popover_widget = new Keyboard.Widgets.PopoverWidget (server_type); - } - return popover_widget; } diff --git a/src/LayoutManager.vala b/src/LayoutManager.vala index e53a612..411666a 100644 --- a/src/LayoutManager.vala +++ b/src/LayoutManager.vala @@ -4,7 +4,6 @@ */ public class Keyboard.LayoutManager : Object { - public string current_lang_code { get; private set; } public GLib.ListStore input_sources { get; private set; } private static GLib.Once layout_manager; @@ -22,15 +21,9 @@ public class Keyboard.LayoutManager : Object { input_sources = new GLib.ListStore (typeof (InputSource)); settings = new Settings ("org.gnome.desktop.input-sources"); - // FIXME: current key is deprecated https://github.com/elementary/gala/issues/2367 - settings.changed["current"].connect (update_current); settings.changed["sources"].connect (update_sources); } - private void update_current () { - var current_input_source = (InputSource) input_sources.get_item (settings.get_uint ("current")); - current_lang_code = current_input_source.get_lang_code (); - } private void update_sources () { input_sources.remove_all (); @@ -44,7 +37,5 @@ public class Keyboard.LayoutManager : Object { input_sources.append (input_source); } } - - update_current (); } } diff --git a/src/PopoverWidget.vala b/src/PopoverWidget.vala index 3a46e4b..9353e20 100644 --- a/src/PopoverWidget.vala +++ b/src/PopoverWidget.vala @@ -122,10 +122,6 @@ public class Keyboard.Widgets.PopoverWidget : Gtk.Box { settings = new GLib.Settings ("org.gnome.desktop.input-sources"); - settings.changed["sources"].connect (() => { - populate_layouts (); - }); - settings.changed["current"].connect_after (() => { set_active_layout_from_settings (); // Gala will set the keymap if required updated (); @@ -154,6 +150,9 @@ public class Keyboard.Widgets.PopoverWidget : Gtk.Box { show_all (); + var layout_manager = LayoutManager.get_default (); + layout_manager.input_sources.items_changed.connect (populate_layouts); + populate_layouts (); } From cfb7bf8d020725b6f2a123ee38cc58d4b86f6b38 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Danielle=20For=C3=A9?= Date: Wed, 30 Apr 2025 15:25:08 -0700 Subject: [PATCH 4/4] Remove extra line --- src/InputSource.vala | 1 - 1 file changed, 1 deletion(-) diff --git a/src/InputSource.vala b/src/InputSource.vala index 1174124..1985dcc 100644 --- a/src/InputSource.vala +++ b/src/InputSource.vala @@ -31,7 +31,6 @@ public class Keyboard.InputSource : Object { ); } - public static InputSource? new_from_variant (Variant? variant) { if (!variant.is_of_type (new VariantType ("(ss)"))) { critical ("Ignoring attempt to create InputSource from invalid VariantType");