From c6c73ca1d9d26f45d780231a8be41b0ed02b672a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Danielle=20For=C3=A9?= Date: Mon, 23 Jun 2025 13:04:41 -0700 Subject: [PATCH 1/2] App: add menu model --- src/Backend/App.vala | 203 +++++++++++++++++++++++++++++++++++++ src/Widgets/AppButton.vala | 7 +- 2 files changed, 206 insertions(+), 4 deletions(-) diff --git a/src/Backend/App.vala b/src/Backend/App.vala index 8211010c..3296ed3e 100644 --- a/src/Backend/App.vala +++ b/src/Backend/App.vala @@ -28,6 +28,14 @@ public class Slingshot.Backend.App : Object { SYNAPSE } + public const string ACTION_GROUP_PREFIX = "app-actions"; + private const string ACTION_PREFIX = ACTION_GROUP_PREFIX + "."; + private const string APP_ACTION = "action.%s"; + private const string PINNED_ACTION = "pinned"; + private const string SWITCHEROO_ACTION = "switcheroo"; + private const string UNINSTALL_ACTION = "uninstall"; + private const string VIEW_ACTION = "view-in-appcenter"; + public string name { get; construct set; } public string description { get; private set; default = ""; } public string desktop_id { get; construct set; } @@ -49,6 +57,12 @@ public class Slingshot.Backend.App : Object { public Synapse.Match? target { get; private set; default = null; } private Slingshot.Backend.SwitcherooControl switcheroo_control; + private GLib.SimpleAction pinned_action; + private GLib.SimpleAction uninstall_action; + private GLib.SimpleAction view_action; + + private bool has_system_item = false; + private string appstream_comp_id = ""; construct { switcheroo_control = new Slingshot.Backend.SwitcherooControl (); @@ -174,4 +188,193 @@ public class Slingshot.Backend.App : Object { current_count = 0; } } + + public GLib.Menu get_menu_model (out SimpleActionGroup action_group) { + var actions_section = new GLib.Menu (); + var shell_section = new GLib.Menu (); + + action_group = new SimpleActionGroup (); + + var app_info = new DesktopAppInfo (desktop_id); + foreach (unowned var action in app_info.list_actions ()) { + var simple_action = new SimpleAction (APP_ACTION.printf (action), null); + simple_action.activate.connect (() => { + var context = Gdk.Display.get_default ().get_app_launch_context (); + context.set_timestamp (Gdk.CURRENT_TIME); + + app_info.launch_action (action, context); + launched (this); + }); + action_group.add_action (simple_action); + + actions_section.append ( + app_info.get_action_name (action), + ACTION_PREFIX + APP_ACTION.printf (action) + ); + } + + if (switcheroo_control != null && switcheroo_control.has_dual_gpu) { + bool prefers_non_default_gpu = app_info.get_boolean ("PrefersNonDefaultGPU"); + + var switcheroo_action = new SimpleAction (SWITCHEROO_ACTION, null); + switcheroo_action.activate.connect (() => { + try { + var context = Gdk.Display.get_default ().get_app_launch_context (); + context.set_timestamp (Gdk.CURRENT_TIME); + + switcheroo_control.apply_gpu_environment (context, prefers_non_default_gpu); + + app_info.launch (null, context); + launched (this); + } catch (Error e) { + warning ("Failed to launch %s: %s", name, e.message); + } + }); + action_group.add_action (switcheroo_action); + + actions_section.append ( + _("Open with %s Graphics").printf (switcheroo_control.get_gpu_name (prefers_non_default_gpu)), + ACTION_PREFIX + SWITCHEROO_ACTION + ); + } + + if (Environment.find_program_in_path ("io.elementary.dock") != null) { + has_system_item = true; + + var dock = Backend.Dock.get_default (); + var pinned_variant = new Variant.boolean (false); + try { + pinned_variant = new Variant.boolean (desktop_id in dock.dbus.list_launchers ()); + } catch (GLib.Error e) { + critical (e.message); + } + + pinned_action = new SimpleAction.stateful (PINNED_ACTION, null, pinned_variant); + pinned_action.change_state.connect (pinned_action_change_state); + + action_group.add_action (pinned_action); + + shell_section.append ( + _("Keep in _Dock"), + ACTION_PREFIX + PINNED_ACTION + ); + + dock.notify["dbus"].connect (() => on_dock_dbus_changed (dock)); + on_dock_dbus_changed (dock); + } + + if (Environment.find_program_in_path ("io.elementary.appcenter") != null) { + uninstall_action = new SimpleAction (UNINSTALL_ACTION, null); + uninstall_action.activate.connect (action_uninstall); + + view_action = new SimpleAction (VIEW_ACTION, null); + view_action.activate.connect (open_in_appcenter); + + action_group.add_action (uninstall_action); + action_group.add_action (view_action); + + shell_section.append ( + _("Uninstall"), + ACTION_PREFIX + UNINSTALL_ACTION + ); + + shell_section.append ( + _("View in AppCenter"), + ACTION_PREFIX + VIEW_ACTION + ); + + var appcenter = Backend.AppCenter.get_default (); + appcenter.notify["dbus"].connect (() => on_appcenter_dbus_changed.begin (appcenter)); + on_appcenter_dbus_changed.begin (appcenter); + } + + var model = new GLib.Menu (); + model.append_section (null, actions_section); + model.append_section (null, shell_section); + + return model; + } + + private void action_uninstall () { + var appcenter = Backend.AppCenter.get_default (); + if (appcenter.dbus == null || appstream_comp_id == "") { + return; + } + + launched (this); + + appcenter.dbus.uninstall.begin (appstream_comp_id, (obj, res) => { + try { + appcenter.dbus.uninstall.end (res); + } catch (GLib.Error e) { + warning (e.message); + } + }); + } + + private void open_in_appcenter () { + AppInfo.launch_default_for_uri_async.begin ("appstream://" + appstream_comp_id, null, null, (obj, res) => { + try { + AppInfo.launch_default_for_uri_async.end (res); + } catch (Error error) { + var app_info = new DesktopAppInfo (desktop_id); + var message_dialog = new Granite.MessageDialog.with_image_from_icon_name ( + "Unable to open %s in AppCenter".printf (app_info.get_display_name ()), + "", + "dialog-error", + Gtk.ButtonsType.CLOSE + ); + message_dialog.show_error_details (error.message); + message_dialog.response.connect (message_dialog.destroy); + message_dialog.present (); + } finally { + launched (this); + } + }); + } + + private async void on_appcenter_dbus_changed (Backend.AppCenter appcenter) { + if (appcenter.dbus != null) { + try { + appstream_comp_id = yield appcenter.dbus.get_component_from_desktop_id (desktop_id); + } catch (GLib.Error e) { + appstream_comp_id = ""; + warning (e.message); + } + } else { + appstream_comp_id = ""; + } + + uninstall_action.set_enabled (appstream_comp_id != ""); + view_action.set_enabled (appstream_comp_id != ""); + } + + private void on_dock_dbus_changed (Backend.Dock dock) { + pinned_action.set_enabled (dock.dbus != null); + + if (dock.dbus == null) { + return; + } + + try { + pinned_action.change_state (new Variant.boolean (desktop_id in dock.dbus.list_launchers ())); + } catch (GLib.Error e) { + critical (e.message); + } + } + + private void pinned_action_change_state (Variant? value) { + pinned_action.set_state (value); + + try { + var dock = Backend.Dock.get_default (); + if (value.get_boolean ()) { + dock.dbus.add_launcher (desktop_id); + } else { + dock.dbus.remove_launcher (desktop_id); + } + } catch (GLib.Error e) { + critical (e.message); + } + } } diff --git a/src/Widgets/AppButton.vala b/src/Widgets/AppButton.vala index 5919212a..a5629569 100644 --- a/src/Widgets/AppButton.vala +++ b/src/Widgets/AppButton.vala @@ -76,10 +76,9 @@ public class Slingshot.Widgets.AppButton : Gtk.Button { child = box; - var context_menu = new Slingshot.AppContextMenu (app.desktop_id, app.desktop_path); - context_menu.app_launched.connect (() => { - app_launched (); - }); + SimpleActionGroup? action_group = null; + var context_menu = new Gtk.Menu.from_model (app.get_menu_model (out action_group)); + insert_action_group (Backend.App.ACTION_GROUP_PREFIX, action_group); this.clicked.connect (launch_app); From 7e5c196ed8b14644ecb198d377c6426cef716cc5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Danielle=20For=C3=A9?= Date: Mon, 23 Jun 2025 13:21:53 -0700 Subject: [PATCH 2/2] More owl --- po/POTFILES | 1 - src/Backend/App.vala | 3 +- src/Views/CategoryView.vala | 10 +- src/Views/SearchView.vala | 2 + src/Widgets/AppButton.vala | 11 +- src/Widgets/AppContextMenu.vala | 243 -------------------------------- src/Widgets/AppListRow.vala | 10 +- src/Widgets/SearchItem.vala | 5 +- src/meson.build | 1 - 9 files changed, 25 insertions(+), 261 deletions(-) delete mode 100644 src/Widgets/AppContextMenu.vala diff --git a/po/POTFILES b/po/POTFILES index 75180cde..8c4ad60f 100644 --- a/po/POTFILES +++ b/po/POTFILES @@ -15,7 +15,6 @@ src/Views/GridView.vala src/Views/SearchView.vala src/Widgets/AppButton.vala -src/Widgets/AppContextMenu.vala src/Widgets/PageChecker.vala src/Widgets/SearchItem.vala src/Widgets/Switcher.vala diff --git a/src/Backend/App.vala b/src/Backend/App.vala index 3296ed3e..4ec6f986 100644 --- a/src/Backend/App.vala +++ b/src/Backend/App.vala @@ -36,6 +36,7 @@ public class Slingshot.Backend.App : Object { private const string UNINSTALL_ACTION = "uninstall"; private const string VIEW_ACTION = "view-in-appcenter"; + public SimpleActionGroup action_group { get; private set; } public string name { get; construct set; } public string description { get; private set; default = ""; } public string desktop_id { get; construct set; } @@ -189,7 +190,7 @@ public class Slingshot.Backend.App : Object { } } - public GLib.Menu get_menu_model (out SimpleActionGroup action_group) { + public GLib.Menu get_menu_model () { var actions_section = new GLib.Menu (); var shell_section = new GLib.Menu (); diff --git a/src/Views/CategoryView.vala b/src/Views/CategoryView.vala index ba80cca7..445be0b8 100644 --- a/src/Views/CategoryView.vala +++ b/src/Views/CategoryView.vala @@ -169,12 +169,10 @@ public class Slingshot.Widgets.CategoryView : Gtk.EventBox { private Gtk.Menu create_context_menu () { var selected_row = (AppListRow) listbox.get_selected_row (); - var menu = new Slingshot.AppContextMenu (selected_row.app_id, selected_row.desktop_path); - menu.app_launched.connect (() => { - view.close_indicator (); - }); + var context_menu = new Gtk.Menu.from_model (selected_row.app.get_menu_model ()); + context_menu.insert_action_group (Backend.App.ACTION_GROUP_PREFIX, selected_row.app.action_group); - return menu; + return context_menu; } public void page_down () { @@ -204,7 +202,7 @@ public class Slingshot.Widgets.CategoryView : Gtk.EventBox { listbox.foreach ((app_list_row) => listbox.remove (app_list_row)); foreach (unowned Backend.App app in view.app_system.get_apps_by_name ()) { - listbox.add (new AppListRow (app.desktop_id, app.desktop_path)); + listbox.add (new AppListRow (app)); } listbox.show_all (); diff --git a/src/Views/SearchView.vala b/src/Views/SearchView.vala index 513bb82c..7fa740e5 100644 --- a/src/Views/SearchView.vala +++ b/src/Views/SearchView.vala @@ -282,6 +282,8 @@ public class Slingshot.Widgets.SearchView : Gtk.ScrolledWindow { var search_item = new SearchItem (app, search_term, result_type); app.start_search.connect ((search, target) => start_search (search, target)); + app.launched.connect (() => app_launched ()); + list_box.add (search_item); search_item.show_all (); } diff --git a/src/Widgets/AppButton.vala b/src/Widgets/AppButton.vala index a5629569..8c1ee6a6 100644 --- a/src/Widgets/AppButton.vala +++ b/src/Widgets/AppButton.vala @@ -76,9 +76,7 @@ public class Slingshot.Widgets.AppButton : Gtk.Button { child = box; - SimpleActionGroup? action_group = null; - var context_menu = new Gtk.Menu.from_model (app.get_menu_model (out action_group)); - insert_action_group (Backend.App.ACTION_GROUP_PREFIX, action_group); + app.launched.connect (() => app_launched ()); this.clicked.connect (launch_app); @@ -91,6 +89,8 @@ public class Slingshot.Widgets.AppButton : Gtk.Button { var event = click_controller.get_last_event (sequence); if (event.triggers_context_menu ()) { + var context_menu = new Gtk.Menu.from_model (app.get_menu_model ()); + context_menu.insert_action_group (Backend.App.ACTION_GROUP_PREFIX, app.action_group); context_menu.popup_at_pointer (); click_controller.set_state (CLAIMED); @@ -104,11 +104,15 @@ public class Slingshot.Widgets.AppButton : Gtk.Button { switch (keyval) { case Gdk.Key.F10: if (mods == Gdk.ModifierType.SHIFT_MASK) { + var context_menu = new Gtk.Menu.from_model (app.get_menu_model ()); + context_menu.insert_action_group (Backend.App.ACTION_GROUP_PREFIX, app.action_group); context_menu.popup_at_widget (this, EAST, CENTER); } break; case Gdk.Key.Menu: case Gdk.Key.MenuKB: + var context_menu = new Gtk.Menu.from_model (app.get_menu_model ()); + context_menu.insert_action_group (Backend.App.ACTION_GROUP_PREFIX, app.action_group); context_menu.popup_at_widget (this, EAST, CENTER); break; default: @@ -140,7 +144,6 @@ public class Slingshot.Widgets.AppButton : Gtk.Button { public void launch_app () { app.launch (); - app_launched (); } private void update_badge_count () { diff --git a/src/Widgets/AppContextMenu.vala b/src/Widgets/AppContextMenu.vala deleted file mode 100644 index 4dbbdc94..00000000 --- a/src/Widgets/AppContextMenu.vala +++ /dev/null @@ -1,243 +0,0 @@ -/* - * Copyright 2019-2025 elementary, Inc. (https://elementary.io) - * Copyright 2020-2021 Justin Haygood - * - * 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 3 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, see . - */ - -public class Slingshot.AppContextMenu : Gtk.Menu { - public signal void app_launched (); - - private const string ACTION_GROUP_PREFIX = "app-actions"; - private const string ACTION_PREFIX = ACTION_GROUP_PREFIX + "."; - private const string APP_ACTION = "action.%s"; - private const string PINNED_ACTION = "pinned"; - private const string SWITCHEROO_ACTION = "switcheroo"; - private const string UNINSTALL_ACTION = "uninstall"; - private const string VIEW_ACTION = "view-in-appcenter"; - - public string desktop_id { get; construct; } - public string desktop_path { get; construct; } - private DesktopAppInfo app_info; - - private bool has_system_item = false; - private string appstream_comp_id = ""; - - private Slingshot.Backend.SwitcherooControl switcheroo_control; - private GLib.SimpleAction pinned_action; - private GLib.SimpleAction uninstall_action; - private GLib.SimpleAction view_action; - - public AppContextMenu (string desktop_id, string desktop_path) { - Object ( - desktop_id: desktop_id, - desktop_path: desktop_path - ); - } - - construct { - var action_group = new SimpleActionGroup (); - insert_action_group (ACTION_GROUP_PREFIX, action_group); - - app_info = new DesktopAppInfo (desktop_id); - foreach (unowned var action in app_info.list_actions ()) { - var simple_action = new SimpleAction (APP_ACTION.printf (action), null); - simple_action.activate.connect (() => { - var context = Gdk.Display.get_default ().get_app_launch_context (); - context.set_timestamp (Gdk.CURRENT_TIME); - - app_info.launch_action (action, context); - app_launched (); - }); - action_group.add_action (simple_action); - - var menuitem = new Gtk.MenuItem.with_mnemonic (app_info.get_action_name (action)); - menuitem.set_detailed_action_name (ACTION_PREFIX + APP_ACTION.printf (action)); - - add (menuitem); - } - - switcheroo_control = new Slingshot.Backend.SwitcherooControl (); - if (switcheroo_control != null && switcheroo_control.has_dual_gpu) { - bool prefers_non_default_gpu = app_info.get_boolean ("PrefersNonDefaultGPU"); - - var switcheroo_action = new SimpleAction (SWITCHEROO_ACTION, null); - switcheroo_action.activate.connect (() => { - try { - var context = Gdk.Display.get_default ().get_app_launch_context (); - context.set_timestamp (Gdk.CURRENT_TIME); - - switcheroo_control.apply_gpu_environment (context, prefers_non_default_gpu); - - app_info.launch (null, context); - app_launched (); - } catch (Error e) { - warning ("Failed to launch %s: %s", name, e.message); - } - }); - action_group.add_action (switcheroo_action); - - var menu_item = new Gtk.MenuItem.with_mnemonic ( - _("Open with %s Graphics").printf (switcheroo_control.get_gpu_name (prefers_non_default_gpu)) - ); - menu_item.set_detailed_action_name (ACTION_PREFIX + SWITCHEROO_ACTION); - - add (menu_item); - } - - if (Environment.find_program_in_path ("io.elementary.dock") != null) { - if (get_children ().length () > 0) { - add (new Gtk.SeparatorMenuItem ()); - } - - has_system_item = true; - - var dock = Backend.Dock.get_default (); - var pinned_variant = new Variant.boolean (false); - try { - pinned_variant = new Variant.boolean (desktop_id in dock.dbus.list_launchers ()); - } catch (GLib.Error e) { - critical (e.message); - } - - pinned_action = new SimpleAction.stateful (PINNED_ACTION, null, pinned_variant); - pinned_action.change_state.connect (pinned_action_change_state); - - action_group.add_action (pinned_action); - - var menuitem = new Gtk.CheckMenuItem () { - label = _("Keep in _Dock"), - use_underline = true - }; - menuitem.set_detailed_action_name (ACTION_PREFIX + PINNED_ACTION); - - add (menuitem); - - dock.notify["dbus"].connect (() => on_dock_dbus_changed (dock)); - on_dock_dbus_changed (dock); - } - - if (Environment.find_program_in_path ("io.elementary.appcenter") != null) { - if (!has_system_item && get_children ().length () > 0) { - add (new Gtk.SeparatorMenuItem ()); - } - - uninstall_action = new SimpleAction (UNINSTALL_ACTION, null); - uninstall_action.activate.connect (action_uninstall); - - view_action = new SimpleAction (VIEW_ACTION, null); - view_action.activate.connect (open_in_appcenter); - - action_group.add_action (uninstall_action); - action_group.add_action (view_action); - - var uninstall_menuitem = new Gtk.MenuItem.with_label (_("Uninstall")); - uninstall_menuitem.set_detailed_action_name (ACTION_PREFIX + UNINSTALL_ACTION); - - var appcenter_menuitem = new Gtk.MenuItem.with_label (_("View in AppCenter")); - appcenter_menuitem.set_detailed_action_name (ACTION_PREFIX + VIEW_ACTION); - - add (uninstall_menuitem); - add (appcenter_menuitem); - - var appcenter = Backend.AppCenter.get_default (); - appcenter.notify["dbus"].connect (() => on_appcenter_dbus_changed.begin (appcenter)); - on_appcenter_dbus_changed.begin (appcenter); - } - - show_all (); - } - - private void action_uninstall () { - var appcenter = Backend.AppCenter.get_default (); - if (appcenter.dbus == null || appstream_comp_id == "") { - return; - } - - app_launched (); - - appcenter.dbus.uninstall.begin (appstream_comp_id, (obj, res) => { - try { - appcenter.dbus.uninstall.end (res); - } catch (GLib.Error e) { - warning (e.message); - } - }); - } - - private void open_in_appcenter () { - AppInfo.launch_default_for_uri_async.begin ("appstream://" + appstream_comp_id, null, null, (obj, res) => { - try { - AppInfo.launch_default_for_uri_async.end (res); - } catch (Error error) { - var message_dialog = new Granite.MessageDialog.with_image_from_icon_name ( - "Unable to open %s in AppCenter".printf (app_info.get_display_name ()), - "", - "dialog-error", - Gtk.ButtonsType.CLOSE - ); - message_dialog.show_error_details (error.message); - message_dialog.response.connect (message_dialog.destroy); - message_dialog.present (); - } finally { - app_launched (); - } - }); - } - - private async void on_appcenter_dbus_changed (Backend.AppCenter appcenter) { - if (appcenter.dbus != null) { - try { - appstream_comp_id = yield appcenter.dbus.get_component_from_desktop_id (desktop_id); - } catch (GLib.Error e) { - appstream_comp_id = ""; - warning (e.message); - } - } else { - appstream_comp_id = ""; - } - - uninstall_action.set_enabled (appstream_comp_id != ""); - view_action.set_enabled (appstream_comp_id != ""); - } - - private void on_dock_dbus_changed (Backend.Dock dock) { - pinned_action.set_enabled (dock.dbus != null); - - if (dock.dbus == null) { - return; - } - - try { - pinned_action.change_state (new Variant.boolean (desktop_id in dock.dbus.list_launchers ())); - } catch (GLib.Error e) { - critical (e.message); - } - } - - private void pinned_action_change_state (Variant? value) { - pinned_action.set_state (value); - - try { - var dock = Backend.Dock.get_default (); - if (value.get_boolean ()) { - dock.dbus.add_launcher (desktop_id); - } else { - dock.dbus.remove_launcher (desktop_id); - } - } catch (GLib.Error e) { - critical (e.message); - } - } -} diff --git a/src/Widgets/AppListRow.vala b/src/Widgets/AppListRow.vala index b31c03b9..cb71fe71 100644 --- a/src/Widgets/AppListRow.vala +++ b/src/Widgets/AppListRow.vala @@ -3,15 +3,17 @@ * SPDX-FileCopyrightText: 2019-2025 elementary, Inc. (https://elementary.io) */ -public class AppListRow : Gtk.ListBoxRow { +public class Slingshot.AppListRow : Gtk.ListBoxRow { + public Backend.App app { get; construct; } public string app_id { get; construct; } public string desktop_path { get; construct; } public GLib.DesktopAppInfo app_info { get; private set; } - public AppListRow (string app_id, string desktop_path) { + public AppListRow (Backend.App app) { Object ( - app_id: app_id, - desktop_path: desktop_path + app: app, + app_id: app.desktop_id, + desktop_path: app.desktop_path ); } diff --git a/src/Widgets/SearchItem.vala b/src/Widgets/SearchItem.vala index 566093aa..889b0a32 100644 --- a/src/Widgets/SearchItem.vala +++ b/src/Widgets/SearchItem.vala @@ -138,6 +138,9 @@ public class Slingshot.Widgets.SearchItem : Gtk.ListBoxRow { return null; } - return new Slingshot.AppContextMenu (app.desktop_id, app.desktop_path); + var context_menu = new Gtk.Menu.from_model (app.get_menu_model ()); + context_menu.insert_action_group (Backend.App.ACTION_GROUP_PREFIX, app.action_group); + + return context_menu; } } diff --git a/src/meson.build b/src/meson.build index fd486110..d187e937 100644 --- a/src/meson.build +++ b/src/meson.build @@ -25,7 +25,6 @@ sources = [ 'Views/SearchView.vala', 'Widgets/AppButton.vala', - 'Widgets/AppContextMenu.vala', 'Widgets/AppListRow.vala', 'Widgets/Switcher.vala', 'Widgets/SearchItem.vala',