diff --git a/data/applications.gresource.xml b/data/applications.gresource.xml
new file mode 100644
index 00000000..067e48d6
--- /dev/null
+++ b/data/applications.gresource.xml
@@ -0,0 +1,6 @@
+
+
+
+ background.svg
+
+
diff --git a/data/background.svg b/data/background.svg
new file mode 100644
index 00000000..3c2b63b9
--- /dev/null
+++ b/data/background.svg
@@ -0,0 +1,176 @@
+
+
diff --git a/data/meson.build b/data/meson.build
index be9909aa..980b974b 100644
--- a/data/meson.build
+++ b/data/meson.build
@@ -1,3 +1,10 @@
+gresource = gnome.compile_resources(
+ 'applications-resources',
+ 'applications.gresource.xml',
+ source_dir: meson.current_source_dir()
+)
+
+
i18n.merge_file(
input: 'applications.metainfo.xml.in',
output: 'io.elementary.settings.applications.metainfo.xml',
diff --git a/src/Permissions/Backend/App.vala b/src/Permissions/Backend/App.vala
index 51bd1cf8..85d0f31b 100644
--- a/src/Permissions/Backend/App.vala
+++ b/src/Permissions/Backend/App.vala
@@ -25,6 +25,7 @@ public class Permissions.Backend.App : GLib.Object {
public Flatpak.InstalledRef installed_ref { get; construct; }
public string id { get; private set; }
public string name { get; private set; }
+ public Icon icon { get; private set; }
public GenericArray settings;
private const string GROUP = "Context";
@@ -35,7 +36,15 @@ public class Permissions.Backend.App : GLib.Object {
construct {
id = installed_ref.get_name ();
- name = installed_ref.get_appdata_name () ?? id;
+
+ var appinfo = new GLib.DesktopAppInfo (id + ".desktop");
+ if (appinfo != null) {
+ name = appinfo.get_name ();
+ icon = appinfo.get_icon () ?? new ThemedIcon ("application-default-icon");
+ } else {
+ icon = new ThemedIcon ("application-default-icon");
+ name = id;
+ }
settings = new GenericArray ();
diff --git a/src/Permissions/Backend/PermissionStore.vala b/src/Permissions/Backend/PermissionStore.vala
new file mode 100644
index 00000000..d32b24b4
--- /dev/null
+++ b/src/Permissions/Backend/PermissionStore.vala
@@ -0,0 +1,58 @@
+/*
+ * SPDX-License-Identifier: LGPL-3.0-or-later
+ * SPDX-FileCopyrightText: 2023 elementary, Inc. (https://elementary.io)
+ */
+
+[DBus (name = "org.freedesktop.impl.portal.PermissionStore", timeout = 120000)]
+public interface PermissionStoreDBus : GLib.Object {
+ public signal void changed (string table, string id, bool deleted, GLib.Variant data, [DBus (signature = "a{sas}")] GLib.Variant permissions);
+ public abstract async void set_permission (string table, bool create, string id, string app, string[] permissions) throws DBusError, IOError;
+ public abstract async string[] get_permission (string table, string id, string app) throws DBusError, IOError;
+}
+
+public class Permissions.PermissionStore : GLib.Object {
+ public signal void changed ();
+
+ private const string DBUS_NAME = "org.freedesktop.impl.portal.PermissionStore";
+ private const string DBUS_PATH = "/org/freedesktop/impl/portal/PermissionStore";
+ private const uint RECONNECT_TIMEOUT = 5000U;
+
+ private static PermissionStore? instance;
+ public static unowned PermissionStore get_default () {
+ if (instance == null) {
+ instance = new PermissionStore ();
+ }
+
+ return instance;
+ }
+
+ public PermissionStoreDBus dbus { get; private set; default = null; }
+
+ construct {
+ Bus.watch_name (
+ BusType.SESSION, DBUS_NAME, AUTO_START,
+ () => try_connect (), name_vanished_callback
+ );
+ }
+
+ private PermissionStore () { }
+
+ private void try_connect () {
+ Bus.get_proxy.begin (SESSION, DBUS_NAME, DBUS_PATH, 0, null, (obj, res) => {
+ try {
+ dbus = Bus.get_proxy.end (res);
+ dbus.changed.connect (() => changed ());
+ } catch (Error e) {
+ critical (e.message);
+ Timeout.add (RECONNECT_TIMEOUT, () => {
+ try_connect ();
+ return GLib.Source.REMOVE;
+ });
+ }
+ });
+ }
+
+ private void name_vanished_callback (DBusConnection connection, string name) {
+ dbus = null;
+ }
+}
diff --git a/src/Permissions/PermissionsPlug.vala b/src/Permissions/PermissionsPlug.vala
index d89abc39..c7eafad3 100644
--- a/src/Permissions/PermissionsPlug.vala
+++ b/src/Permissions/PermissionsPlug.vala
@@ -73,7 +73,10 @@ public class Permissions.Plug : Gtk.Box {
child = scrolled_window
};
- var sidebar = new Gtk.Box (VERTICAL, 12);
+ var sidebar = new Gtk.Box (VERTICAL, 12) {
+ margin_bottom = 12,
+ margin_start = 12
+ };
sidebar.append (search_entry);
sidebar.append (frame);
@@ -94,12 +97,7 @@ public class Permissions.Plug : Gtk.Box {
show_row (row);
}
- var grid = new Gtk.Grid () {
- margin_end = 12,
- margin_bottom = 12,
- margin_start = 12,
- column_spacing = 12
- };
+ var grid = new Gtk.Grid ();
grid.attach (sidebar, 0, 0, 1, 1);
grid.attach (app_settings_view, 1, 0, 2, 1);
diff --git a/src/Permissions/Widgets/AppSettingsView.vala b/src/Permissions/Widgets/AppSettingsView.vala
index 0e8fdc16..f77bedf6 100644
--- a/src/Permissions/Widgets/AppSettingsView.vala
+++ b/src/Permissions/Widgets/AppSettingsView.vala
@@ -1,5 +1,5 @@
/*
-* Copyright 2020 elementary, Inc. (https://elementary.io)
+* Copyright 2020-2024 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
@@ -19,15 +19,76 @@
* Authored by: Marius Meisenzahl
*/
-public class Permissions.Widgets.AppSettingsView : Gtk.Grid {
+public class Permissions.Widgets.AppSettingsView : Switchboard.SettingsPage {
public Backend.App? selected_app { get; set; default = null; }
- private Gtk.ListBox list_box;
+ private const string BACKGROUND_TABLE = "background";
+ private const string BACKGROUND_ID = "background";
+
+ private Gtk.ListBox sandbox_box;
+ private Gtk.ListBox permission_box;
private Gtk.Button reset_button;
+ private Gtk.Switch background_switch;
+ private Gtk.LinkButton notification_button;
construct {
notify["selected-app"].connect (update_view);
+ var background_image = new Gtk.Image.from_icon_name ("permissions-background") {
+ icon_size = LARGE
+ };
+
+ var background_label = new Gtk.Label (_("Background Activity")) {
+ hexpand = true,
+ xalign = 0
+ };
+
+ var background_description = new Gtk.Label (_("Perform tasks and use system resources while its window is closed.")) {
+ xalign = 0,
+ wrap = true
+ };
+ background_description.add_css_class (Granite.STYLE_CLASS_DIM_LABEL);
+ background_description.add_css_class (Granite.STYLE_CLASS_SMALL_LABEL);
+
+ background_switch = new Gtk.Switch () {
+ valign = CENTER
+ };
+
+ var background_grid = new Gtk.Grid () {
+ column_spacing = 6
+ };
+ background_grid.attach (background_image, 0, 0, 1, 2);
+ background_grid.attach (background_label, 1, 0);
+ background_grid.attach (background_description, 1, 1);
+ background_grid.attach (background_switch, 2, 0, 1, 2);
+
+ var notification_icon = new Gtk.Image.from_icon_name ("preferences-system-notifications") {
+ icon_size = LARGE
+ };
+
+ var notification_label = new Gtk.Label (_("Notifications")) {
+ hexpand = true,
+ xalign = 0
+ };
+
+ notification_button = new Gtk.LinkButton ("settings://notifications") {
+ icon_name = "view-more-horizontal-symbolic"
+ };
+
+ var notification_box = new Gtk.Box (HORIZONTAL, 6);
+ notification_box.append (notification_icon);
+ notification_box.append (notification_label);
+ notification_box.append (notification_button);
+
+ permission_box = new Gtk.ListBox () {
+ hexpand = true,
+ selection_mode = NONE
+ };
+ permission_box.add_css_class ("boxed-list");
+ permission_box.add_css_class (Granite.STYLE_CLASS_RICH_LIST);
+ permission_box.append (background_grid);
+ permission_box.append (notification_box);
+
var homefolder_widget = new PermissionSettingsWidget (
Plug.permission_names["filesystems=home"],
_("Access your entire Home folder, including any hidden folders."),
@@ -84,36 +145,29 @@ public class Permissions.Widgets.AppSettingsView : Gtk.Grid {
new Backend.PermissionSettings ("devices=dri")
);
- list_box = new Gtk.ListBox () {
+ sandbox_box = new Gtk.ListBox () {
hexpand = true,
- vexpand = true
- };
- list_box.add_css_class (Granite.STYLE_CLASS_RICH_LIST);
- list_box.append (homefolder_widget);
- list_box.append (sysfolders_widget);
- list_box.append (devices_widget);
- list_box.append (network_widget);
- list_box.append (bluetooth_widget);
- list_box.append (printing_widget);
- list_box.append (ssh_widget);
- list_box.append (gpu_widget);
-
- var scrolled_window = new Gtk.ScrolledWindow () {
- child = list_box
+ vexpand = true,
+ selection_mode = NONE
};
+ sandbox_box.add_css_class ("boxed-list");
+ sandbox_box.add_css_class (Granite.STYLE_CLASS_RICH_LIST);
+ sandbox_box.append (homefolder_widget);
+ sandbox_box.append (sysfolders_widget);
+ sandbox_box.append (devices_widget);
+ sandbox_box.append (network_widget);
+ sandbox_box.append (bluetooth_widget);
+ sandbox_box.append (printing_widget);
+ sandbox_box.append (ssh_widget);
+ sandbox_box.append (gpu_widget);
- var frame = new Gtk.Frame (null) {
- child = scrolled_window
- };
- frame.add_css_class (Granite.STYLE_CLASS_VIEW);
+ var box = new Gtk.Box (VERTICAL, 24);
+ box.append (permission_box);
+ box.append (sandbox_box);
- reset_button = new Gtk.Button.with_label (_("Reset to Defaults")) {
- halign = Gtk.Align.END
- };
+ child = box;
- row_spacing = 24;
- attach (frame, 0, 0);
- attach (reset_button, 0, 1);
+ reset_button = add_button (_("Reset to Defaults"));
update_view ();
@@ -126,6 +180,32 @@ public class Permissions.Widgets.AppSettingsView : Gtk.Grid {
ssh_widget.changed_permission_settings.connect (change_permission_settings);
gpu_widget.changed_permission_settings.connect (change_permission_settings);
+ background_switch.notify["active"].connect (() => {
+ string[] permissions;
+ if (background_switch.active) {
+ permissions += "yes";
+ } else {
+ permissions += "no";
+ }
+
+ try {
+ PermissionStore.get_default ().dbus.set_permission.begin (BACKGROUND_TABLE, true, BACKGROUND_ID, selected_app.id, permissions);
+ } catch (Error e) {
+ critical (e.message);
+ var dialog = new Granite.MessageDialog (
+ _("Couldn't set background activity permission"),
+ e.message,
+ new ThemedIcon ("preferences-system")
+ ) {
+ badge_icon = new ThemedIcon ("dialog-error"),
+ modal = true,
+ transient_for = (Gtk.Window) get_root ()
+ };
+ dialog.present ();
+ dialog.response.connect (dialog.destroy);
+ }
+ });
+
reset_button.clicked.connect (() => {
if (selected_app != null) {
selected_app.reset_settings_to_standard ();
@@ -135,7 +215,7 @@ public class Permissions.Widgets.AppSettingsView : Gtk.Grid {
}
private void initialize_settings_view () {
- var children = list_box.observe_children ();
+ var children = sandbox_box.observe_children ();
for (var iter = 0; iter < children.get_n_items (); iter++) {
if (children.get_item (iter) is PermissionSettingsWidget) {
var widget = (PermissionSettingsWidget) children.get_item (iter);
@@ -151,14 +231,13 @@ public class Permissions.Widgets.AppSettingsView : Gtk.Grid {
initialize_settings_view ();
if (selected_app == null) {
- list_box.sensitive = false;
- reset_button.sensitive = false;
+ sensitive = false;
return;
}
var should_enable_reset = false;
selected_app.settings.foreach ((settings) => {
- var children = list_box.observe_children ();
+ var children = sandbox_box.observe_children ();
for (var iter = 0; iter < children.get_n_items (); iter++) {
if (children.get_item (iter) is PermissionSettingsWidget) {
var widget = (PermissionSettingsWidget) children.get_item (iter);
@@ -175,11 +254,53 @@ public class Permissions.Widgets.AppSettingsView : Gtk.Grid {
}
}
- list_box.sensitive = true;
+ sensitive = true;
reset_button.sensitive = should_enable_reset;
});
+ update_permissions ();
+ var permission_store = PermissionStore.get_default ();
+ permission_store.notify["dbus"].connect (update_permissions);
+ permission_store.changed.connect (update_permissions);
+
+ notification_button.uri = "settings://notifications/%s".printf (selected_app.id);
+
update_property (Gtk.AccessibleProperty.LABEL, _("%s permissions").printf (selected_app.name), -1);
+ title = selected_app.name;
+ icon = selected_app.icon;
+ }
+
+ private void update_permissions () {
+ var permission_store = PermissionStore.get_default ();
+ if (permission_store.dbus == null) {
+ permission_box.sensitive = false;
+ return;
+ }
+
+ permission_box.sensitive = true;
+
+ permission_store.dbus.get_permission.begin (BACKGROUND_TABLE, BACKGROUND_ID, selected_app.id, (obj, res) => {
+ try {
+ var background_permission = permission_store.dbus.get_permission.end (res);
+
+ // A lack of explicit permission is considered permission
+ // to allow pre-emptive opt-out
+ background_switch.active = background_permission[0] != "no";
+ } catch (Error e) {
+ critical (e.message);
+ var dialog = new Granite.MessageDialog (
+ _("Couldn't get background activity permission"),
+ e.message,
+ new ThemedIcon ("preferences-system")
+ ) {
+ badge_icon = new ThemedIcon ("dialog-error"),
+ modal = true,
+ transient_for = (Gtk.Window) get_root ()
+ };
+ dialog.present ();
+ dialog.response.connect (dialog.destroy);
+ }
+ });
}
private void change_permission_settings (Backend.PermissionSettings settings) {
diff --git a/src/Permissions/Widgets/PermissionSettingsWidget.vala b/src/Permissions/Widgets/PermissionSettingsWidget.vala
index 63e58d22..b31b0071 100644
--- a/src/Permissions/Widgets/PermissionSettingsWidget.vala
+++ b/src/Permissions/Widgets/PermissionSettingsWidget.vala
@@ -40,7 +40,7 @@ public class Permissions.Widgets.PermissionSettingsWidget : Gtk.ListBoxRow {
construct {
var icon = new Gtk.Image.from_icon_name (icon_name) {
- pixel_size = 32,
+ icon_size = LARGE,
tooltip_text = settings.context
};
@@ -48,19 +48,20 @@ public class Permissions.Widgets.PermissionSettingsWidget : Gtk.ListBoxRow {
halign = Gtk.Align.START,
hexpand = true
};
- name_label.add_css_class (Granite.STYLE_CLASS_H3_LABEL);
var description_label = new Gtk.Label (description) {
wrap = true,
xalign = 0
};
+ description_label.add_css_class (Granite.STYLE_CLASS_DIM_LABEL);
+ description_label.add_css_class (Granite.STYLE_CLASS_SMALL_LABEL);
var allow_switch = new Gtk.Switch () {
valign = Gtk.Align.CENTER
};
var grid = new Gtk.Grid () {
- column_spacing = 12
+ column_spacing = 6
};
grid.attach (icon, 0, 0, 1, 2);
grid.attach (name_label, 1, 0);
diff --git a/src/Permissions/Widgets/SidebarRow.vala b/src/Permissions/Widgets/SidebarRow.vala
index 134f4c3a..5aa183d2 100644
--- a/src/Permissions/Widgets/SidebarRow.vala
+++ b/src/Permissions/Widgets/SidebarRow.vala
@@ -30,16 +30,10 @@ public class Permissions.SidebarRow : Gtk.ListBoxRow {
construct {
hexpand = true;
- var appinfo = new GLib.DesktopAppInfo (app.id + ".desktop");
- Gtk.Image image;
- if (appinfo != null && appinfo.get_icon () != null) {
- image = new Gtk.Image.from_gicon (appinfo.get_icon ());
- } else {
- image = new Gtk.Image.from_icon_name ("application-default-icon");
- }
-
- image.pixel_size = 32;
+ var image = new Gtk.Image.from_gicon (app.icon) {
+ pixel_size = 32
+ };
var title_label = new Gtk.Label (app.name) {
ellipsize = Pango.EllipsizeMode.END,
diff --git a/src/meson.build b/src/meson.build
index 2a5f6e5e..5a7cedef 100644
--- a/src/meson.build
+++ b/src/meson.build
@@ -16,6 +16,7 @@ plug_files = files(
'Permissions/Backend/AppManager.vala',
'Permissions/Backend/FlatpakManager.vala',
'Permissions/Backend/PermissionSettings.vala',
+ 'Permissions/Backend/PermissionStore.vala',
'Permissions/Widgets/AppSettingsView.vala',
'Permissions/Widgets/PermissionSettingsWidget.vala',
'Permissions/Widgets/SidebarRow.vala',
@@ -27,6 +28,7 @@ switchboard_plugsdir = switchboard_dep.get_pkgconfig_variable('plugsdir', define
shared_module(
meson.project_name(),
+ gresource,
plug_files,
conf_file,
dependencies: [