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..d2c22809 100644
--- a/data/meson.build
+++ b/data/meson.build
@@ -1,3 +1,9 @@
+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/PermissionStore.vala b/src/Permissions/Backend/PermissionStore.vala
new file mode 100644
index 00000000..700ddcf6
--- /dev/null
+++ b/src/Permissions/Backend/PermissionStore.vala
@@ -0,0 +1,99 @@
+/*
+ * 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;
+ }
+
+ public void set_permission (string table, string id, string app, string[] permissions) {
+ dbus.set_permission.begin (table, false, id, app, permissions, (obj, res) => {
+ try {
+ dbus.set_permission.end (res);
+ } catch (Error e) {
+ critical (e.message);
+ var dialog = new Granite.MessageDialog (
+ _("Couldn't set permission"),
+ e.message,
+ new ThemedIcon ("preferences-system")
+ ) {
+ badge_icon = new ThemedIcon ("dialog-error"),
+ modal = true,
+ transient_for = ((Gtk.Application) GLib.Application.get_default ()).active_window
+ };
+ dialog.present ();
+ dialog.response.connect (dialog.destroy);
+ }
+ });
+ }
+
+ public async string[] get_permission (string table, string id, string app) {
+ try {
+ return yield dbus.get_permission (table, id, app);
+ } catch (Error e) {
+ var dialog = new Granite.MessageDialog (
+ _("Couldn't get permission"),
+ e.message,
+ new ThemedIcon ("preferences-system")
+ ) {
+ badge_icon = new ThemedIcon ("dialog-error"),
+ modal = true,
+ transient_for = ((Gtk.Application) GLib.Application.get_default ()).active_window
+ };
+ dialog.present ();
+ dialog.response.connect (dialog.destroy);
+ }
+
+ return { null };
+ }
+}
diff --git a/src/Permissions/Widgets/AppSettingsView.vala b/src/Permissions/Widgets/AppSettingsView.vala
index 9b27c4b2..f72b2a54 100644
--- a/src/Permissions/Widgets/AppSettingsView.vala
+++ b/src/Permissions/Widgets/AppSettingsView.vala
@@ -22,21 +22,36 @@
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 PermissionSettingsWidget background_row;
construct {
notify["selected-app"].connect (update_view);
- list_box = new Gtk.ListBox () {
+ 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);
+
+ sandbox_box = new Gtk.ListBox () {
hexpand = true,
- valign = START,
selection_mode = NONE
};
- list_box.add_css_class ("boxed-list");
- list_box.add_css_class (Granite.STYLE_CLASS_RICH_LIST);
+ sandbox_box.add_css_class ("boxed-list");
+ sandbox_box.add_css_class (Granite.STYLE_CLASS_RICH_LIST);
+
+ var box = new Gtk.Box (VERTICAL, 24);
+ box.append (permission_box);
+ box.append (sandbox_box);
- child = list_box;
+ child = box;
reset_button = add_button (_("Reset to Defaults"));
@@ -51,10 +66,11 @@ public class Permissions.Widgets.AppSettingsView : Switchboard.SettingsPage {
}
private void update_view () {
- list_box.remove_all ();
+ permission_box.remove_all ();
+ sandbox_box.remove_all ();
if (selected_app == null) {
- reset_button.sensitive = false;
+ sensitive = false;
return;
}
@@ -113,9 +129,27 @@ public class Permissions.Widgets.AppSettingsView : Switchboard.SettingsPage {
should_enable_reset = true;
}
- list_box.append (override_row);
+ sandbox_box.append (override_row);
});
+ var permission_store = PermissionStore.get_default ();
+
+ background_row = new PermissionSettingsWidget (
+ _("Background Activity"),
+ _("Perform tasks and use system resources while its window is closed."),
+ "permissions-background"
+ );
+
+ background_row.notify["active"].connect (() => {
+ string[] permissions = { background_row.active ? "yes" : "no" };
+ permission_store.set_permission (BACKGROUND_TABLE, BACKGROUND_ID, selected_app.id, permissions);
+ });
+
+ update_permissions.begin ();
+ permission_store.notify["dbus"].connect (update_permissions);
+ permission_store.changed.connect (update_permissions);
+
+ sensitive = true;
reset_button.sensitive = should_enable_reset;
update_property (Gtk.AccessibleProperty.LABEL, _("%s permissions").printf (selected_app.name), -1);
@@ -123,6 +157,27 @@ public class Permissions.Widgets.AppSettingsView : Switchboard.SettingsPage {
icon = selected_app.icon;
}
+ private async void update_permissions () {
+ var permission_store = PermissionStore.get_default ();
+ if (permission_store.dbus == null) {
+ permission_box.sensitive = false;
+ return;
+ }
+
+ permission_box.sensitive = true;
+
+ var background_permission = yield permission_store.get_permission (BACKGROUND_TABLE, BACKGROUND_ID, selected_app.id);
+ if (background_permission[0] != null) {
+ background_row.active = background_permission[0] == "yes";
+
+ if (background_row.parent == null) {
+ permission_box.append (background_row);
+ }
+ }
+
+ permission_box.visible = permission_box.get_row_at_index (0) != null;
+ }
+
private void change_permission_settings (Backend.PermissionSettings settings) {
if (selected_app == null) {
return;
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: [