Skip to content

Commit 1abf8da

Browse files
lenemterzeebokleonardo-lemos
authored
Add settings-daemon application launcher support (#536)
* Add settings-daemon application launcher support * Address review comments --------- Co-authored-by: Ryan Kornheisl <ryan@skarva.tech> Co-authored-by: Leonardo Lemos <leonardolemos@live.com>
1 parent da4a4df commit 1abf8da

11 files changed

Lines changed: 429 additions & 288 deletions

po/POTFILES

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -20,11 +20,14 @@ src/Layout/Widgets/AdvancedSettingsPanel.vala
2020
src/Layout/Widgets/Display.vala
2121
src/Plug.vala
2222
src/Shortcuts/Backend/ConflictsManager.vala
23-
src/Shortcuts/Backend/CustomShortcutSettings.vala
23+
src/Shortcuts/Backend/CustomShortcuts.vala
2424
src/Shortcuts/Backend/Settings.vala
25-
src/Shortcuts/Backend/ShortcutsList.vala
2625
src/Shortcuts/Backend/Shortcut.vala
27-
src/Shortcuts/Shortcuts.vala
26+
src/Shortcuts/Backend/ShortcutsList.vala
27+
src/Shortcuts/Backend/Utils.vala
28+
src/Shortcuts/Widgets/AppChooser.vala
29+
src/Shortcuts/Widgets/AppChooserRow.vala
2830
src/Shortcuts/Widgets/CustomShortcutListBox.vala
2931
src/Shortcuts/Widgets/CustomShortcutRow.vala
3032
src/Shortcuts/Widgets/ShortcutListBox.vala
33+
src/Shortcuts/Shortcuts.vala

src/Shortcuts/Backend/ConflictsManager.vala

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,20 @@ class Keyboard.Shortcuts.ConflictsManager : GLib.Object {
5252
private static bool custom_shortcut_conflicts (Shortcut shortcut, out string name, out string group) {
5353
name = "";
5454
group = SectionID.CUSTOM.to_string ();
55-
return CustomShortcutSettings.shortcut_conflicts (shortcut, out name, null);
55+
56+
var application_shortcuts = new GLib.Settings (CustomShortcuts.SETTINGS_SCHEMA);
57+
var shortcuts = (CustomShortcuts.ParsedShortcut[]) application_shortcuts.get_value (CustomShortcuts.APPLICATION_SHORTCUTS);
58+
for (int i = 0; i < shortcuts.length; i++) {
59+
for (int j = 0; j < shortcuts[i].keybindings.length; j++) {
60+
var action_shortcut = new Shortcut.parse (shortcuts[i].keybindings[j]);
61+
if (shortcut.is_equal (action_shortcut)) {
62+
name = shortcuts[i].target;
63+
return true;
64+
}
65+
}
66+
}
67+
68+
return false;
5669
}
5770

5871
private static bool standard_shortcut_conflicts (Shortcut shortcut, out string name, out string group) {

src/Shortcuts/Backend/CustomShortcutSettings.vala

Lines changed: 0 additions & 158 deletions
This file was deleted.
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
/*
2+
* SPDX-License-Identifier: GPL-2.0-or-later
3+
* SPDX-FileCopyrightText: 2025 elementary, Inc. (https://elementary.io)
4+
*/
5+
6+
namespace Keyboard.Shortcuts.CustomShortcuts {
7+
public enum ActionType {
8+
DESKTOP_FILE,
9+
COMMAND_LINE
10+
}
11+
12+
public struct ParsedShortcut {
13+
ActionType type;
14+
string target;
15+
GLib.HashTable<string, Variant> parameters;
16+
string[] keybindings;
17+
}
18+
19+
public const string SETTINGS_SCHEMA = "io.elementary.settings-daemon.applications";
20+
public const string APPLICATION_SHORTCUTS = "application-shortcuts";
21+
}

src/Shortcuts/Backend/Utils.vala

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
/*
2+
* SPDX-License-Identifier: GPL-3.0-or-later
3+
* SPDX-FileCopyrightText: 2025 elementary, Inc. (https://elementary.io)
4+
*/
5+
6+
namespace Keyboard.Shortcuts.Utils {
7+
public GLib.Icon? get_action_icon (GLib.DesktopAppInfo app_info, string action) {
8+
unowned var icon_theme = Gtk.IconTheme.get_for_display (Gdk.Display.get_default ());
9+
10+
GLib.Icon? action_icon = null;
11+
try {
12+
var keyfile = new GLib.KeyFile ();
13+
keyfile.load_from_file (app_info.get_filename (), GLib.KeyFileFlags.NONE);
14+
15+
var group = "Desktop Action %s".printf (action);
16+
if (keyfile.has_key (group, GLib.KeyFileDesktop.KEY_ICON)) {
17+
var icon_name = keyfile.get_string (group, GLib.KeyFileDesktop.KEY_ICON);
18+
19+
if (icon_theme.has_icon (icon_name)) {
20+
action_icon = new ThemedIcon (icon_name);
21+
}
22+
}
23+
} catch (Error e) {
24+
warning (e.message);
25+
}
26+
27+
return action_icon;
28+
}
29+
}

src/Shortcuts/Shortcuts.vala

Lines changed: 1 addition & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -62,8 +62,6 @@ namespace Keyboard.Shortcuts {
6262
private SwitcherRow custom_shortcuts_row;
6363

6464
construct {
65-
CustomShortcutSettings.init ();
66-
6765
unowned var list = Shortcuts.ShortcutsList.get_default ();
6866

6967
section_switcher = new Gtk.ListBox ();
@@ -113,12 +111,7 @@ namespace Keyboard.Shortcuts {
113111
for (int id = 0; id < SectionID.CUSTOM; id++) {
114112
shortcut_views += new ShortcutListBox ((SectionID) id);
115113
}
116-
117-
if (CustomShortcutSettings.available) {
118-
var custom_tree = new CustomShortcutListBox ();
119-
120-
shortcut_views += custom_tree;
121-
}
114+
shortcut_views += new CustomShortcutListBox ();
122115

123116
foreach (unowned Gtk.Widget view in shortcut_views) {
124117
stack.add_child (view);
Lines changed: 118 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,118 @@
1+
/* SPDX-License-Identifier: GPL-3.0-or-later
2+
* SPDX-FileCopyrightText: 2025 elementary, Inc. (https://elementary.io)
3+
*/
4+
5+
public class Keyboard.Shortcuts.AppChooser : Granite.Dialog {
6+
public signal void app_chosen (string filename, GLib.HashTable<string, Variant> parameters);
7+
public signal void custom_command_chosen (string command, GLib.HashTable<string, Variant> parameters);
8+
9+
private Gtk.ListBox list;
10+
private Gtk.SearchEntry search_entry;
11+
private Gtk.Entry custom_entry;
12+
13+
construct {
14+
search_entry = new Gtk.SearchEntry () {
15+
placeholder_text = _("Search Applications")
16+
};
17+
search_entry.set_key_capture_widget (this);
18+
19+
list = new Gtk.ListBox () {
20+
hexpand = true,
21+
vexpand = true
22+
};
23+
list.add_css_class (Granite.STYLE_CLASS_RICH_LIST);
24+
list.set_sort_func (sort_function);
25+
list.set_filter_func (filter_function);
26+
27+
var scrolled = new Gtk.ScrolledWindow () {
28+
child = list,
29+
has_frame = true
30+
};
31+
32+
custom_entry = new Gtk.Entry () {
33+
placeholder_text = _("Type in a custom command"),
34+
primary_icon_activatable = false,
35+
primary_icon_name = "utilities-terminal-symbolic"
36+
};
37+
38+
var box = new Gtk.Box (VERTICAL, 6);
39+
box.append (search_entry);
40+
box.append (scrolled);
41+
box.append (custom_entry);
42+
43+
modal = true;
44+
default_height = 500;
45+
default_width = 400;
46+
get_content_area ().append (box);
47+
add_button (_("Cancel"), Gtk.ResponseType.CANCEL);
48+
49+
// TRANSLATORS: This string is used by screen reader
50+
update_property (Gtk.AccessibleProperty.LABEL, _("Select an app"), -1);
51+
52+
search_entry.grab_focus ();
53+
search_entry.search_changed.connect (() => {
54+
list.invalidate_filter ();
55+
});
56+
57+
response.connect (hide);
58+
59+
list.row_activated.connect (on_app_selected);
60+
custom_entry.activate.connect (on_custom_command_entered);
61+
}
62+
63+
public void init_list (GLib.List<GLib.DesktopAppInfo> app_infos) {
64+
foreach (var app_info in app_infos) {
65+
var icon = app_info.get_icon () ?? new ThemedIcon ("application-default-icon");
66+
var name = app_info.get_name ();
67+
var description = app_info.get_description ();
68+
var filename = File.new_for_path (app_info.get_filename ()).get_basename ();
69+
70+
list.prepend (new AppChooserRow ({icon, null, name, description, null, filename}));
71+
72+
unowned var icon_theme = Gtk.IconTheme.get_for_display (Gdk.Display.get_default ());
73+
var actions = app_info.list_actions ();
74+
for (var i = 0; i < actions.length; i++) {
75+
var action = actions[i];
76+
var action_icon = Utils.get_action_icon (app_info, action);
77+
var action_name = "%s%s".printf (name, app_info.get_action_name (action));
78+
list.prepend (new AppChooserRow ({icon, action_icon, action_name, description, action, filename}));
79+
}
80+
}
81+
}
82+
83+
private int sort_function (Gtk.ListBoxRow row1, Gtk.ListBoxRow row2) {
84+
unowned AppChooserRow row_1 = (AppChooserRow) row1.get_child ();
85+
unowned AppChooserRow row_2 = (AppChooserRow) row2.get_child ();
86+
87+
var name_1 = row_1.info.name;
88+
var name_2 = row_2.info.name;
89+
90+
return name_1.collate (name_2);
91+
}
92+
93+
private bool filter_function (Gtk.ListBoxRow list_box_row) {
94+
var app_row = (AppChooserRow) list_box_row.get_child ();
95+
return search_entry.text.down () in app_row.info.name.down ()
96+
|| search_entry.text.down () in app_row.info.name.down ();
97+
}
98+
99+
private void on_app_selected (Gtk.ListBoxRow list_box_row) {
100+
var app_row = (AppChooserRow) list_box_row.get_child ();
101+
102+
var parameters = new GLib.HashTable<string, Variant> (null, null);
103+
if (app_row.info.action != null) {
104+
parameters["action"] = app_row.info.action;
105+
}
106+
107+
app_chosen (app_row.info.filename, parameters);
108+
hide ();
109+
}
110+
111+
private void on_custom_command_entered () {
112+
custom_command_chosen (
113+
custom_entry.text,
114+
new GLib.HashTable<string, Variant> (null, null)
115+
);
116+
hide ();
117+
}
118+
}

0 commit comments

Comments
 (0)