diff --git a/data/dock.metainfo.xml.in b/data/dock.metainfo.xml.in index 385784e1..a8fe6734 100644 --- a/data/dock.metainfo.xml.in +++ b/data/dock.metainfo.xml.in @@ -29,6 +29,8 @@

Updated translations

+ Uninstall Menu Item + Add View in AppCenter menu item Give workspaces an activate state/animation diff --git a/src/AppSystem/App.vala b/src/AppSystem/App.vala index 5ebadc82..5ad6b65f 100644 --- a/src/AppSystem/App.vala +++ b/src/AppSystem/App.vala @@ -5,7 +5,10 @@ public class Dock.App : Object { public const string ACTION_GROUP_PREFIX = "app-actions"; - private const string ACTION_PREFIX = ACTION_GROUP_PREFIX + "."; + public const string ACTION_PREFIX = ACTION_GROUP_PREFIX + "."; + public const string UNINSTALL_ACTION = "uninstall"; + public const string VIEW_ACTION = "view-in-appcenter"; + private const string SWITCHEROO_ACTION = "switcheroo"; private const string APP_ACTION = "action.%s"; @@ -51,6 +54,10 @@ public class Dock.App : Object { public GLib.GenericArray windows { get; private owned set; } // Ordered by stacking order with topmost at 0 private static Dock.SwitcherooControl switcheroo_control; + private GLib.SimpleAction uninstall_action; + private GLib.SimpleAction view_action; + + private string appstream_comp_id = ""; public App (GLib.DesktopAppInfo app_info, bool pinned) { Object (app_info: app_info, pinned: pinned); @@ -101,6 +108,21 @@ public class Dock.App : Object { app_action_group.add_action (simple_action); } + 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); + + app_action_group.add_action (uninstall_action); + app_action_group.add_action (view_action); + + var appcenter = Dock.AppCenter.get_default (); + appcenter.notify["dbus"].connect (() => on_appcenter_dbus_changed.begin (appcenter)); + on_appcenter_dbus_changed.begin (appcenter); + } + notify["pinned"].connect (() => { check_remove (); ItemManager.get_default ().sync_pinned (); @@ -269,4 +291,53 @@ public class Dock.App : Object { return Source.REMOVE; }); } + + private void action_uninstall () { + var appcenter = Dock.AppCenter.get_default (); + if (appcenter.dbus == null || appstream_comp_id == "") { + return; + } + + 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 (); + } + }); + } + + private async void on_appcenter_dbus_changed (Dock.AppCenter appcenter) { + if (appcenter.dbus != null) { + try { + appstream_comp_id = yield appcenter.dbus.get_component_from_desktop_id (app_info.get_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 != ""); + } } diff --git a/src/AppSystem/Launcher.vala b/src/AppSystem/Launcher.vala index 4ecef3b7..62ca0855 100644 --- a/src/AppSystem/Launcher.vala +++ b/src/AppSystem/Launcher.vala @@ -76,12 +76,17 @@ public class Dock.Launcher : BaseItem { insert_action_group (App.ACTION_GROUP_PREFIX, app.app_action_group); - var pinned_section = new Menu (); - pinned_section.append (_("Keep in Dock"), ACTION_PREFIX + PINNED_ACTION); + var shell_section = new Menu (); + shell_section.append (_("Keep in Dock"), ACTION_PREFIX + PINNED_ACTION); + + if (Environment.find_program_in_path ("io.elementary.appcenter") != null) { + shell_section.append (_("Uninstall"), App.ACTION_PREFIX + App.UNINSTALL_ACTION); + shell_section.append (_("View in AppCenter"), App.ACTION_PREFIX + App.VIEW_ACTION); + } var menu = new Menu (); menu.append_section (null, app.app_action_menu); - menu.append_section (null, pinned_section); + menu.append_section (null, shell_section); popover_menu = new Gtk.PopoverMenu.from_model (menu) { autohide = true, diff --git a/src/DBus/AppCenter.vala b/src/DBus/AppCenter.vala new file mode 100644 index 00000000..b3788de0 --- /dev/null +++ b/src/DBus/AppCenter.vala @@ -0,0 +1,57 @@ +/* + * SPDX-License-Identifier: GPL-3.0 + * SPDX-FileCopyrightText: 2026 elementary, Inc. (https://elementary.io) + */ + +[DBus (name = "io.elementary.appcenter")] +public interface AppCenterDBus : Object { + public abstract async void install (string component_id) throws GLib.Error; + public abstract async void update (string component_id) throws GLib.Error; + public abstract async void uninstall (string component_id) throws GLib.Error; + public abstract async string get_component_from_desktop_id (string desktop_id) throws GLib.Error; + public abstract async string[] search_components (string query) throws GLib.Error; +} + +public class Dock.AppCenter : Object { + private const string DBUS_NAME = "io.elementary.appcenter"; + private const string DBUS_PATH = "/io/elementary/appcenter"; + private const uint RECONNECT_TIMEOUT = 5000U; + + private static AppCenter? instance; + public static unowned AppCenter get_default () { + if (instance == null) { + instance = new AppCenter (); + } + + return instance; + } + + public AppCenterDBus? dbus { public get; private set; default = null; } + + construct { + Bus.watch_name (BusType.SESSION, DBUS_NAME, BusNameWatcherFlags.AUTO_START, + () => try_connect (), name_vanished_callback); + } + + private AppCenter () { + + } + + private void try_connect () { + Bus.get_proxy.begin (BusType.SESSION, DBUS_NAME, DBUS_PATH, 0, null, (obj, res) => { + try { + dbus = Bus.get_proxy.end (res); + } catch (Error e) { + warning (e.message); + Timeout.add (RECONNECT_TIMEOUT, () => { + try_connect (); + return false; + }); + } + }); + } + + private void name_vanished_callback (DBusConnection connection, string name) { + dbus = null; + } +} diff --git a/src/meson.build b/src/meson.build index 1b84b18f..b6a8f052 100644 --- a/src/meson.build +++ b/src/meson.build @@ -15,6 +15,7 @@ sources = [ 'AppSystem' / 'Background' / 'BackgroundAppRow.vala', 'AppSystem' / 'Background' / 'BackgroundItem.vala', 'AppSystem' / 'Background' / 'BackgroundMonitor.vala', + 'DBus' / 'AppCenter.vala', 'DBus' / 'GalaDBus.vala', 'DBus' / 'ItemInterface.vala', 'DBus' / 'ShellKeyGrabber.vala',