diff options
Diffstat (limited to 'panels/applications')
28 files changed, 4851 insertions, 0 deletions
diff --git a/panels/applications/applications.gresource.xml b/panels/applications/applications.gresource.xml new file mode 100644 index 0000000..b77aaba --- /dev/null +++ b/panels/applications/applications.gresource.xml @@ -0,0 +1,12 @@ +<?xml version="1.0" encoding="UTF-8"?> +<gresources> + <gresource prefix="/org/gnome/control-center/applications"> + <file preprocess="xml-stripblanks">cc-action-row.ui</file> + <file preprocess="xml-stripblanks">cc-applications-panel.ui</file> + <file preprocess="xml-stripblanks">cc-applications-row.ui</file> + <file preprocess="xml-stripblanks">cc-info-row.ui</file> + <file preprocess="xml-stripblanks">cc-snap-row.ui</file> + <file preprocess="xml-stripblanks">cc-toggle-row.ui</file> + <file>cc-applications-panel.css</file> + </gresource> +</gresources> diff --git a/panels/applications/cc-action-row.c b/panels/applications/cc-action-row.c new file mode 100644 index 0000000..b541f79 --- /dev/null +++ b/panels/applications/cc-action-row.c @@ -0,0 +1,218 @@ +/* cc-action-row.c + * + * Copyright 2018 Matthias Clasen <matthias.clasen@gmail.com> + * + * 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 <http://www.gnu.org/licenses/>. + * + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +#include <config.h> +#include <glib/gi18n.h> + +#include "cc-action-row.h" +#include "cc-applications-resources.h" + +struct _CcActionRow +{ + GtkListBoxRow parent; + + GtkWidget *title; + GtkWidget *subtitle; + GtkWidget *button; +}; + +G_DEFINE_TYPE (CcActionRow, cc_action_row, GTK_TYPE_LIST_BOX_ROW) + +static int activated_signal; + +enum +{ + PROP_0, + PROP_TITLE, + PROP_SUBTITLE, + PROP_ACTION, + PROP_ENABLED, + PROP_DESTRUCTIVE +}; + +static void +clicked_cb (CcActionRow *row) +{ + g_signal_emit (row, activated_signal, 0); +} + +static void +cc_action_row_get_property (GObject *object, + guint prop_id, + GValue *value, + GParamSpec *pspec) +{ + CcActionRow *row = CC_ACTION_ROW (object); + + switch (prop_id) + { + case PROP_TITLE: + g_value_set_string (value, gtk_label_get_label (GTK_LABEL (row->title))); + break; + + case PROP_SUBTITLE: + g_value_set_string (value, gtk_label_get_label (GTK_LABEL (row->subtitle))); + break; + + case PROP_ACTION: + g_value_set_string (value, gtk_button_get_label (GTK_BUTTON (row->button))); + break; + + case PROP_ENABLED: + g_value_set_boolean (value, gtk_widget_get_sensitive (row->button)); + break; + + case PROP_DESTRUCTIVE: + g_value_set_boolean (value, + gtk_style_context_has_class (gtk_widget_get_style_context (row->button), "destructive-action")); + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} + +static void +cc_action_row_set_property (GObject *object, + guint prop_id, + const GValue *value, + GParamSpec *pspec) +{ + CcActionRow *row = CC_ACTION_ROW (object); + + switch (prop_id) + { + case PROP_TITLE: + gtk_label_set_label (GTK_LABEL (row->title), g_value_get_string (value)); + break; + + case PROP_SUBTITLE: + gtk_label_set_label (GTK_LABEL (row->subtitle), g_value_get_string (value)); + gtk_widget_set_visible (row->subtitle, strlen (g_value_get_string (value)) > 0); + break; + + case PROP_ACTION: + gtk_button_set_label (GTK_BUTTON (row->button), g_value_get_string (value)); + break; + + case PROP_ENABLED: + gtk_widget_set_sensitive (row->button, g_value_get_boolean (value)); + break; + + case PROP_DESTRUCTIVE: + if (g_value_get_boolean (value)) + gtk_style_context_add_class (gtk_widget_get_style_context (row->button), "destructive-action"); + else + gtk_style_context_remove_class (gtk_widget_get_style_context (row->button), "destructive-action"); + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} + +static void +cc_action_row_class_init (CcActionRowClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass); + + object_class->get_property = cc_action_row_get_property; + object_class->set_property = cc_action_row_set_property; + + g_object_class_install_property (object_class, + PROP_TITLE, + g_param_spec_string ("title", "title", "title", + NULL, G_PARAM_READWRITE)); + + g_object_class_install_property (object_class, + PROP_SUBTITLE, + g_param_spec_string ("subtitle", "subtitle", "subtitle", + NULL, G_PARAM_READWRITE)); + + g_object_class_install_property (object_class, + PROP_ACTION, + g_param_spec_string ("action", "action", "action", + NULL, G_PARAM_READWRITE)); + + g_object_class_install_property (object_class, + PROP_ENABLED, + g_param_spec_boolean ("enabled", "enabled", "enabled", + TRUE, G_PARAM_READWRITE)); + + g_object_class_install_property (object_class, + PROP_DESTRUCTIVE, + g_param_spec_boolean ("destructive", "destructive", "destructive", + FALSE, G_PARAM_READWRITE)); + + activated_signal = g_signal_new ("activated", + G_OBJECT_CLASS_TYPE (object_class), + G_SIGNAL_RUN_FIRST, + 0, + NULL, NULL, + NULL, + G_TYPE_NONE, 0); + + gtk_widget_class_set_template_from_resource (widget_class, "/org/gnome/control-center/applications/cc-action-row.ui"); + + gtk_widget_class_bind_template_child (widget_class, CcActionRow, title); + gtk_widget_class_bind_template_child (widget_class, CcActionRow, subtitle); + gtk_widget_class_bind_template_child (widget_class, CcActionRow, button); + + gtk_widget_class_bind_template_callback (widget_class, clicked_cb); +} + +static void +cc_action_row_init (CcActionRow *self) +{ + gtk_widget_init_template (GTK_WIDGET (self)); +} + +CcActionRow * +cc_action_row_new (void) +{ + return CC_ACTION_ROW (g_object_new (CC_TYPE_ACTION_ROW, NULL)); +} + +void +cc_action_row_set_title (CcActionRow *row, + const gchar *name) +{ + gtk_label_set_label (GTK_LABEL (row->title), name); +} + +void +cc_action_row_set_subtitle (CcActionRow *row, + const gchar *name) +{ + gtk_label_set_label (GTK_LABEL (row->subtitle), name); + gtk_widget_set_visible (row->subtitle, strlen (name) > 0); +} + +void +cc_action_row_set_action (CcActionRow *row, + const gchar *action, + gboolean sensitive) +{ + gtk_button_set_label (GTK_BUTTON (row->button), action); + gtk_widget_set_sensitive (row->button, sensitive); +} diff --git a/panels/applications/cc-action-row.h b/panels/applications/cc-action-row.h new file mode 100644 index 0000000..2912adb --- /dev/null +++ b/panels/applications/cc-action-row.h @@ -0,0 +1,42 @@ +/* cc-action-row.h + * + * Copyright 2018 Matthias Clasen <matthias.clasen@gmail.com> + * + * 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 <http://www.gnu.org/licenses/>. + * + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +#pragma once + +#include <gtk/gtk.h> + +G_BEGIN_DECLS + +#define CC_TYPE_ACTION_ROW (cc_action_row_get_type()) +G_DECLARE_FINAL_TYPE (CcActionRow, cc_action_row, CC, ACTION_ROW, GtkListBoxRow) + +CcActionRow* cc_action_row_new (void); + +void cc_action_row_set_title (CcActionRow *row, + const gchar *label); + +void cc_action_row_set_subtitle (CcActionRow *row, + const gchar *label); + +void cc_action_row_set_action (CcActionRow *row, + const gchar *action, + gboolean sensitive); + +G_END_DECLS diff --git a/panels/applications/cc-action-row.ui b/panels/applications/cc-action-row.ui new file mode 100644 index 0000000..6a75964 --- /dev/null +++ b/panels/applications/cc-action-row.ui @@ -0,0 +1,51 @@ +<?xml version="1.0" encoding="UTF-8"?> +<interface> + <template class="CcActionRow" parent="GtkListBoxRow"> + <property name="visible">True</property> + <property name="can-focus">True</property> + <property name="activatable">False</property> + <child> + <object class="GtkBox"> + <property name="visible">1</property> + <property name="border-width">12</property> + <property name="spacing">12</property> + <child> + <object class="GtkBox"> + <property name="visible">1</property> + <property name="orientation">vertical</property> + <property name="spacing">4</property> + <child> + <object class="GtkLabel" id="title"> + <property name="visible">1</property> + <property name="xalign">0</property> + <property name="hexpand">1</property> + <property name="ellipsize">end</property> + </object> + <packing> + <property name="expand">1</property> + </packing> + </child> + <child> + <object class="GtkLabel" id="subtitle"> + <property name="visible">1</property> + <property name="xalign">0</property> + <property name="hexpand">1</property> + <property name="ellipsize">end</property> + <style> + <class name="dim-label"/> + </style> + </object> + </child> + </object> + </child> + <child> + <object class="GtkButton" id="button"> + <property name="visible">1</property> + <property name="valign">center</property> + <signal name="clicked" handler="clicked_cb" swapped="yes"/> + </object> + </child> + </object> + </child> + </template> +</interface> diff --git a/panels/applications/cc-applications-panel.c b/panels/applications/cc-applications-panel.c new file mode 100644 index 0000000..8b632d4 --- /dev/null +++ b/panels/applications/cc-applications-panel.c @@ -0,0 +1,2141 @@ +/* cc-applications-panel.c + * + * Copyright 2018 Georges Basile Stavracas Neto <georges.stavracas@gmail.com> + * + * 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 <http://www.gnu.org/licenses/>. + * + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +#undef G_LOG_DOMAIN +#define G_LOG_DOMAIN "cc-applications-panel" + +#include <config.h> +#include <glib/gi18n.h> +#ifdef HAVE_SNAP +#include <snapd-glib/snapd-glib.h> +#endif +#ifdef HAVE_MALCONTENT +#include <libmalcontent/malcontent.h> +#endif + +#include <gio/gdesktopappinfo.h> + +#include "cc-applications-panel.h" +#include "cc-applications-row.h" +#include "cc-toggle-row.h" +#include "cc-info-row.h" +#include "cc-action-row.h" +#include "cc-applications-resources.h" +#include "cc-util.h" +#ifdef HAVE_SNAP +#include "cc-snap-row.h" +#endif +#include "globs.h" +#include "list-box-helper.h" +#include "search.h" +#include "utils.h" + +#define MASTER_SCHEMA "org.gnome.desktop.notifications" +#define APP_SCHEMA MASTER_SCHEMA ".application" +#define APP_PREFIX "/org/gnome/desktop/notifications/application/" + +#define PORTAL_SNAP_PREFIX "snap." + +struct _CcApplicationsPanel +{ + CcPanel parent; + + GtkBox *sidebar_box; + GtkListBox *sidebar_listbox; + GtkEntry *sidebar_search_entry; + GtkButton *header_button; + GtkLabel *title_label; + GAppInfoMonitor *monitor; + gulong monitor_id; +#ifdef HAVE_MALCONTENT + GCancellable *cancellable; + + MctAppFilter *app_filter; + MctManager *manager; + guint app_filter_id; +#endif + + gchar *current_app_id; + gchar *current_portal_app_id; + + GHashTable *globs; + GHashTable *search_providers; + + GDBusProxy *perm_store; + GSettings *notification_settings; + GSettings *location_settings; + GSettings *privacy_settings; + GSettings *search_settings; + + GtkStack *stack; + GtkBox *empty_box; + GtkBox *settings_box; + + GtkBox *permission_section; + GtkListBox *permission_list; + CcToggleRow *camera; + CcInfoRow *no_camera; + CcToggleRow *location; + CcInfoRow *no_location; + CcToggleRow *shortcuts; + CcToggleRow *microphone; + CcInfoRow *no_microphone; + CcInfoRow *builtin; + GtkDialog *builtin_dialog; + GtkLabel *builtin_label; + GtkListBox *builtin_list; + + GtkBox *integration_section; + GtkListBox *integration_list; + CcToggleRow *notification; + CcToggleRow *background; + CcToggleRow *wallpaper; + CcToggleRow *sound; + CcInfoRow *no_sound; + CcToggleRow *search; + CcInfoRow *no_search; + + GtkBox *handler_section; + GtkButton *handler_reset; + GtkListBox *handler_list; + CcInfoRow *hypertext; + CcInfoRow *text; + CcInfoRow *images; + CcInfoRow *fonts; + CcInfoRow *archives; + CcInfoRow *packages; + CcInfoRow *audio; + CcInfoRow *video; + CcInfoRow *other; + CcInfoRow *link; + + GtkBox *usage_section; + GtkListBox *usage_list; + CcInfoRow *storage; + GtkDialog *storage_dialog; + GtkListBox *storage_list; + CcInfoRow *app; + CcInfoRow *data; + CcInfoRow *cache; + CcInfoRow *total; + GtkButton *clear_cache_button; + + guint64 app_size; + guint64 cache_size; + guint64 data_size; +}; + +static void select_app (CcApplicationsPanel *self, + const gchar *app_id); + +G_DEFINE_TYPE (CcApplicationsPanel, cc_applications_panel, CC_TYPE_PANEL) + +enum +{ + PROP_0, + PROP_PARAMETERS +}; + +/* Callbacks */ + +static gboolean +privacy_link_cb (CcApplicationsPanel *self) +{ + CcShell *shell = cc_panel_get_shell (CC_PANEL (self)); + g_autoptr(GError) error = NULL; + + if (!cc_shell_set_active_panel_from_id (shell, "location", NULL, &error)) + g_warning ("Failed to switch to privacy panel: %s", error->message); + + return TRUE; +} + +static void +open_software_cb (CcApplicationsPanel *self) +{ + const gchar *argv[] = { "gnome-software", "--details", "appid", NULL }; + + if (self->current_app_id == NULL) + argv[1] = NULL; + else + argv[2] = self->current_app_id; + + g_spawn_async (NULL, (char **)argv, NULL, G_SPAWN_SEARCH_PATH, NULL, NULL, NULL, NULL); +} + +/* --- portal permissions and utilities --- */ + +static gchar ** +get_portal_permissions (CcApplicationsPanel *self, + const gchar *table, + const gchar *id, + const gchar *app_id) +{ + g_autoptr(GVariant) ret = NULL; + g_autoptr(GVariantIter) iter = NULL; + const gchar *key = NULL; + GStrv val; + GStrv result = NULL; + + ret = g_dbus_proxy_call_sync (self->perm_store, + "Lookup", + g_variant_new ("(ss)", table, id), + 0, G_MAXINT, NULL, NULL); + if (ret == NULL) + return NULL; + + g_variant_get (ret, "(a{sas}v)", &iter, NULL); + + while (g_variant_iter_loop (iter, "{&s^a&s}", &key, &val)) + { + if (strcmp (key, app_id) == 0 && result == NULL) + result = g_strdupv (val); + } + + return result; +} + +static void +set_portal_permissions (CcApplicationsPanel *self, + const gchar *table, + const gchar *id, + const gchar *app_id, + const gchar * const *permissions) +{ + g_autoptr(GError) error = NULL; + + g_dbus_proxy_call_sync (self->perm_store, + "SetPermission", + g_variant_new ("(sbss^as)", table, TRUE, id, app_id, permissions), + 0, + G_MAXINT, + NULL, + &error); + if (error) + g_warning ("Error setting portal permissions: %s", error->message); +} + +static gchar * +get_portal_app_id (GAppInfo *info) +{ + if (G_IS_DESKTOP_APP_INFO (info)) + { + g_autofree gchar *snap_name = NULL; + gchar *flatpak_id; + + flatpak_id = g_desktop_app_info_get_string (G_DESKTOP_APP_INFO (info), "X-Flatpak"); + if (flatpak_id != NULL) + return flatpak_id; + + snap_name = g_desktop_app_info_get_string (G_DESKTOP_APP_INFO (info), "X-SnapInstanceName"); + if (snap_name != NULL) + return g_strdup_printf ("%s%s", PORTAL_SNAP_PREFIX, snap_name); + } + + return NULL; +} + +static GFile * +get_flatpak_app_dir (const gchar *app_id, + const gchar *subdir) +{ + g_autofree gchar *path = NULL; + g_autoptr(GFile) appdir = NULL; + + path = g_build_filename (g_get_home_dir (), ".var", "app", app_id, NULL); + appdir = g_file_new_for_path (path); + + return g_file_get_child (appdir, subdir); +} + +/* --- search settings --- */ + +static void +set_search_enabled (CcApplicationsPanel *self, + const gchar *app_id, + gboolean enabled) +{ + g_autoptr(GPtrArray) new_apps = NULL; + g_autofree gchar *desktop_id = NULL; + g_auto(GStrv) apps = NULL; + gpointer key, value; + gboolean default_disabled; + gint i; + + desktop_id = g_strconcat (app_id, ".desktop", NULL); + + if (!g_hash_table_lookup_extended (self->search_providers, app_id, &key, &value)) + { + g_warning ("Trying to configure search for a provider-less app - this shouldn't happen"); + return; + } + + default_disabled = GPOINTER_TO_INT (value); + + new_apps = g_ptr_array_new_with_free_func (g_free); + if (default_disabled) + { + apps = g_settings_get_strv (self->search_settings, "enabled"); + for (i = 0; apps[i]; i++) + { + if (strcmp (apps[i], desktop_id) != 0) + g_ptr_array_add (new_apps, g_strdup (apps[i])); + } + if (enabled) + g_ptr_array_add (new_apps, g_strdup (desktop_id)); + g_ptr_array_add (new_apps, NULL); + g_settings_set_strv (self->search_settings, "enabled", (const gchar * const *)new_apps->pdata); + } + else + { + apps = g_settings_get_strv (self->search_settings, "disabled"); + for (i = 0; apps[i]; i++) + { + if (strcmp (apps[i], desktop_id) != 0) + g_ptr_array_add (new_apps, g_strdup (apps[i])); + } + if (!enabled) + g_ptr_array_add (new_apps, g_strdup (desktop_id)); + g_ptr_array_add (new_apps, NULL); + g_settings_set_strv (self->search_settings, "disabled", (const gchar * const *)new_apps->pdata); + } +} + +static gboolean +search_contains_string_for_app (CcApplicationsPanel *self, + const gchar *app_id, + const gchar *setting) +{ + g_autofree gchar *desktop_id = NULL; + g_auto(GStrv) apps = NULL; + + desktop_id = g_strconcat (app_id, ".desktop", NULL); + apps = g_settings_get_strv (self->search_settings, setting); + + return g_strv_contains ((const gchar * const *)apps, desktop_id); +} + +static gboolean +search_enabled_for_app (CcApplicationsPanel *self, + const gchar *app_id) +{ + return search_contains_string_for_app (self, app_id, "enabled"); +} + +static gboolean +search_disabled_for_app (CcApplicationsPanel *self, + const gchar *app_id) +{ + return search_contains_string_for_app (self, app_id, "disabled"); +} + +static void +get_search_enabled (CcApplicationsPanel *self, + const gchar *app_id, + gboolean *set, + gboolean *enabled) +{ + gpointer key, value; + + *enabled = FALSE; + *set = g_hash_table_lookup_extended (self->search_providers, app_id, &key, &value); + if (!*set) + return; + + if (search_enabled_for_app (self, app_id)) + *enabled = TRUE; + else if (search_disabled_for_app (self, app_id)) + *enabled = FALSE; + else + *enabled = !GPOINTER_TO_INT (value); +} + +static void +search_cb (CcApplicationsPanel *self) +{ + if (self->current_app_id) + set_search_enabled (self, + self->current_app_id, + cc_toggle_row_get_allowed (self->search)); +} + +/* --- notification permissions (flatpaks and non-flatpak) --- */ + +static void +get_notification_allowed (CcApplicationsPanel *self, + const gchar *app_id, + gboolean *set, + gboolean *allowed) +{ + if (self->notification_settings) + { + /* FIXME */ + *set = TRUE; + *allowed = g_settings_get_boolean (self->notification_settings, "enable"); + } + else + { + g_auto(GStrv) perms = get_portal_permissions (self, "notifications", "notification", app_id); + *set = perms != NULL; + /* FIXME: needs unreleased xdg-desktop-portals to write permissions on use */ + *set = TRUE; + *allowed = perms == NULL || strcmp (perms[0], "no") != 0; + } +} + +static void +set_notification_allowed (CcApplicationsPanel *self, + gboolean allowed) +{ + if (self->notification_settings) + { + g_settings_set_boolean (self->notification_settings, "enable", allowed); + } + else + { + const gchar *perms[2] = { NULL, NULL }; + + perms[0] = allowed ? "yes" : "no"; + set_portal_permissions (self, "notifications", "notification", self->current_portal_app_id, perms); + } +} + +static void +notification_cb (CcApplicationsPanel *self) +{ + if (self->current_app_id) + set_notification_allowed (self, cc_toggle_row_get_allowed (self->notification)); +} + +static gchar * +munge_app_id (const gchar *app_id) +{ + gchar *id = g_strdup (app_id); + gint i; + + g_strcanon (id, + "0123456789" + "abcdefghijklmnopqrstuvwxyz" + "ABCDEFGHIJKLMNOPQRSTUVWXYZ" + "-", + '-'); + for (i = 0; id[i] != '\0'; i++) + id[i] = g_ascii_tolower (id[i]); + + return id; +} + +static GSettings * +get_notification_settings (const gchar *app_id) +{ + g_autofree gchar *munged_app_id = munge_app_id (app_id); + g_autofree gchar *path = g_strconcat (APP_PREFIX, munged_app_id, "/", NULL); + return g_settings_new_with_path (APP_SCHEMA, path); +} + + +/* --- background --- */ + +static void +get_background_allowed (CcApplicationsPanel *self, + const gchar *app_id, + gboolean *set, + gboolean *allowed) +{ + g_auto(GStrv) perms = get_portal_permissions (self, "background", "background", app_id); + *set = TRUE; + *allowed = perms == NULL || strcmp (perms[0], "no") != 0; +} + +static void +set_background_allowed (CcApplicationsPanel *self, + gboolean allowed) +{ + const gchar *perms[2] = { NULL, NULL }; + + perms[0] = allowed ? "yes" : "no"; + set_portal_permissions (self, "background", "background", self->current_portal_app_id, perms); +} + +static void +background_cb (CcApplicationsPanel *self) +{ + if (self->current_app_id) + set_background_allowed (self, cc_toggle_row_get_allowed (self->background)); +} + +/* --- wallpaper --- */ + +static void +get_wallpaper_allowed (CcApplicationsPanel *self, + const gchar *app_id, + gboolean *set, + gboolean *allowed) +{ + g_auto(GStrv) perms = get_portal_permissions (self, "wallpaper", "wallpaper", app_id); + + *set = perms != NULL; + *allowed = perms == NULL || strcmp (perms[0], "no") != 0; +} + +static void +set_wallpaper_allowed (CcApplicationsPanel *self, + gboolean allowed) +{ + const gchar *perms[2] = { NULL, NULL }; + + perms[0] = allowed ? "yes" : "no"; + set_portal_permissions (self, "wallpaper", "wallpaper", self->current_app_id, perms); +} + +static void +wallpaper_cb (CcApplicationsPanel *self) +{ + if (self->current_app_id) + set_wallpaper_allowed (self, cc_toggle_row_get_allowed (self->wallpaper)); +} + +/* --- shortcuts permissions (flatpak) --- */ + +static void +get_shortcuts_allowed (CcApplicationsPanel *self, + const gchar *app_id, + gboolean *set, + gboolean *granted) +{ + g_auto(GStrv) perms = NULL; + + perms = get_portal_permissions (self, "gnome", "shortcuts-inhibitor", app_id); + + /* GNOME Shell's "inhibit shortcut dialog" sets the permission to "GRANTED" if + * the user allowed for the keyboard shortcuts to be inhibited, check for that + * string value here. + */ + *set = perms != NULL; + *granted = (perms != NULL) && g_ascii_strcasecmp (perms[0], "GRANTED") == 0; +} + +static void +set_shortcuts_allowed (CcApplicationsPanel *self, + gboolean granted) +{ + const gchar *perms[2]; + g_autofree gchar *desktop_id = g_strconcat (self->current_app_id, ".desktop", NULL); + + /* "GRANTED" and "DENIED" here match the values set by the "inhibit shortcut + * dialog" is GNOME Shell: + * https://gitlab.gnome.org/GNOME/gnome-shell/-/blob/master/js/ui/inhibitShortcutsDialog.js + */ + perms[0] = granted ? "GRANTED" : "DENIED"; + perms[1] = NULL; + + set_portal_permissions (self, "gnome", "shortcuts-inhibitor", desktop_id, perms); +} + +static void +shortcuts_cb (CcApplicationsPanel *self) +{ + if (self->current_app_id) + set_shortcuts_allowed (self, cc_toggle_row_get_allowed (self->shortcuts)); +} + +/* --- device (microphone, camera, speaker) permissions (flatpak) --- */ + +static void +get_device_allowed (CcApplicationsPanel *self, + const gchar *device, + const gchar *app_id, + gboolean *set, + gboolean *allowed) +{ + g_auto(GStrv) perms = NULL; + + perms = get_portal_permissions (self, "devices", device, app_id); + + *set = perms != NULL; + *allowed = perms == NULL || strcmp (perms[0], "no") != 0; +} + +static void +set_device_allowed (CcApplicationsPanel *self, + const gchar *device, + gboolean allowed) +{ + const gchar *perms[2]; + + perms[0] = allowed ? "yes" : "no"; + perms[1] = NULL; + + set_portal_permissions (self, "devices", device, self->current_portal_app_id, perms); +} + +static void +microphone_cb (CcApplicationsPanel *self) +{ + if (self->current_portal_app_id) + set_device_allowed (self, "microphone", cc_toggle_row_get_allowed (self->microphone)); +} + +static void +sound_cb (CcApplicationsPanel *self) +{ + if (self->current_portal_app_id) + set_device_allowed (self, "speakers", cc_toggle_row_get_allowed (self->sound)); +} + +static void +camera_cb (CcApplicationsPanel *self) +{ + if (self->current_portal_app_id) + set_device_allowed (self, "camera", cc_toggle_row_get_allowed (self->camera)); +} + +/* --- location permissions (flatpak) --- */ + +static void +get_location_allowed (CcApplicationsPanel *self, + const gchar *app_id, + gboolean *set, + gboolean *allowed) +{ + g_auto(GStrv) perms = NULL; + + perms = get_portal_permissions (self, "location", "location", app_id); + + *set = perms != NULL; + *allowed = perms == NULL || strcmp (perms[0], "NONE") != 0; +} + +static void +set_location_allowed (CcApplicationsPanel *self, + gboolean allowed) +{ + const gchar *perms[3]; + + /* FIXME allow setting accuracy */ + perms[0] = allowed ? "EXACT" : "NONE"; + perms[1] = "0"; + perms[2] = NULL; + + set_portal_permissions (self, "location", "location", self->current_portal_app_id, perms); +} + +static void +location_cb (CcApplicationsPanel *self) +{ + if (self->current_portal_app_id) + set_location_allowed (self, cc_toggle_row_get_allowed (self->location)); +} + +/* --- permissions section --- */ + +#ifdef HAVE_SNAP +static void +remove_snap_permissions (CcApplicationsPanel *self) +{ + g_autoptr(GList) rows = NULL; + GList *link; + + rows = gtk_container_get_children (GTK_CONTAINER (self->permission_list)); + for (link = rows; link; link = link->next) + { + GtkWidget *row = link->data; + + if (row == GTK_WIDGET (self->builtin)) + break; + + if (CC_IS_SNAP_ROW (row)) + gtk_container_remove (GTK_CONTAINER (self->permission_list), GTK_WIDGET (row)); + } +} + +static gboolean +add_snap_permissions (CcApplicationsPanel *self, + GAppInfo *info, + const gchar *app_id) +{ + const gchar *snap_name; + g_autoptr(GList) rows = NULL; + gint index; + g_autoptr(SnapdClient) client = NULL; + g_autoptr(GPtrArray) interfaces = NULL; + g_autoptr(GPtrArray) plugs = NULL; + g_autoptr(GPtrArray) slots = NULL; + SnapdInterface *interface = NULL; + gint added = 0; + g_autoptr(GError) error = NULL; + g_autoptr(GError) interfaces_error = NULL; + + if (!g_str_has_prefix (app_id, PORTAL_SNAP_PREFIX)) + return FALSE; + snap_name = app_id + strlen (PORTAL_SNAP_PREFIX); + + rows = gtk_container_get_children (GTK_CONTAINER (self->permission_list)); + index = g_list_index (rows, self->builtin); + g_assert (index >= 0); + + client = snapd_client_new (); + + interfaces = snapd_client_get_interfaces2_sync (client, + SNAPD_GET_INTERFACES_FLAGS_NONE, + NULL, + NULL, &interfaces_error); + if (interfaces == NULL) + g_warning ("Failed to get snap interfaces: %s", interfaces_error->message); + + if (!snapd_client_get_connections2_sync (client, + SNAPD_GET_CONNECTIONS_FLAGS_SELECT_ALL, + NULL, NULL, + NULL, NULL, + &plugs, &slots, + NULL, &error)) + { + g_warning ("Failed to get snap connections: %s", error->message); + return FALSE; + } + + for (int i = 0; i < plugs->len; i++) + { + SnapdPlug *plug = g_ptr_array_index (plugs, i); + CcSnapRow *row; + g_autoptr(GPtrArray) available_slots = NULL; + const gchar * const hidden_interfaces[] = { "content", + "desktop", "desktop-legacy", + "mir", + "unity7", "unity8", + "wayland", + "x11", + NULL }; + + /* Skip if not relating to this snap */ + if (g_strcmp0 (snapd_plug_get_snap (plug), snap_name) != 0) + continue; + + /* Ignore interfaces that are too low level to make sense to show or disable */ + if (g_strv_contains (hidden_interfaces, snapd_plug_get_interface (plug))) + continue; + + available_slots = g_ptr_array_new_with_free_func (g_object_unref); + for (int j = 0; j < slots->len; j++) + { + SnapdSlot *slot = g_ptr_array_index (slots, j); + if (g_strcmp0 (snapd_plug_get_interface (plug), snapd_slot_get_interface (slot)) != 0) + continue; + + g_ptr_array_add (available_slots, g_object_ref (slot)); + } + + if (interfaces != NULL) + { + for (int j = 0; j < interfaces->len; j++) + { + SnapdInterface *i = g_ptr_array_index (interfaces, j); + if (g_strcmp0 (snapd_interface_get_name (i), snapd_plug_get_interface (plug)) == 0) + interface = i; + } + } + + row = cc_snap_row_new (cc_panel_get_cancellable (CC_PANEL (self)), interface, plug, available_slots); + gtk_widget_show (GTK_WIDGET (row)); + gtk_list_box_insert (GTK_LIST_BOX (self->permission_list), GTK_WIDGET (row), index); + index++; + added++; + } + + return added > 0; +} +#endif + +static gint +add_static_permission_row (CcApplicationsPanel *self, + const gchar *title, + const gchar *subtitle) +{ + GtkWidget *row; + + row = g_object_new (CC_TYPE_INFO_ROW, + "title", title, + "info", subtitle, + NULL); + gtk_container_add (GTK_CONTAINER (self->builtin_list), row); + + return 1; +} + +static void +permission_row_activated_cb (CcApplicationsPanel *self, + GtkListBoxRow *list_row) +{ + if (list_row == GTK_LIST_BOX_ROW (self->builtin)) + { + gtk_window_set_transient_for (GTK_WINDOW (self->builtin_dialog), + GTK_WINDOW (gtk_widget_get_toplevel (GTK_WIDGET (self)))); + gtk_window_present (GTK_WINDOW (self->builtin_dialog)); + } +} + +static gboolean +add_static_permissions (CcApplicationsPanel *self, + GAppInfo *info, + const gchar *app_id) +{ + g_autoptr(GKeyFile) keyfile = NULL; + g_auto(GStrv) sockets = NULL; + g_auto(GStrv) devices = NULL; + g_auto(GStrv) shared = NULL; + g_auto(GStrv) filesystems = NULL; + g_autofree gchar *str = NULL; + gint added = 0; + g_autofree gchar *text = NULL; + + if (!g_str_has_prefix (app_id, PORTAL_SNAP_PREFIX)) + keyfile = get_flatpak_metadata (app_id); + if (keyfile == NULL) + return FALSE; + + sockets = g_key_file_get_string_list (keyfile, "Context", "sockets", NULL, NULL); + if (sockets && g_strv_contains ((const gchar * const*)sockets, "system-bus")) + added += add_static_permission_row (self, _("System Bus"), _("Full access")); + if (sockets && g_strv_contains ((const gchar * const*)sockets, "session-bus")) + added += add_static_permission_row (self, _("Session Bus"), _("Full access")); + + devices = g_key_file_get_string_list (keyfile, "Context", "devices", NULL, NULL); + if (devices && g_strv_contains ((const gchar * const*)devices, "all")) + added += add_static_permission_row (self, _("Devices"), _("Full access to /dev")); + + shared = g_key_file_get_string_list (keyfile, "Context", "shared", NULL, NULL); + if (shared && g_strv_contains ((const gchar * const*)shared, "network")) + added += add_static_permission_row (self, _("Network"), _("Has network access")); + + filesystems = g_key_file_get_string_list (keyfile, "Context", "filesystems", NULL, NULL); + if (filesystems && (g_strv_contains ((const gchar * const *)filesystems, "home") || + g_strv_contains ((const gchar * const *)filesystems, "home:rw"))) + added += add_static_permission_row (self, _("Home"), _("Full access")); + else if (filesystems && g_strv_contains ((const gchar * const *)filesystems, "home:ro")) + added += add_static_permission_row (self, _("Home"), _("Read-only")); + if (filesystems && (g_strv_contains ((const gchar * const *)filesystems, "host") || + g_strv_contains ((const gchar * const *)filesystems, "host:rw"))) + added += add_static_permission_row (self, _("File System"), _("Full access")); + else if (filesystems && g_strv_contains ((const gchar * const *)filesystems, "host:ro")) + added += add_static_permission_row (self, _("File System"), _("Read-only")); + + str = g_key_file_get_string (keyfile, "Session Bus Policy", "ca.desrt.dconf", NULL); + if (str && g_str_equal (str, "talk")) + added += add_static_permission_row (self, _("Settings"), _("Can change settings")); + + text = g_strdup_printf (_("%s has the following permissions built-in. These cannot be altered. If you are concerned about these permissions, consider removing this application."), g_app_info_get_display_name (info)); + gtk_label_set_label (self->builtin_label, text); + + return added > 0; +} + +static void +remove_static_permissions (CcApplicationsPanel *self) +{ + container_remove_all (GTK_CONTAINER (self->builtin_list)); +} + +static void +update_permission_section (CcApplicationsPanel *self, + GAppInfo *info) +{ + g_autofree gchar *portal_app_id = get_portal_app_id (info); + gboolean disabled, allowed, set; + gboolean has_any = FALSE, has_builtin = FALSE; + + if (portal_app_id == NULL) + { + gtk_widget_hide (GTK_WIDGET (self->permission_section)); + return; + } + + disabled = g_settings_get_boolean (self->privacy_settings, "disable-camera"); + get_device_allowed (self, "camera", portal_app_id, &set, &allowed); + cc_toggle_row_set_allowed (self->camera, allowed); + gtk_widget_set_visible (GTK_WIDGET (self->camera), set && !disabled); + gtk_widget_set_visible (GTK_WIDGET (self->no_camera), set && disabled); + has_any |= set; + + disabled = g_settings_get_boolean (self->privacy_settings, "disable-microphone"); + get_device_allowed (self, "microphone", portal_app_id, &set, &allowed); + cc_toggle_row_set_allowed (self->microphone, allowed); + gtk_widget_set_visible (GTK_WIDGET (self->microphone), set && !disabled); + gtk_widget_set_visible (GTK_WIDGET (self->no_microphone), set && disabled); + has_any |= set; + + disabled = !g_settings_get_boolean (self->location_settings, "enabled"); + get_location_allowed (self, portal_app_id, &set, &allowed); + cc_toggle_row_set_allowed (self->location, allowed); + gtk_widget_set_visible (GTK_WIDGET (self->location), set && !disabled); + gtk_widget_set_visible (GTK_WIDGET (self->no_location), set && disabled); + has_any |= set; + +#ifdef HAVE_SNAP + remove_snap_permissions (self); + has_any |= add_snap_permissions (self, info, portal_app_id); +#endif + + remove_static_permissions (self); + has_builtin = add_static_permissions (self, info, portal_app_id); + gtk_widget_set_visible (GTK_WIDGET (self->builtin), has_builtin); + has_any |= has_builtin; + + gtk_widget_set_visible (GTK_WIDGET (self->permission_section), has_any); +} + +/* --- gintegration section --- */ + +static void +update_integration_section (CcApplicationsPanel *self, + GAppInfo *info) +{ + g_autofree gchar *app_id = get_app_id (info); + g_autofree gchar *portal_app_id = get_portal_app_id (info); + gboolean set, allowed, disabled; + gboolean has_any = FALSE; + + disabled = g_settings_get_boolean (self->search_settings, "disable-external"); + get_search_enabled (self, app_id, &set, &allowed); + cc_toggle_row_set_allowed (self->search, allowed); + gtk_widget_set_visible (GTK_WIDGET (self->search), set && !disabled); + gtk_widget_set_visible (GTK_WIDGET (self->no_search), set && disabled); + + if (app_id != NULL) + { + g_autofree gchar *desktop_id = g_strconcat (app_id, ".desktop", NULL); + get_shortcuts_allowed (self, desktop_id, &set, &allowed); + gtk_widget_set_visible (GTK_WIDGET (self->shortcuts), set); + cc_toggle_row_set_allowed (self->shortcuts, allowed); + } + else + { + gtk_widget_hide (GTK_WIDGET (self->shortcuts)); + } + + if (portal_app_id != NULL) + { + g_clear_object (&self->notification_settings); + get_notification_allowed (self, portal_app_id, &set, &allowed); + cc_toggle_row_set_allowed (self->notification, allowed); + gtk_widget_set_visible (GTK_WIDGET (self->notification), set); + has_any |= set; + + get_background_allowed (self, portal_app_id, &set, &allowed); + cc_toggle_row_set_allowed (self->background, allowed); + gtk_widget_set_visible (GTK_WIDGET (self->background), set); + has_any |= set; + + get_wallpaper_allowed (self, portal_app_id, &set, &allowed); + cc_toggle_row_set_allowed (self->wallpaper, allowed); + gtk_widget_set_visible (GTK_WIDGET (self->wallpaper), set); + has_any |= set; + + disabled = g_settings_get_boolean (self->privacy_settings, "disable-sound-output"); + get_device_allowed (self, "speakers", portal_app_id, &set, &allowed); + cc_toggle_row_set_allowed (self->sound, allowed); + gtk_widget_set_visible (GTK_WIDGET (self->sound), set && !disabled); + gtk_widget_set_visible (GTK_WIDGET (self->no_sound), set && disabled); + } + else + { + g_set_object (&self->notification_settings, get_notification_settings (app_id)); + get_notification_allowed (self, app_id, &set, &allowed); + cc_toggle_row_set_allowed (self->notification, allowed); + gtk_widget_set_visible (GTK_WIDGET (self->notification), set); + has_any |= set; + + gtk_widget_hide (GTK_WIDGET (self->background)); + gtk_widget_hide (GTK_WIDGET (self->wallpaper)); + gtk_widget_hide (GTK_WIDGET (self->sound)); + gtk_widget_hide (GTK_WIDGET (self->no_sound)); + } + + gtk_widget_set_visible (GTK_WIDGET (self->integration_section), has_any); +} + +/* --- handler section --- */ + +static void +unset_cb (CcApplicationsPanel *self, + CcActionRow *row) +{ + const gchar *type; + GtkListBoxRow *selected; + GAppInfo *info; + + selected = gtk_list_box_get_selected_row (self->sidebar_listbox); + info = cc_applications_row_get_info (CC_APPLICATIONS_ROW (selected)); + + type = (const gchar *)g_object_get_data (G_OBJECT (row), "type"); + + g_app_info_remove_supports_type (info, type, NULL); +} + +static void +update_group_row_count (CcInfoRow *row, + gint delta) +{ + gint count; + g_autofree gchar *text = NULL; + + count = GPOINTER_TO_INT (g_object_get_data (G_OBJECT (row), "count")); + count += delta; + g_object_set_data (G_OBJECT (row), "count", GINT_TO_POINTER (count)); + text = g_strdup_printf ("%d", count); + g_object_set (row, "info", text, NULL); +} + +static void +add_scheme (CcApplicationsPanel *self, + CcInfoRow *after, + const gchar *type) +{ + CcActionRow *row = NULL; + gint pos; + + if (g_str_has_suffix (type, "http")) + { + row = cc_action_row_new (); + cc_action_row_set_title (row, _("Web Links")); + cc_action_row_set_subtitle (row, "http://, https://"); + } + else if (g_str_has_suffix (type, "https")) + { + return; /* assume anything that handles https also handles http */ + } + else if (g_str_has_suffix (type, "git")) + { + row = cc_action_row_new (); + cc_action_row_set_title (row, _("Git Links")); + cc_action_row_set_subtitle (row, "git://"); + } + else + { + gchar *scheme = strrchr (type, '/') + 1; + g_autofree gchar *title = g_strdup_printf (_("%s Links"), scheme); + g_autofree gchar *subtitle = g_strdup_printf ("%s://", scheme); + + row = cc_action_row_new (); + cc_action_row_set_title (row, title); + cc_action_row_set_subtitle (row, subtitle); + } + + cc_action_row_set_action (row, _("Unset"), TRUE); + g_object_set_data_full (G_OBJECT (row), "type", g_strdup (type), g_free); + g_signal_connect_object (row, + "activated", + G_CALLBACK (unset_cb), + self, G_CONNECT_SWAPPED); + + if (after) + { + pos = gtk_list_box_row_get_index (GTK_LIST_BOX_ROW (after)) + 1; + g_object_bind_property (after, "expanded", + row, "visible", + G_BINDING_SYNC_CREATE); + } + else + pos = -1; + gtk_list_box_insert (self->handler_list, GTK_WIDGET (row), pos); + update_group_row_count (after, 1); +} + +static void +add_file_type (CcApplicationsPanel *self, + CcInfoRow *after, + const gchar *type) +{ + CcActionRow *row; + g_autofree gchar *desc = NULL; + gint pos; + const gchar *glob; + + glob = g_hash_table_lookup (self->globs, type); + + desc = g_content_type_get_description (type); + row = cc_action_row_new (); + cc_action_row_set_title (row, desc); + cc_action_row_set_subtitle (row, glob ? glob : ""); + cc_action_row_set_action (row, _("Unset"), TRUE); + g_object_set_data_full (G_OBJECT (row), "type", g_strdup (type), g_free); + g_signal_connect_object (row, "activated", G_CALLBACK (unset_cb), self, G_CONNECT_SWAPPED); + + if (after) + { + pos = gtk_list_box_row_get_index (GTK_LIST_BOX_ROW (after)) + 1; + g_object_bind_property (after, "expanded", + row, "visible", + G_BINDING_SYNC_CREATE); + } + else + { + pos = -1; + } + + gtk_list_box_insert (self->handler_list, GTK_WIDGET (row), pos); + update_group_row_count (after, 1); +} + +static gboolean +is_hypertext_type (const gchar *type) +{ + const gchar *types[] = { + "text/html", + "text/htmlh", + "text/xml", + "application/xhtml+xml", + "application/vnd.mozilla.xul+xml", + "text/mml", + NULL + }; + return g_strv_contains (types, type); +} + +static void +ensure_group_row (CcApplicationsPanel *self, + CcInfoRow **row, + const gchar *title) +{ + if (*row == NULL) + { + CcInfoRow *r = CC_INFO_ROW (g_object_new (CC_TYPE_INFO_ROW, + "title", title, + "has-expander", TRUE, + NULL)); + gtk_list_box_insert (self->handler_list, GTK_WIDGET (r), -1); + *row = r; + } +} + +static void +add_link_type (CcApplicationsPanel *self, + const gchar *type) +{ + ensure_group_row (self, &self->link, _("Links")); + add_scheme (self, self->link, type); +} + +static void +add_hypertext_type (CcApplicationsPanel *self, + const gchar *type) +{ + ensure_group_row (self, &self->hypertext, _("Hypertext Files")); + add_file_type (self, self->hypertext, type); +} + +static gboolean +is_text_type (const gchar *type) +{ + return g_content_type_is_a (type, "text/*"); +} + +static void +add_text_type (CcApplicationsPanel *self, + const gchar *type) +{ + ensure_group_row (self, &self->text, _("Text Files")); + add_file_type (self, self->text, type); +} + +static gboolean +is_image_type (const gchar *type) +{ + return g_content_type_is_a (type, "image/*"); +} + +static void +add_image_type (CcApplicationsPanel *self, + const gchar *type) +{ + ensure_group_row (self, &self->images, _("Image Files")); + add_file_type (self, self->images, type); +} + +static gboolean +is_font_type (const gchar *type) +{ + return g_content_type_is_a (type, "font/*") || + g_str_equal (type, "application/x-font-pcf") || + g_str_equal (type, "application/x-font-type1"); +} + +static void +add_font_type (CcApplicationsPanel *self, + const gchar *type) +{ + ensure_group_row (self, &self->fonts, _("Font Files")); + add_file_type (self, self->fonts, type); +} + +static gboolean +is_archive_type (const gchar *type) +{ + const gchar *types[] = { + "application/bzip2", + "application/zip", + "application/x-xz-compressed-tar", + "application/x-xz", + "application/x-xar", + "application/x-tarz", + "application/x-tar", + "application/x-lzma-compressed-tar", + "application/x-lzma", + "application/x-lzip-compressed-tar", + "application/x-lzip", + "application/x-lha", + "application/gzip", + "application/x-cpio", + "application/x-compressed-tar", + "application/x-compress", + "application/x-bzip-compressed-tar", + "application/x-bzip", + "application/x-7z-compressed-tar", + "application/x-7z-compressed", + "application/x-zoo", + "application/x-war", + "application/x-stuffit", + "application/x-rzip-compressed-tar", + "application/x-rzip", + "application/vnd.rar", + "application/x-lzop-compressed-tar", + "application/x-lzop", + "application/x-lz4-compressed-tar", + "application/x-lz4", + "application/x-lrzip-compressed-tar", + "application/x-lrzip", + "application/x-lhz", + "application/x-java-archive", + "application/x-ear", + "application/x-cabinet", + "application/x-bzip1-compressed-tar", + "application/x-bzip1", + "application/x-arj", + "application/x-archive", + "application/x-ar", + "application/x-alz", + "application/x-ace", + "application/vnd.ms-cab-compressed", + NULL + }; + return g_strv_contains (types, type); +} + +static void +add_archive_type (CcApplicationsPanel *self, + const gchar *type) +{ + ensure_group_row (self, &self->archives, _("Archive Files")); + add_file_type (self, self->archives, type); +} + +static gboolean +is_package_type (const gchar *type) +{ + const gchar *types[] = { + "application/x-source-rpm", + "application/x-rpm", + "application/vnd.debian.binary-package", + NULL + }; + return g_strv_contains (types, type); +} + +static void +add_package_type (CcApplicationsPanel *self, + const gchar *type) +{ + ensure_group_row (self, &self->packages, _("Package Files")); + add_file_type (self, self->packages, type); +} + +static gboolean +is_audio_type (const gchar *type) +{ + return g_content_type_is_a (type, "audio/*") || + g_str_equal (type, "application/ogg") || + g_str_equal (type, "application/x-shorten") || + g_str_equal (type, "application/x-matroska") || + g_str_equal (type, "application/x-flac") || + g_str_equal (type, "application/x-extension-mp4") || + g_str_equal (type, "application/x-extension-m4a") || + g_str_equal (type, "application/vnd.rn-realmedia") || + g_str_equal (type, "application/ram") || + g_str_equal (type, "application/vnd.ms-wpl"); +} + +static void +add_audio_type (CcApplicationsPanel *self, + const gchar *type) +{ + ensure_group_row (self, &self->audio, _("Audio Files")); + add_file_type (self, self->audio, type); +} + +static gboolean +is_video_type (const gchar *type) +{ + return g_content_type_is_a (type, "video/*") || + g_str_equal (type, "application/x-smil") || + g_str_equal (type, "application/vnd.ms-asf") || + g_str_equal (type, "application/mxf"); +} + +static void +add_video_type (CcApplicationsPanel *self, + const gchar *type) +{ + ensure_group_row (self, &self->video, _("Video Files")); + add_file_type (self, self->video, type); +} + +static void +add_other_type (CcApplicationsPanel *self, + const gchar *type) +{ + ensure_group_row (self, &self->other, _("Other Files")); + add_file_type (self, self->other, type); +} + +static void +add_handler_row (CcApplicationsPanel *self, + const gchar *type) +{ + gtk_widget_show (GTK_WIDGET (self->handler_section)); + + if (g_content_type_is_a (type, "x-scheme-handler/*")) + add_link_type (self, type); + else if (is_hypertext_type (type)) + add_hypertext_type (self, type); + else if (is_font_type (type)) + add_font_type (self, type); + else if (is_package_type (type)) + add_package_type (self, type); + else if (is_audio_type (type)) + add_audio_type (self, type); + else if (is_video_type (type)) + add_video_type (self, type); + else if (is_archive_type (type)) + add_archive_type (self, type); + else if (is_text_type (type)) + add_text_type (self, type); + else if (is_image_type (type)) + add_image_type (self, type); + else + add_other_type (self, type); +} + +static void +handler_row_activated_cb (CcApplicationsPanel *self, + GtkListBoxRow *list_row) +{ + CcInfoRow *row; + + if (!CC_IS_INFO_ROW (list_row)) + return; + + row = CC_INFO_ROW (list_row); + if (row == self->hypertext || + row == self->text || + row == self->images || + row == self->fonts || + row == self->archives || + row == self->packages || + row == self->audio || + row == self->video || + row == self->other || + row == self->link) + { + cc_info_row_set_expanded (row, !cc_info_row_get_expanded (row)); + } +} + +static gboolean +app_info_recommended_for (GAppInfo *info, + const gchar *type) +{ + /* this is horribly inefficient. I blame the mime system */ + g_autolist(GObject) list = NULL; + GList *l; + gboolean ret = FALSE; + + list = g_app_info_get_recommended_for_type (type); + for (l = list; l; l = l->next) + { + GAppInfo *ri = l->data; + + if (g_app_info_equal (info, ri)) + { + ret = TRUE; + break; + } + } + + return ret; +} + +static void +handler_reset_cb (CcApplicationsPanel *self) +{ + GtkListBoxRow *selected; + GAppInfo *info; + const gchar **types; + gint i; + + selected = gtk_list_box_get_selected_row (self->sidebar_listbox); + info = cc_applications_row_get_info (CC_APPLICATIONS_ROW (selected)); + + types = g_app_info_get_supported_types (info); + if (types == NULL || types[0] == NULL) + return; + + g_signal_handler_block (self->monitor, self->monitor_id); + for (i = 0; types[i]; i++) + { + gchar *ctype = g_content_type_from_mime_type (types[i]); + g_app_info_add_supports_type (info, ctype, NULL); + } + g_signal_handler_unblock (self->monitor, self->monitor_id); + g_signal_emit_by_name (self->monitor, "changed"); +} + +static void +update_handler_sections (CcApplicationsPanel *self, + GAppInfo *info) +{ + g_autoptr(GHashTable) hash = NULL; + const gchar **types; + gint i; + + container_remove_all (GTK_CONTAINER (self->handler_list)); + + self->hypertext = NULL; + self->text = NULL; + self->images = NULL; + self->fonts = NULL; + self->archives = NULL; + self->packages = NULL; + self->audio = NULL; + self->video = NULL; + self->other = NULL; + self->link = NULL; + + gtk_widget_hide (GTK_WIDGET (self->handler_section)); + + types = g_app_info_get_supported_types (info); + if (types == NULL || types[0] == NULL) + return; + + hash = g_hash_table_new_full (g_str_hash, g_str_equal, NULL, g_free); + + gtk_widget_set_sensitive (GTK_WIDGET (self->handler_reset), FALSE); + for (i = 0; types[i]; i++) + { + g_autofree gchar *ctype = g_content_type_from_mime_type (types[i]); + + if (g_hash_table_contains (hash, ctype)) + continue; + + if (!app_info_recommended_for (info, ctype)) + { + gtk_widget_set_sensitive (GTK_WIDGET (self->handler_reset), TRUE); + continue; + } + + add_handler_row (self, ctype); + g_hash_table_add (hash, g_steal_pointer (&ctype)); + } +} + +/* --- usage section --- */ + +static void +storage_row_activated_cb (CcApplicationsPanel *self, + GtkListBoxRow *list_row) +{ + if (list_row == GTK_LIST_BOX_ROW (self->storage)) + { + gtk_window_set_transient_for (GTK_WINDOW (self->storage_dialog), + GTK_WINDOW (gtk_widget_get_toplevel (GTK_WIDGET (self)))); + gtk_window_present (GTK_WINDOW (self->storage_dialog)); + } +} + +static void +update_total_size (CcApplicationsPanel *self) +{ + g_autofree gchar *formatted_size = NULL; + guint64 total; + + total = self->app_size + self->data_size + self->cache_size; + formatted_size = g_format_size (total); + g_object_set (self->total, "info", formatted_size, NULL); + g_object_set (self->storage, "info", formatted_size, NULL); +} + +static void +set_cache_size (GObject *source, + GAsyncResult *res, + gpointer data) +{ + CcApplicationsPanel *self = data; + g_autofree gchar *formatted_size = NULL; + guint64 size; + g_autoptr(GError) error = NULL; + + if (!file_size_finish (G_FILE (source), res, &size, &error)) + { + if (!g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED)) + g_warning ("Failed to get flatpak cache size: %s", error->message); + return; + } + self->cache_size = size; + + formatted_size = g_format_size (self->cache_size); + g_object_set (self->cache, "info", formatted_size, NULL); + + gtk_widget_set_sensitive (GTK_WIDGET (self->clear_cache_button), self->cache_size > 0); + + update_total_size (self); +} + +static void +update_cache_row (CcApplicationsPanel *self, + const gchar *app_id) +{ + g_autoptr(GFile) dir = get_flatpak_app_dir (app_id, "cache"); + g_object_set (self->cache, "info", "...", NULL); + file_size_async (dir, cc_panel_get_cancellable (CC_PANEL (self)), set_cache_size, self); +} + +static void +set_data_size (GObject *source, + GAsyncResult *res, + gpointer data) +{ + CcApplicationsPanel *self = data; + g_autofree gchar *formatted_size = NULL; + guint64 size; + g_autoptr(GError) error = NULL; + + if (!file_size_finish (G_FILE (source), res, &size, &error)) + { + if (!g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED)) + g_warning ("Failed to get flatpak data size: %s", error->message); + return; + } + self->data_size = size; + + formatted_size = g_format_size (self->data_size); + g_object_set (self->data, "info", formatted_size, NULL); + + update_total_size (self); +} + +static void +update_data_row (CcApplicationsPanel *self, + const gchar *app_id) +{ + g_autoptr(GFile) dir = get_flatpak_app_dir (app_id, "data"); + + g_object_set (self->data, "info", "...", NULL); + file_size_async (dir, cc_panel_get_cancellable (CC_PANEL (self)), set_data_size, self); +} + +static void +cache_cleared (GObject *source, + GAsyncResult *res, + gpointer data) +{ + CcApplicationsPanel *self = data; + g_autoptr(GError) error = NULL; + + if (!file_remove_finish (G_FILE (source), res, &error)) + { + if (!g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED)) + g_warning ("Failed to remove cache: %s", error->message); + return; + } + + update_cache_row (self, self->current_app_id); +} + +static void +clear_cache_cb (CcApplicationsPanel *self) +{ + g_autoptr(GFile) dir = NULL; + + if (self->current_app_id == NULL) + return; + + dir = get_flatpak_app_dir (self->current_app_id, "cache"); + file_remove_async (dir, cc_panel_get_cancellable (CC_PANEL (self)), cache_cleared, self); +} + +static void +update_app_row (CcApplicationsPanel *self, + const gchar *app_id) +{ + g_autofree gchar *formatted_size = NULL; + + if (g_str_has_prefix (app_id, PORTAL_SNAP_PREFIX)) + self->app_size = get_snap_app_size (app_id + strlen (PORTAL_SNAP_PREFIX)); + else + self->app_size = get_flatpak_app_size (app_id); + formatted_size = g_format_size (self->app_size); + g_object_set (self->app, "info", formatted_size, NULL); + update_total_size (self); +} + +static void +update_app_sizes (CcApplicationsPanel *self, + const gchar *app_id) +{ + gtk_widget_set_sensitive (GTK_WIDGET (self->clear_cache_button), FALSE); + + self->app_size = self->data_size = self->cache_size = 0; + + update_app_row (self, app_id); + update_cache_row (self, app_id); + update_data_row (self, app_id); +} + +static void +update_usage_section (CcApplicationsPanel *self, + GAppInfo *info) +{ + g_autofree gchar *portal_app_id = get_portal_app_id (info); + + if (portal_app_id != NULL) + { + gtk_widget_show (GTK_WIDGET (self->usage_section)); + update_app_sizes (self, portal_app_id); + } + else + { + gtk_widget_hide (GTK_WIDGET (self->usage_section)); + } +} + +/* --- panel setup --- */ + +static void +update_panel (CcApplicationsPanel *self, + GtkListBoxRow *row) +{ + GAppInfo *info; + + if (self->perm_store == NULL) + { + g_message ("No permissions store proxy yet, come back later"); + return; + } + + if (row == NULL) + { + gtk_label_set_label (self->title_label, _("Applications")); + gtk_stack_set_visible_child (self->stack, GTK_WIDGET (self->empty_box)); + gtk_widget_hide (GTK_WIDGET (GTK_WIDGET (self->header_button))); + return; + } + + info = cc_applications_row_get_info (CC_APPLICATIONS_ROW (row)); + + gtk_label_set_label (self->title_label, g_app_info_get_display_name (info)); + gtk_stack_set_visible_child (self->stack, GTK_WIDGET (self->settings_box)); + gtk_widget_show (GTK_WIDGET (self->header_button)); + + g_clear_pointer (&self->current_app_id, g_free); + g_clear_pointer (&self->current_portal_app_id, g_free); + + update_permission_section (self, info); + update_integration_section (self, info); + update_handler_sections (self, info); + update_usage_section (self, info); + + self->current_app_id = get_app_id (info); + self->current_portal_app_id = get_portal_app_id (info); +} + +static void +populate_applications (CcApplicationsPanel *self) +{ + g_autolist(GObject) infos = NULL; + GList *l; + + container_remove_all (GTK_CONTAINER (self->sidebar_listbox)); +#ifdef HAVE_MALCONTENT + g_signal_handler_block (self->manager, self->app_filter_id); +#endif + + infos = g_app_info_get_all (); + + for (l = infos; l; l = l->next) + { + GAppInfo *info = l->data; + GtkWidget *row; + g_autofree gchar *id = NULL; + + if (!g_app_info_should_show (info)) + continue; + +#ifdef HAVE_MALCONTENT + if (!mct_app_filter_is_appinfo_allowed (self->app_filter, info)) + continue; +#endif + + row = GTK_WIDGET (cc_applications_row_new (info)); + gtk_list_box_insert (self->sidebar_listbox, row, -1); + + id = get_app_id (info); + if (g_strcmp0 (id, self->current_app_id) == 0) + gtk_list_box_select_row (self->sidebar_listbox, GTK_LIST_BOX_ROW (row)); + } +#ifdef HAVE_MALCONTENT + g_signal_handler_unblock (self->manager, self->app_filter_id); +#endif +} + +static gint +compare_rows (GtkListBoxRow *row1, + GtkListBoxRow *row2, + gpointer data) +{ + const gchar *key1 = cc_applications_row_get_sort_key (CC_APPLICATIONS_ROW (row1)); + const gchar *key2 = cc_applications_row_get_sort_key (CC_APPLICATIONS_ROW (row2)); + + return strcmp (key1, key2); +} + +static gboolean +filter_sidebar_rows (GtkListBoxRow *row, + gpointer data) +{ + CcApplicationsPanel *self = CC_APPLICATIONS_PANEL (data); + g_autofree gchar *app_name = NULL; + g_autofree gchar *search_text = NULL; + GAppInfo *info; + + /* Only filter after the second character */ + if (gtk_entry_get_text_length (self->sidebar_search_entry) < 2) + return TRUE; + + info = cc_applications_row_get_info (CC_APPLICATIONS_ROW (row)); + app_name = cc_util_normalize_casefold_and_unaccent (g_app_info_get_name (info)); + search_text = cc_util_normalize_casefold_and_unaccent (gtk_entry_get_text (self->sidebar_search_entry)); + + return g_strstr_len (app_name, -1, search_text) != NULL; +} + +#ifdef HAVE_MALCONTENT +static void +app_filter_changed_cb (MctAppFilter *app_filter, + uid_t uid, + CcApplicationsPanel *self) +{ + populate_applications (self); +} +#endif + +static void +apps_changed (CcApplicationsPanel *self) +{ + populate_applications (self); +} + +static void +row_activated_cb (CcApplicationsPanel *self, + GtkListBoxRow *row) +{ + update_panel (self, row); + g_signal_emit_by_name (self, "sidebar-activated"); +} + +static void +on_perm_store_ready (GObject *source_object, + GAsyncResult *res, + gpointer data) +{ + CcApplicationsPanel *self = data; + GDBusProxy *proxy; + g_autoptr(GError) error = NULL; + + proxy = g_dbus_proxy_new_for_bus_finish (res, &error); + if (proxy == NULL) + { + if (!g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED)) + g_warning ("Failed to connect to portal permission store: %s", + error->message); + return; + } + + self->perm_store = proxy; + + update_panel (self, gtk_list_box_get_selected_row (self->sidebar_listbox)); +} + +static void +select_app (CcApplicationsPanel *self, + const gchar *app_id) +{ + g_autoptr(GList) children = NULL; + GList *l; + + children = gtk_container_get_children (GTK_CONTAINER (self->sidebar_listbox)); + for (l = children; l; l = l->next) + { + CcApplicationsRow *row = CC_APPLICATIONS_ROW (l->data); + GAppInfo *info = cc_applications_row_get_info (row); + if (g_str_has_prefix (g_app_info_get_id (info), app_id)) + { + gtk_list_box_select_row (self->sidebar_listbox, GTK_LIST_BOX_ROW (row)); + break; + } + } +} + +static void +on_sidebar_search_entry_activated_cb (CcApplicationsPanel *self) +{ + GtkListBoxRow *row; + + row = gtk_list_box_get_row_at_y (self->sidebar_listbox, 0); + + if (!row) + return; + + /* Show the app */ + gtk_list_box_select_row (self->sidebar_listbox, row); + g_signal_emit_by_name (row, "activate"); + + /* Cleanup the entry */ + gtk_entry_set_text (self->sidebar_search_entry, ""); + gtk_widget_grab_focus (GTK_WIDGET (self->sidebar_search_entry)); +} + +static void +on_sidebar_search_entry_search_changed_cb (CcApplicationsPanel *self) +{ + gtk_list_box_invalidate_filter (self->sidebar_listbox); +} + +static void +on_sidebar_search_entry_search_stopped_cb (CcApplicationsPanel *self) +{ + gtk_entry_set_text (self->sidebar_search_entry, ""); +} + +static void +cc_applications_panel_dispose (GObject *object) +{ + CcApplicationsPanel *self = CC_APPLICATIONS_PANEL (object); + + g_clear_object (&self->monitor); + g_clear_object (&self->perm_store); + + G_OBJECT_CLASS (cc_applications_panel_parent_class)->dispose (object); +} + +static void +cc_applications_panel_finalize (GObject *object) +{ + CcApplicationsPanel *self = CC_APPLICATIONS_PANEL (object); +#ifdef HAVE_MALCONTENT + if (self->app_filter != NULL && self->app_filter_id != 0) + { + g_signal_handler_disconnect (self->manager, self->app_filter_id); + self->app_filter_id = 0; + } + g_clear_pointer (&self->app_filter, mct_app_filter_unref); + + g_clear_object (&self->manager); +#endif + g_clear_object (&self->notification_settings); + g_clear_object (&self->location_settings); + g_clear_object (&self->privacy_settings); + g_clear_object (&self->search_settings); + + g_clear_pointer (&self->current_app_id, g_free); + g_clear_pointer (&self->current_portal_app_id, g_free); + g_clear_pointer (&self->globs, g_hash_table_unref); + g_clear_pointer (&self->search_providers, g_hash_table_unref); + + G_OBJECT_CLASS (cc_applications_panel_parent_class)->finalize (object); +} + +static void +cc_applications_panel_set_property (GObject *object, + guint property_id, + const GValue *value, + GParamSpec *pspec) +{ + switch (property_id) + { + case PROP_PARAMETERS: + { + GVariant *parameters, *v; + const gchar *first_arg = NULL; + + parameters = g_value_get_variant (value); + if (parameters == NULL) + return; + + if (g_variant_n_children (parameters) > 0) + { + g_variant_get_child (parameters, 0, "v", &v); + if (g_variant_is_of_type (v, G_VARIANT_TYPE_STRING)) + first_arg = g_variant_get_string (v, NULL); + else + g_warning ("Wrong type for the second argument GVariant, expected 's' but got '%s'", + (gchar *)g_variant_get_type (v)); + g_variant_unref (v); + + select_app (CC_APPLICATIONS_PANEL (object), first_arg); + } + + return; + } + } + + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); +} + +static void +cc_applications_panel_constructed (GObject *object) +{ + CcApplicationsPanel *self = CC_APPLICATIONS_PANEL (object); + CcShell *shell; + + G_OBJECT_CLASS (cc_applications_panel_parent_class)->constructed (object); + + shell = cc_panel_get_shell (CC_PANEL (self)); + cc_shell_embed_widget_in_header (shell, GTK_WIDGET (self->header_button), GTK_POS_RIGHT); +} + +static GtkWidget* +cc_applications_panel_get_sidebar_widget (CcPanel *panel) +{ + CcApplicationsPanel *self = CC_APPLICATIONS_PANEL (panel); + return GTK_WIDGET (self->sidebar_box); +} + +static GtkWidget * +cc_applications_panel_get_title_widget (CcPanel *panel) +{ + CcApplicationsPanel *self = CC_APPLICATIONS_PANEL (panel); + return GTK_WIDGET (self->title_label); +} + +static void +cc_applications_panel_class_init (CcApplicationsPanelClass *klass) +{ + CcPanelClass *panel_class = CC_PANEL_CLASS (klass); + GObjectClass *object_class = G_OBJECT_CLASS (klass); + GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass); + + object_class->dispose = cc_applications_panel_dispose; + object_class->finalize = cc_applications_panel_finalize; + object_class->constructed = cc_applications_panel_constructed; + object_class->set_property = cc_applications_panel_set_property; + + panel_class->get_sidebar_widget = cc_applications_panel_get_sidebar_widget; + panel_class->get_title_widget = cc_applications_panel_get_title_widget; + + g_object_class_override_property (object_class, PROP_PARAMETERS, "parameters"); + + gtk_widget_class_set_template_from_resource (widget_class, "/org/gnome/control-center/applications/cc-applications-panel.ui"); + + gtk_widget_class_bind_template_child (widget_class, CcApplicationsPanel, app); + gtk_widget_class_bind_template_child (widget_class, CcApplicationsPanel, builtin); + gtk_widget_class_bind_template_child (widget_class, CcApplicationsPanel, builtin_dialog); + gtk_widget_class_bind_template_child (widget_class, CcApplicationsPanel, builtin_label); + gtk_widget_class_bind_template_child (widget_class, CcApplicationsPanel, builtin_list); + gtk_widget_class_bind_template_child (widget_class, CcApplicationsPanel, cache); + gtk_widget_class_bind_template_child (widget_class, CcApplicationsPanel, camera); + gtk_widget_class_bind_template_child (widget_class, CcApplicationsPanel, clear_cache_button); + gtk_widget_class_bind_template_child (widget_class, CcApplicationsPanel, data); + gtk_widget_class_bind_template_child (widget_class, CcApplicationsPanel, empty_box); + gtk_widget_class_bind_template_child (widget_class, CcApplicationsPanel, header_button); + gtk_widget_class_bind_template_child (widget_class, CcApplicationsPanel, handler_section); + gtk_widget_class_bind_template_child (widget_class, CcApplicationsPanel, handler_reset); + gtk_widget_class_bind_template_child (widget_class, CcApplicationsPanel, handler_list); + gtk_widget_class_bind_template_child (widget_class, CcApplicationsPanel, integration_list); + gtk_widget_class_bind_template_child (widget_class, CcApplicationsPanel, integration_section); + gtk_widget_class_bind_template_child (widget_class, CcApplicationsPanel, location); + gtk_widget_class_bind_template_child (widget_class, CcApplicationsPanel, microphone); + gtk_widget_class_bind_template_child (widget_class, CcApplicationsPanel, no_camera); + gtk_widget_class_bind_template_child (widget_class, CcApplicationsPanel, no_location); + gtk_widget_class_bind_template_child (widget_class, CcApplicationsPanel, no_microphone); + gtk_widget_class_bind_template_child (widget_class, CcApplicationsPanel, no_search); + gtk_widget_class_bind_template_child (widget_class, CcApplicationsPanel, no_sound); + gtk_widget_class_bind_template_child (widget_class, CcApplicationsPanel, notification); + gtk_widget_class_bind_template_child (widget_class, CcApplicationsPanel, background); + gtk_widget_class_bind_template_child (widget_class, CcApplicationsPanel, wallpaper); + gtk_widget_class_bind_template_child (widget_class, CcApplicationsPanel, shortcuts); + gtk_widget_class_bind_template_child (widget_class, CcApplicationsPanel, permission_section); + gtk_widget_class_bind_template_child (widget_class, CcApplicationsPanel, permission_list); + gtk_widget_class_bind_template_child (widget_class, CcApplicationsPanel, sidebar_box); + gtk_widget_class_bind_template_child (widget_class, CcApplicationsPanel, sidebar_listbox); + gtk_widget_class_bind_template_child (widget_class, CcApplicationsPanel, sidebar_search_entry); + gtk_widget_class_bind_template_child (widget_class, CcApplicationsPanel, search); + gtk_widget_class_bind_template_child (widget_class, CcApplicationsPanel, settings_box); + gtk_widget_class_bind_template_child (widget_class, CcApplicationsPanel, sound); + gtk_widget_class_bind_template_child (widget_class, CcApplicationsPanel, stack); + gtk_widget_class_bind_template_child (widget_class, CcApplicationsPanel, storage); + gtk_widget_class_bind_template_child (widget_class, CcApplicationsPanel, storage_dialog); + gtk_widget_class_bind_template_child (widget_class, CcApplicationsPanel, storage_list); + gtk_widget_class_bind_template_child (widget_class, CcApplicationsPanel, title_label); + gtk_widget_class_bind_template_child (widget_class, CcApplicationsPanel, total); + gtk_widget_class_bind_template_child (widget_class, CcApplicationsPanel, usage_list); + gtk_widget_class_bind_template_child (widget_class, CcApplicationsPanel, usage_section); + + gtk_widget_class_bind_template_callback (widget_class, camera_cb); + gtk_widget_class_bind_template_callback (widget_class, location_cb); + gtk_widget_class_bind_template_callback (widget_class, microphone_cb); + gtk_widget_class_bind_template_callback (widget_class, search_cb); + gtk_widget_class_bind_template_callback (widget_class, notification_cb); + gtk_widget_class_bind_template_callback (widget_class, background_cb); + gtk_widget_class_bind_template_callback (widget_class, wallpaper_cb); + gtk_widget_class_bind_template_callback (widget_class, shortcuts_cb); + gtk_widget_class_bind_template_callback (widget_class, privacy_link_cb); + gtk_widget_class_bind_template_callback (widget_class, sound_cb); + gtk_widget_class_bind_template_callback (widget_class, permission_row_activated_cb); + gtk_widget_class_bind_template_callback (widget_class, handler_row_activated_cb); + gtk_widget_class_bind_template_callback (widget_class, clear_cache_cb); + gtk_widget_class_bind_template_callback (widget_class, storage_row_activated_cb); + gtk_widget_class_bind_template_callback (widget_class, open_software_cb); + gtk_widget_class_bind_template_callback (widget_class, handler_reset_cb); + gtk_widget_class_bind_template_callback (widget_class, on_sidebar_search_entry_activated_cb); + gtk_widget_class_bind_template_callback (widget_class, on_sidebar_search_entry_search_changed_cb); + gtk_widget_class_bind_template_callback (widget_class, on_sidebar_search_entry_search_stopped_cb); +} + +static void +cc_applications_panel_init (CcApplicationsPanel *self) +{ + g_autoptr(GtkStyleProvider) provider = NULL; + GtkListBoxRow *row; +#ifdef HAVE_MALCONTENT + g_autoptr(GDBusConnection) system_bus = NULL; + g_autoptr(GError) error = NULL; +#endif + + g_resources_register (cc_applications_get_resource ()); + + g_type_ensure(CC_TYPE_TOGGLE_ROW); + g_type_ensure(CC_TYPE_INFO_ROW); + + gtk_widget_init_template (GTK_WIDGET (self)); + + provider = GTK_STYLE_PROVIDER (gtk_css_provider_new ()); + gtk_css_provider_load_from_resource (GTK_CSS_PROVIDER (provider), + "/org/gnome/control-center/applications/cc-applications-panel.css"); + + gtk_style_context_add_provider_for_screen (gdk_screen_get_default (), + provider, + GTK_STYLE_PROVIDER_PRIORITY_APPLICATION); + + g_signal_connect_object (self->sidebar_listbox, "row-activated", + G_CALLBACK (row_activated_cb), self, G_CONNECT_SWAPPED); + + g_signal_connect_object (self->header_button, "clicked", G_CALLBACK (open_software_cb), self, G_CONNECT_SWAPPED); + + gtk_list_box_set_header_func (self->permission_list, + cc_list_box_update_header_func, + NULL, NULL); + + gtk_list_box_set_header_func (self->integration_list, + cc_list_box_update_header_func, + NULL, NULL); + + gtk_list_box_set_header_func (self->handler_list, + cc_list_box_update_header_func, + NULL, NULL); + + gtk_list_box_set_header_func (self->usage_list, + cc_list_box_update_header_func, + NULL, NULL); + + gtk_list_box_set_header_func (self->builtin_list, + cc_list_box_update_header_func, + NULL, NULL); + + gtk_list_box_set_header_func (self->storage_list, + cc_list_box_update_header_func, + NULL, NULL); + + gtk_list_box_set_sort_func (self->sidebar_listbox, + compare_rows, + NULL, NULL); + + gtk_list_box_set_filter_func (self->sidebar_listbox, + filter_sidebar_rows, + self, NULL); + + self->location_settings = g_settings_new ("org.gnome.system.location"); + self->privacy_settings = g_settings_new ("org.gnome.desktop.privacy"); + self->search_settings = g_settings_new ("org.gnome.desktop.search-providers"); +#ifdef HAVE_MALCONTENT + /* FIXME: should become asynchronous */ + system_bus = g_bus_get_sync (G_BUS_TYPE_SYSTEM, self->cancellable, &error); + if (system_bus == NULL) + { + g_warning ("Error getting system bus while setting up app permissions: %s", error->message); + return; + } + + /* Load the user’s parental controls settings too, so we can filter the list. */ + self->manager = mct_manager_new (system_bus); + self->app_filter = mct_manager_get_app_filter (self->manager, + getuid (), + MCT_GET_APP_FILTER_FLAGS_NONE, + self->cancellable, + &error); + if (error) + { + g_warning ("Error retrieving app filter: %s", error->message); + return; + } + + self->app_filter_id = g_signal_connect (self->manager, "app-filter-changed", + G_CALLBACK (app_filter_changed_cb), self); +#endif + populate_applications (self); + + self->monitor = g_app_info_monitor_get (); + self->monitor_id = g_signal_connect_object (self->monitor, "changed", G_CALLBACK (apps_changed), self, G_CONNECT_SWAPPED); + + g_dbus_proxy_new_for_bus (G_BUS_TYPE_SESSION, + G_DBUS_PROXY_FLAGS_NONE, + NULL, + "org.freedesktop.impl.portal.PermissionStore", + "/org/freedesktop/impl/portal/PermissionStore", + "org.freedesktop.impl.portal.PermissionStore", + cc_panel_get_cancellable (CC_PANEL (self)), + on_perm_store_ready, + self); + + self->globs = parse_globs (); + self->search_providers = parse_search_providers (); + + /* Select the first row */ + row = gtk_list_box_get_row_at_index (self->sidebar_listbox, 0); + gtk_list_box_select_row (self->sidebar_listbox, row); + g_signal_emit_by_name (row, "activate"); +} diff --git a/panels/applications/cc-applications-panel.css b/panels/applications/cc-applications-panel.css new file mode 100644 index 0000000..538a9c9 --- /dev/null +++ b/panels/applications/cc-applications-panel.css @@ -0,0 +1,11 @@ +.section-title { + font-weight: bold; +} + +.section-subtitle { + opacity: 0.55; +} + +.sidebar-icon.fullcolor { + opacity: 1; +} diff --git a/panels/applications/cc-applications-panel.h b/panels/applications/cc-applications-panel.h new file mode 100644 index 0000000..c6c8882 --- /dev/null +++ b/panels/applications/cc-applications-panel.h @@ -0,0 +1,30 @@ +/* cc-applications-panel.h + * + * Copyright 2018 Georges Basile Stavracas Neto <georges.stavracas@gmail.com> + * + * 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 <http://www.gnu.org/licenses/>. + * + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +#pragma once + +#include <shell/cc-panel.h> + +G_BEGIN_DECLS + +#define CC_TYPE_APPLICATIONS_PANEL (cc_applications_panel_get_type()) +G_DECLARE_FINAL_TYPE (CcApplicationsPanel, cc_applications_panel, CC, APPLICATIONS_PANEL, CcPanel) + +G_END_DECLS diff --git a/panels/applications/cc-applications-panel.ui b/panels/applications/cc-applications-panel.ui new file mode 100644 index 0000000..7a08465 --- /dev/null +++ b/panels/applications/cc-applications-panel.ui @@ -0,0 +1,608 @@ +<?xml version="1.0" encoding="UTF-8"?> +<interface> + <template class="CcApplicationsPanel" parent="CcPanel"> + <property name="visible">True</property> + <property name="can-focus">False</property> + <child> + <object class="GtkScrolledWindow" id="main_scroll"> + <property name="visible">1</property> + <property name="hscrollbar-policy">never</property> + <child> + <object class="HdyClamp"> + <property name="visible">True</property> + <property name="margin_top">32</property> + <property name="margin_bottom">32</property> + <property name="margin_start">12</property> + <property name="margin_end">12</property> + <child> + <object class="GtkStack" id="stack"> + <property name="visible">1</property> + <child> + <object class="GtkBox" id="empty_box"> + <property name="visible">1</property> + <property name="orientation">vertical</property> + <property name="valign">center</property> + <child> + <object class="GtkImage"> + <property name="visible">1</property> + <property name="valign">start</property> + <property name="pixel-size">80</property> + <property name="icon-name">org.gnome.Software-symbolic</property> + <style> + <class name="dim-label"/> + </style> + </object> + <packing> + <property name="fill">0</property> + </packing> + </child> + <child> + <object class="GtkLabel"> + <property name="visible">1</property> + <property name="margin-bottom">15</property> + <property name="label" translatable="yes">No applications</property> + <style> + <class name="dim-label"/> + </style> + <attributes> + <attribute name="scale" value="1.2"/> + </attributes> + </object> + <packing> + <property name="position">1</property> + </packing> + </child> + <child> + <object class="GtkButton"> + <property name="label" translatable="yes">Install some…</property> + <property name="visible">1</property> + <property name="can-focus">1</property> + <property name="receives-default">1</property> + <property name="halign">center</property> + <signal name="clicked" handler="open_software_cb" object="CcApplicationsPanel" swapped="yes"/> + </object> + <packing> + <property name="fill">0</property> + <property name="position">2</property> + </packing> + </child> + </object> + </child> + <child> + <object class="GtkBox" id="settings_box"> + <property name="visible">1</property> + <property name="orientation">vertical</property> + <property name="spacing">24</property> + <property name="hexpand">1</property> + <child> + <object class="GtkBox" id="permission_section"> + <property name="visible">1</property> + <property name="orientation">vertical</property> + <property name="spacing">12</property> + <style> + <class name="section"/> + </style> + <child> + <object class="GtkBox"> + <property name="visible">1</property> + <property name="orientation">vertical</property> + <property name="spacing">6</property> + <child> + <object class="GtkLabel"> + <property name="visible">1</property> + <property name="xalign">0</property> + <property name="label" translatable="yes">Permissions & Access</property> + <style> + <class name="section-title"/> + </style> + </object> + </child> + <child> + <object class="GtkLabel"> + <property name="visible">1</property> + <property name="xalign">0</property> + <property name="wrap">1</property> + <property name="max-width-chars">50</property> + <property name="label" translatable="yes">Data and services that this app has asked for access to and permissions that it requires.</property> + <style> + <class name="section-subtitle"/> + </style> + </object> + </child> + </object> + </child> + <child> + <object class="GtkListBox" id="permission_list"> + <property name="visible">1</property> + <property name="selection-mode">none</property> + <signal name="row-activated" handler="permission_row_activated_cb" object="CcApplicationsPanel" swapped="yes"/> + <child> + <object class="CcToggleRow" id="camera"> + <property name="title" translatable="yes">Camera</property> + <signal name="notify::allowed" handler="camera_cb" object="CcApplicationsPanel" swapped="yes"/> + </object> + </child> + <child> + <object class="CcInfoRow" id="no_camera"> + <property name="title" translatable="yes">Camera</property> + <property name="info" translatable="yes">Disabled</property> + </object> + </child> + <child> + <object class="CcToggleRow" id="microphone"> + <property name="title" translatable="yes">Microphone</property> + <signal name="notify::allowed" handler="microphone_cb" object="CcApplicationsPanel" swapped="yes"/> + </object> + </child> + <child> + <object class="CcInfoRow" id="no_microphone"> + <property name="title" translatable="yes">Microphone</property> + <property name="info" translatable="yes">Disabled</property> + </object> + </child> + <child> + <object class="CcToggleRow" id="location"> + <property name="title" translatable="yes">Location Services</property> + <signal name="notify::allowed" handler="location_cb" object="CcApplicationsPanel" swapped="yes"/> + </object> + </child> + <child> + <object class="CcInfoRow" id="no_location"> + <property name="title" translatable="yes">Location Services</property> + <property name="info" translatable="yes">Disabled</property> + </object> + </child> + <child> + <object class="CcInfoRow" id="builtin"> + <property name="title" translatable="yes">Built-in Permissions</property> + <property name="info" translatable="yes">Cannot be changed</property> + <property name="has-expander">True</property> + <property name="is-link">True</property> + </object> + </child> + <style> + <class name="view"/> + <class name="frame"/> + </style> + </object> + </child> + <child> + <object class="GtkLabel"> + <property name="visible">1</property> + <property name="xalign">0</property> + <property name="wrap">1</property> + <property name="max-width-chars">50</property> + <property name="label" translatable="yes">Individual permissions for applications can be reviewed in the <a href="privacy">Privacy</a> Settings.</property> + <property name="use-markup">1</property> + <signal name="activate-link" handler="privacy_link_cb" object="CcApplicationsPanel" swapped="yes"/> + </object> + </child> + </object> + </child> + <child> + <object class="GtkBox" id="integration_section"> + <property name="visible">1</property> + <property name="orientation">vertical</property> + <property name="spacing">12</property> + <style> + <class name="section"/> + </style> + <child> + <object class="GtkBox"> + <property name="visible">1</property> + <property name="orientation">vertical</property> + <property name="spacing">6</property> + <child> + <object class="GtkLabel"> + <property name="visible">1</property> + <property name="xalign">0</property> + <property name="label" translatable="yes">Integration</property> + <style> + <class name="section-title"/> + </style> + </object> + </child> + <child> + <object class="GtkLabel"> + <property name="visible">1</property> + <property name="xalign">0</property> + <property name="wrap">1</property> + <property name="max-width-chars">50</property> + <property name="label" translatable="yes">System features used by this application.</property> + <style> + <class name="section-subtitle"/> + </style> + </object> + </child> + </object> + </child> + <child> + <object class="GtkListBox" id="integration_list"> + <property name="visible">1</property> + <property name="selection-mode">none</property> + <child> + <object class="CcToggleRow" id="search"> + <property name="title" translatable="yes">Search</property> + <signal name="notify::allowed" handler="search_cb" object="CcApplicationsPanel" swapped="yes"/> + </object> + </child> + <child> + <object class="CcInfoRow" id="no_search"> + <property name="title" translatable="yes">Search</property> + <property name="info" translatable="yes">Disabled</property> + </object> + </child> + <child> + <object class="CcToggleRow" id="notification"> + <property name="title" translatable="yes">Notifications</property> + <signal name="notify::allowed" handler="notification_cb" object="CcApplicationsPanel" swapped="yes"/> + </object> + </child> + <child> + <object class="CcToggleRow" id="background"> + <property name="title" translatable="yes">Run in background</property> + <signal name="notify::allowed" handler="background_cb" swapped="yes"/> + </object> + </child> + <child> + <object class="CcToggleRow" id="wallpaper"> + <property name="title" translatable="yes">Set Desktop Background</property> + <signal name="notify::allowed" handler="wallpaper_cb" swapped="yes"/> + </object> + </child> + <child> + <object class="CcToggleRow" id="sound"> + <property name="title" translatable="yes">Sounds</property> + <signal name="notify::allowed" handler="sound_cb" object="CcApplicationsPanel" swapped="yes"/> + </object> + </child> + <child> + <object class="CcInfoRow" id="no_sound"> + <property name="title" translatable="yes">Sounds</property> + <property name="info" translatable="yes">Disabled</property> + </object> + </child> + <child> + <object class="CcToggleRow" id="shortcuts"> + <property name="title" translatable="yes">Inhibit system keyboard shortcuts</property> + <signal name="notify::allowed" handler="shortcuts_cb" swapped="yes"/> + </object> + </child> + <style> + <class name="view"/> + <class name="frame"/> + </style> + </object> + </child> + </object> + </child> + <child> + <object class="GtkBox" id="handler_section"> + <property name="visible">1</property> + <property name="orientation">vertical</property> + <property name="spacing">12</property> + <style> + <class name="section"/> + </style> + <child> + <object class="GtkBox"> + <property name="visible">1</property> + <property name="spacing">6</property> + <child> + <object class="GtkBox"> + <property name="visible">1</property> + <property name="orientation">vertical</property> + <property name="spacing">6</property> + <child> + <object class="GtkLabel"> + <property name="visible">1</property> + <property name="xalign">0</property> + <property name="label" translatable="yes">Default Handlers</property> + <style> + <class name="section-title"/> + </style> + </object> + </child> + <child> + <object class="GtkLabel"> + <property name="visible">1</property> + <property name="xalign">0</property> + <property name="wrap">1</property> + <property name="max-width-chars">50</property> + <property name="label" translatable="yes">Types of files and links that this application opens.</property> + <style> + <class name="section-subtitle"/> + </style> + </object> + </child> + </object> + <packing> + <property name="expand">1</property> + </packing> + </child> + <child> + <object class="GtkButton" id="handler_reset"> + <property name="visible">1</property> + <property name="halign">end</property> + <property name="valign">center</property> + <property name="label" translatable="yes">Reset</property> + <signal name="clicked" handler="handler_reset_cb" object="CcApplicationsPanel" swapped="yes"/> + </object> + </child> + </object> + </child> + <child> + <object class="GtkListBox" id="handler_list"> + <property name="visible">1</property> + <property name="selection-mode">none</property> + <signal name="row-activated" handler="handler_row_activated_cb" object="CcApplicationsPanel" swapped="yes"/> + <style> + <class name="view"/> + <class name="frame"/> + </style> + </object> + </child> + </object> + </child> + <child> + <object class="GtkBox" id="usage_section"> + <property name="visible">1</property> + <property name="orientation">vertical</property> + <property name="spacing">12</property> + <style> + <class name="section"/> + </style> + <child> + <object class="GtkBox"> + <property name="visible">1</property> + <property name="orientation">vertical</property> + <property name="spacing">6</property> + <child> + <object class="GtkLabel"> + <property name="visible">1</property> + <property name="xalign">0</property> + <property name="label" translatable="yes">Usage</property> + <style> + <class name="section-title"/> + </style> + </object> + </child> + <child> + <object class="GtkLabel"> + <property name="visible">1</property> + <property name="xalign">0</property> + <property name="wrap">1</property> + <property name="max-width-chars">50</property> + <property name="label" translatable="yes">How much resources this application is using.</property> + <style> + <class name="section-subtitle"/> + </style> + </object> + </child> + </object> + </child> + <child> + <object class="GtkListBox" id="usage_list"> + <property name="visible">1</property> + <property name="selection-mode">none</property> + <signal name="row-activated" handler="storage_row_activated_cb" object="CcApplicationsPanel" swapped="yes"/> + <child> + <object class="CcInfoRow" id="storage"> + <property name="title" translatable="yes">Storage</property> + <property name="info">unknown</property> + <property name="has-expander">1</property> + <property name="is-link">1</property> + </object> + </child> + <style> + <class name="view"/> + <class name="frame"/> + </style> + </object> + </child> + </object> + </child> + </object> + </child> + </object> + </child> + </object> + </child> + </object> + </child> + </template> + <object class="GtkLabel" id="title_label"> + <property name="visible">1</property> + <property name="label" translatable="yes">Applications</property> + <style> + <class name="title"/> + </style> + </object> + <object class="GtkButton" id="header_button"> + <property name="visible">1</property> + <property name="label" translatable="yes">Open in Software</property> + </object> + + <!-- Sidebar --> + <object class="GtkBox" id="sidebar_box"> + <property name="visible">True</property> + <property name="orientation">vertical</property> + <child> + <object class="GtkSearchEntry" id="sidebar_search_entry"> + <property name="visible">True</property> + <property name="can-focus">True</property> + <property name="has-focus">True</property> + <property name="margin">12</property> + <property name="margin-bottom">6</property> + <signal name="activate" handler="on_sidebar_search_entry_activated_cb" object="CcApplicationsPanel" swapped="yes" /> + <signal name="search-changed" handler="on_sidebar_search_entry_search_changed_cb" object="CcApplicationsPanel" swapped="yes" /> + <signal name="stop-search" handler="on_sidebar_search_entry_search_stopped_cb" object="CcApplicationsPanel" swapped="yes" /> + </object> + </child> + <child> + <object class="GtkListBox" id="sidebar_listbox"> + <property name="visible">True</property> + <property name="vexpand">True</property> + <property name="selection-mode">browse</property> + + <child type="placeholder"> + <object class="GtkBox" id="empty_search_placeholder"> + <property name="visible">True</property> + <property name="can_focus">False</property> + <property name="halign">center</property> + <property name="valign">center</property> + <property name="expand">True</property> + <property name="border_width">18</property> + <property name="orientation">vertical</property> + <property name="spacing">6</property> + <child> + <object class="GtkImage"> + <property name="visible">True</property> + <property name="can_focus">False</property> + <property name="pixel_size">64</property> + <property name="icon_name">edit-find-symbolic</property> + <style> + <class name="dim-label"/> + </style> + </object> + </child> + <child> + <object class="GtkLabel"> + <property name="visible">True</property> + <property name="can_focus">False</property> + <property name="label" translatable="yes">No results found</property> + <attributes> + <attribute name="weight" value="bold"/> + <attribute name="scale" value="1.44"/> + </attributes> + </object> + </child> + <child> + <object class="GtkLabel"> + <property name="visible">True</property> + <property name="can_focus">False</property> + <property name="label" translatable="yes">Try a different search</property> + <style> + <class name="dim-label"/> + </style> + </object> + </child> + </object> + </child> + + </object> + </child> + </object> + + <!-- Built-in Permissions dialog --> + <object class="GtkDialog" id="builtin_dialog"> + <property name="title" translatable="yes">Built-in Permissions</property> + <property name="modal">1</property> + <property name="type-hint">dialog</property> + <property name="use-header-bar">1</property> + <property name="resizable">0</property> + <property name="border-width">24</property> + <signal name="delete-event" handler="gtk_widget_hide_on_delete"/> + <child internal-child="vbox"> + <object class="GtkBox"> + <property name="visible">1</property> + <property name="orientation">vertical</property> + <property name="spacing">12</property> + <child> + <object class="GtkLabel" id="builtin_label"> + <property name="visible">1</property> + <property name="wrap">1</property> + <property name="max-width-chars">50</property> + <property name="label">Yadda Yadda</property> + </object> + </child> + <child> + <object class="GtkListBox" id="builtin_list"> + <property name="visible">1</property> + <property name="selection-mode">none</property> + <style> + <class name="view"/> + <class name="frame"/> + </style> + </object> + </child> + </object> + </child> + </object> + + <!-- Storage dialog --> + <object class="GtkDialog" id="storage_dialog"> + <property name="title" translatable="yes">Storage</property> + <property name="modal">1</property> + <property name="type-hint">dialog</property> + <property name="use-header-bar">1</property> + <property name="resizable">0</property> + <property name="border-width">24</property> + <signal name="delete-event" handler="gtk_widget_hide_on_delete"/> + <child internal-child="vbox"> + <object class="GtkBox"> + <property name="visible">1</property> + <property name="orientation">vertical</property> + <property name="spacing">12</property> + <child> + <object class="GtkLabel"> + <property name="visible">1</property> + <property name="wrap">1</property> + <property name="max-width-chars">50</property> + <property name="label" translatable="yes">How much disk space this application is occupying with app data and caches.</property> + </object> + </child> + <child> + <object class="GtkListBox" id="storage_list"> + <property name="visible">1</property> + <property name="selection-mode">none</property> + <child> + <object class="CcInfoRow" id="app"> + <property name="title" translatable="yes">Application</property> + <property name="info">Unknown</property> + </object> + </child> + <child> + <object class="CcInfoRow" id="data"> + <property name="title" translatable="yes">Data</property> + <property name="info">Unknown</property> + </object> + </child> + <child> + <object class="CcInfoRow" id="cache"> + <property name="title" translatable="yes">Cache</property> + <property name="info">Unknown</property> + </object> + </child> + <child> + <object class="CcInfoRow" id="total"> + <property name="title" translatable="yes"><b>Total</b></property> + <property name="use-markup">1</property> + <property name="info">Unknown</property> + </object> + </child> + <style> + <class name="view"/> + <class name="frame"/> + </style> + </object> + </child> + <child> + <object class="GtkBox"> + <property name="visible">1</property> + <child> + <object class="GtkButton" id="clear_cache_button"> + <property name="visible">1</property> + <property name="label" translatable="yes">Clear Cache…</property> + <signal name="clicked" handler="clear_cache_cb" object="CcApplicationsPanel" swapped="yes"/> + </object> + <packing> + <property name="pack-type">end</property> + </packing> + </child> + </object> + </child> + </object> + </child> + </object> +</interface> diff --git a/panels/applications/cc-applications-row.c b/panels/applications/cc-applications-row.c new file mode 100644 index 0000000..08516ac --- /dev/null +++ b/panels/applications/cc-applications-row.c @@ -0,0 +1,108 @@ +/* cc-applications-row.c + * + * Copyright 2018 Matthias Clasen <matthias.clasen@gmail.com> + * + * 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 <http://www.gnu.org/licenses/>. + * + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +#include <config.h> +#include <glib/gi18n.h> + +#include "cc-applications-row.h" +#include "cc-applications-resources.h" + +struct _CcApplicationsRow +{ + GtkListBoxRow parent; + + GAppInfo *info; + gchar *sortkey; + + GtkWidget *box; + GtkWidget *image; + GtkWidget *label; +}; + +G_DEFINE_TYPE (CcApplicationsRow, cc_applications_row, GTK_TYPE_LIST_BOX_ROW) + +static void +cc_applications_row_finalize (GObject *object) +{ + CcApplicationsRow *self = CC_APPLICATIONS_ROW (object); + + g_object_unref (self->info); + g_free (self->sortkey); + + G_OBJECT_CLASS (cc_applications_row_parent_class)->finalize (object); +} + +static void +cc_applications_row_class_init (CcApplicationsRowClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass); + + object_class->finalize = cc_applications_row_finalize; + + gtk_widget_class_set_template_from_resource (widget_class, "/org/gnome/control-center/applications/cc-applications-row.ui"); + + gtk_widget_class_bind_template_child (widget_class, CcApplicationsRow, box); + gtk_widget_class_bind_template_child (widget_class, CcApplicationsRow, image); + gtk_widget_class_bind_template_child (widget_class, CcApplicationsRow, label); +} + +static void +cc_applications_row_init (CcApplicationsRow *self) +{ + gtk_widget_init_template (GTK_WIDGET (self)); +} + +CcApplicationsRow * +cc_applications_row_new (GAppInfo *info) +{ + CcApplicationsRow *self; + g_autofree gchar *key = NULL; + GIcon *icon; + + self = g_object_new (CC_TYPE_APPLICATIONS_ROW, NULL); + + self->info = g_object_ref (info); + + key = g_utf8_casefold (g_app_info_get_display_name (info), -1); + self->sortkey = g_utf8_collate_key (key, -1); + + icon = g_app_info_get_icon (info); + if (icon != NULL) + gtk_image_set_from_gicon (GTK_IMAGE (self->image), g_app_info_get_icon (info), GTK_ICON_SIZE_BUTTON); + else + gtk_image_set_from_icon_name (GTK_IMAGE (self->image), "application-x-executable", GTK_ICON_SIZE_BUTTON); + + gtk_label_set_label (GTK_LABEL (self->label), g_app_info_get_display_name (info)); + + return self; +} + +GAppInfo * +cc_applications_row_get_info (CcApplicationsRow *self) +{ + return self->info; +} + +const gchar * +cc_applications_row_get_sort_key (CcApplicationsRow *self) +{ + return self->sortkey; +} diff --git a/panels/applications/cc-applications-row.h b/panels/applications/cc-applications-row.h new file mode 100644 index 0000000..7ca44e1 --- /dev/null +++ b/panels/applications/cc-applications-row.h @@ -0,0 +1,36 @@ +/* cc-applications-row.h + * + * Copyright 2018 Matthias Clasen <matthias.clasen@gmail.com> + * + * 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 <http://www.gnu.org/licenses/>. + * + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +#pragma once + +#include <gtk/gtk.h> + +G_BEGIN_DECLS + +#define CC_TYPE_APPLICATIONS_ROW (cc_applications_row_get_type()) +G_DECLARE_FINAL_TYPE (CcApplicationsRow, cc_applications_row, CC, APPLICATIONS_ROW, GtkListBoxRow) + +CcApplicationsRow* cc_applications_row_new (GAppInfo *info); + +GAppInfo* cc_applications_row_get_info (CcApplicationsRow *row); + +const gchar* cc_applications_row_get_sort_key (CcApplicationsRow *row); + +G_END_DECLS diff --git a/panels/applications/cc-applications-row.ui b/panels/applications/cc-applications-row.ui new file mode 100644 index 0000000..819e48a --- /dev/null +++ b/panels/applications/cc-applications-row.ui @@ -0,0 +1,32 @@ +<?xml version="1.0" encoding="UTF-8"?> +<interface> + <template class="CcApplicationsRow" parent="GtkListBoxRow"> + <property name="visible">True</property> + <property name="can-focus">True</property> + <child> + <object class="GtkBox" id="box"> + <property name="visible">1</property> + <property name="border-width">6</property> + <property name="spacing">12</property> + <child> + <object class="GtkImage" id="image"> + <property name="visible">1</property> + <property name="pixel-size">32</property> + <style> + <class name="sidebar-icon"/> + <class name="fullcolor"/> + <class name="lowres-icon"/> + </style> + </object> + </child> + <child> + <object class="GtkLabel" id="label"> + <property name="visible">1</property> + <property name="xalign">0</property> + <property name="ellipsize">end</property> + </object> + </child> + </object> + </child> + </template> +</interface> diff --git a/panels/applications/cc-info-row.c b/panels/applications/cc-info-row.c new file mode 100644 index 0000000..3a8d88a --- /dev/null +++ b/panels/applications/cc-info-row.c @@ -0,0 +1,215 @@ +/* cc-info-row.c + * + * Copyright 2018 Matthias Clasen <matthias.clasen@gmail.com> + * + * 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 <http://www.gnu.org/licenses/>. + * + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +#include <config.h> +#include <glib/gi18n.h> + +#include "cc-info-row.h" +#include "cc-applications-resources.h" + +struct _CcInfoRow +{ + GtkListBoxRow parent; + + GtkWidget *title; + GtkWidget *info; + GtkWidget *expander; + + gboolean expanded; + gboolean link; +}; + +G_DEFINE_TYPE (CcInfoRow, cc_info_row, GTK_TYPE_LIST_BOX_ROW) + +enum +{ + PROP_0, + PROP_TITLE, + PROP_USE_MARKUP, + PROP_INFO, + PROP_HAS_EXPANDER, + PROP_IS_LINK, + PROP_EXPANDED +}; + +static void +cc_info_row_get_property (GObject *object, + guint prop_id, + GValue *value, + GParamSpec *pspec) +{ + CcInfoRow *row = CC_INFO_ROW (object); + + switch (prop_id) + { + case PROP_TITLE: + g_value_set_string (value, gtk_label_get_label (GTK_LABEL (row->title))); + break; + case PROP_INFO: + g_value_set_string (value, gtk_label_get_label (GTK_LABEL (row->info))); + break; + case PROP_HAS_EXPANDER: + g_value_set_boolean (value, gtk_widget_get_visible (row->expander)); + break; + case PROP_USE_MARKUP: + g_value_set_boolean (value, gtk_label_get_use_markup (GTK_LABEL (row->title))); + break; + case PROP_IS_LINK: + g_value_set_boolean (value, row->link); + break; + case PROP_EXPANDED: + g_value_set_boolean (value, cc_info_row_get_expanded (row)); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} + +static void +update_expander (CcInfoRow *row) +{ + if (row->link) + gtk_image_set_from_icon_name (GTK_IMAGE (row->expander), "go-next-symbolic", GTK_ICON_SIZE_BUTTON); + else if (row->expanded) + gtk_image_set_from_icon_name (GTK_IMAGE (row->expander), "pan-down-symbolic", GTK_ICON_SIZE_BUTTON); + else + gtk_image_set_from_icon_name (GTK_IMAGE (row->expander), "pan-end-symbolic", GTK_ICON_SIZE_BUTTON); +} + +static void +cc_info_row_set_property (GObject *object, + guint prop_id, + const GValue *value, + GParamSpec *pspec) +{ + CcInfoRow *row = CC_INFO_ROW (object); + + switch (prop_id) + { + case PROP_TITLE: + gtk_label_set_label (GTK_LABEL (row->title), g_value_get_string (value)); + break; + + case PROP_INFO: + gtk_label_set_label (GTK_LABEL (row->info), g_value_get_string (value)); + break; + + case PROP_HAS_EXPANDER: + gtk_widget_set_visible (row->expander, g_value_get_boolean (value)); + gtk_list_box_row_set_activatable (GTK_LIST_BOX_ROW (row), g_value_get_boolean (value)); + break; + + case PROP_USE_MARKUP: + gtk_label_set_use_markup (GTK_LABEL (row->title), g_value_get_boolean (value)); + break; + + case PROP_IS_LINK: + row->link = g_value_get_boolean (value); + update_expander (row); + break; + + case PROP_EXPANDED: + cc_info_row_set_expanded (row, g_value_get_boolean (value)); + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} + +static void +cc_info_row_class_init (CcInfoRowClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass); + + object_class->get_property = cc_info_row_get_property; + object_class->set_property = cc_info_row_set_property; + + gtk_widget_class_set_template_from_resource (widget_class, "/org/gnome/control-center/applications/cc-info-row.ui"); + + g_object_class_install_property (object_class, + PROP_TITLE, + g_param_spec_string ("title", "title", "title", + NULL, G_PARAM_READWRITE)); + + g_object_class_install_property (object_class, + PROP_INFO, + g_param_spec_string ("info", "info", "info", + NULL, G_PARAM_READWRITE)); + + g_object_class_install_property (object_class, + PROP_USE_MARKUP, + g_param_spec_boolean ("use-markup", "use-markup", "use-markup", + FALSE, G_PARAM_READWRITE)); + + g_object_class_install_property (object_class, + PROP_HAS_EXPANDER, + g_param_spec_boolean ("has-expander", "has-expander", "has-expander", + FALSE, G_PARAM_READWRITE)); + + g_object_class_install_property (object_class, + PROP_EXPANDED, + g_param_spec_boolean ("expanded", "expanded", "expanded", + FALSE, G_PARAM_READWRITE)); + + g_object_class_install_property (object_class, + PROP_IS_LINK, + g_param_spec_boolean ("is-link", "is-link", "is-link", + FALSE, G_PARAM_READWRITE)); + + gtk_widget_class_bind_template_child (widget_class, CcInfoRow, title); + gtk_widget_class_bind_template_child (widget_class, CcInfoRow, info); + gtk_widget_class_bind_template_child (widget_class, CcInfoRow, expander); +} + +static void +cc_info_row_init (CcInfoRow *self) +{ + gtk_widget_init_template (GTK_WIDGET (self)); +} + +CcInfoRow * +cc_info_row_new (void) +{ + return CC_INFO_ROW (g_object_new (CC_TYPE_INFO_ROW, NULL)); +} + +gboolean +cc_info_row_get_expanded (CcInfoRow *row) +{ + return row->expanded; +} + +void +cc_info_row_set_expanded (CcInfoRow *row, + gboolean expanded) +{ + if (row->expanded == expanded) + return; + + row->expanded = expanded; + update_expander (row); + + g_object_notify (G_OBJECT (row), "expanded"); +} + diff --git a/panels/applications/cc-info-row.h b/panels/applications/cc-info-row.h new file mode 100644 index 0000000..57b9d4a --- /dev/null +++ b/panels/applications/cc-info-row.h @@ -0,0 +1,37 @@ +/* cc-info-row.h + * + * Copyright 2018 Matthias Clasen <matthias.clasen@gmail.com> + * + * 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 <http://www.gnu.org/licenses/>. + * + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +#pragma once + +#include <gtk/gtk.h> + +G_BEGIN_DECLS + +#define CC_TYPE_INFO_ROW (cc_info_row_get_type()) +G_DECLARE_FINAL_TYPE (CcInfoRow, cc_info_row, CC, INFO_ROW, GtkListBoxRow) + +CcInfoRow* cc_info_row_new (void); + +void cc_info_row_set_expanded (CcInfoRow *row, + gboolean expanded); + +gboolean cc_info_row_get_expanded (CcInfoRow *row); + +G_END_DECLS diff --git a/panels/applications/cc-info-row.ui b/panels/applications/cc-info-row.ui new file mode 100644 index 0000000..dcc7a42 --- /dev/null +++ b/panels/applications/cc-info-row.ui @@ -0,0 +1,41 @@ +<?xml version="1.0" encoding="UTF-8"?> +<interface> + <template class="CcInfoRow" parent="GtkListBoxRow"> + <property name="visible">True</property> + <property name="can-focus">True</property> + <property name="activatable">False</property> + <child> + <object class="GtkBox"> + <property name="visible">1</property> + <property name="border-width">12</property> + <property name="spacing">12</property> + <child> + <object class="GtkLabel" id="title"> + <property name="visible">1</property> + <property name="xalign">0</property> + <property name="hexpand">1</property> + <property name="ellipsize">end</property> + </object> + </child> + <child> + <object class="GtkLabel" id="info"> + <property name="visible">1</property> + <property name="valign">center</property> + <style> + <class name="dim-label"/> + </style> + </object> + </child> + <child> + <object class="GtkImage" id="expander"> + <property name="valign">center</property> + <property name="icon-name">pan-end-symbolic</property> + <style> + <class name="dim-label"/> + </style> + </object> + </child> + </object> + </child> + </template> +</interface> diff --git a/panels/applications/cc-snap-row.c b/panels/applications/cc-snap-row.c new file mode 100644 index 0000000..40a7fc4 --- /dev/null +++ b/panels/applications/cc-snap-row.c @@ -0,0 +1,316 @@ +/* cc-snap-row.c + * + * Copyright 2019 Canonical Ltd. + * + * 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 <http://www.gnu.org/licenses/>. + * + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +#include <config.h> +#include <glib/gi18n.h> + +#include "cc-snap-row.h" +#include "cc-applications-resources.h" + +struct _CcSnapRow +{ + GtkListBoxRow parent; + + GtkLabel *title_label; + GtkSwitch *slot_toggle; + GtkComboBox *slots_combo; + GtkListStore *slots_combo_model; + + GCancellable *cancellable; + + SnapdPlug *plug; + SnapdSlot *connected_slot; + GPtrArray *slots; +}; + +G_DEFINE_TYPE (CcSnapRow, cc_snap_row, GTK_TYPE_LIST_BOX_ROW) + +typedef struct +{ + CcSnapRow *self; + SnapdSlot *slot; +} ConnectData; + +static void +update_state (CcSnapRow *self) +{ + gboolean have_single_option; + GtkTreeIter iter; + + have_single_option = self->slots->len == 1; + gtk_widget_set_visible (GTK_WIDGET (self->slot_toggle), have_single_option); + gtk_widget_set_visible (GTK_WIDGET (self->slots_combo), !have_single_option); + + gtk_switch_set_active (self->slot_toggle, self->connected_slot != NULL); + + if (gtk_tree_model_get_iter_first (GTK_TREE_MODEL (self->slots_combo_model), &iter)) + { + do + { + SnapdSlot *slot; + + gtk_tree_model_get (GTK_TREE_MODEL (self->slots_combo_model), &iter, 0, &slot, -1); + if (slot == self->connected_slot) + gtk_combo_box_set_active_iter (self->slots_combo, &iter); + } while (gtk_tree_model_iter_next (GTK_TREE_MODEL (self->slots_combo_model), &iter)); + } +} + +static void +disable_controls (CcSnapRow *self) +{ + gtk_widget_set_sensitive (GTK_WIDGET (self->slot_toggle), FALSE); + gtk_widget_set_sensitive (GTK_WIDGET (self->slots_combo), FALSE); +} + +static void +enable_controls (CcSnapRow *self) +{ + gtk_widget_set_sensitive (GTK_WIDGET (self->slot_toggle), TRUE); + gtk_widget_set_sensitive (GTK_WIDGET (self->slots_combo), TRUE); +} + +static ConnectData * +connect_data_new (CcSnapRow *self, SnapdSlot *slot) +{ + ConnectData *data; + + data = g_new0 (ConnectData, 1); + data->self = self; + data->slot = g_object_ref (slot); + + return data; +} + +static void +connect_data_free (ConnectData *data) +{ + g_clear_object (&data->slot); + g_free (data); +} + +G_DEFINE_AUTOPTR_CLEANUP_FUNC (ConnectData, connect_data_free) + +static void +connect_interface_cb (GObject *client, GAsyncResult *result, gpointer user_data) +{ + g_autoptr(ConnectData) data = user_data; + CcSnapRow *self = data->self; + SnapdSlot *slot = data->slot; + g_autoptr(GError) error = NULL; + + if (snapd_client_connect_interface_finish (SNAPD_CLIENT (client), result, &error)) + { + g_clear_object (&self->connected_slot); + self->connected_slot = g_object_ref (slot); + } + else + { + if (g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED)) + return; + g_warning ("Failed to connect interface: %s", error->message); + } + + update_state (self); + enable_controls (self); +} + +static void +connect_plug (CcSnapRow *self, SnapdSlot *slot) +{ + g_autoptr(SnapdClient) client = NULL; + + /* already connected */ + if (self->connected_slot == slot) + return; + + disable_controls (self); + + client = snapd_client_new (); + snapd_client_connect_interface_async (client, + snapd_plug_get_snap (self->plug), snapd_plug_get_name (self->plug), + snapd_slot_get_snap (slot), snapd_slot_get_name (slot), + NULL, NULL, + self->cancellable, + connect_interface_cb, connect_data_new (self, slot)); +} + +static void +disconnect_interface_cb (GObject *client, GAsyncResult *result, gpointer user_data) +{ + CcSnapRow *self = user_data; + g_autoptr(GError) error = NULL; + + if (snapd_client_disconnect_interface_finish (SNAPD_CLIENT (client), result, &error)) + { + g_clear_object (&self->connected_slot); + } + else + { + if (g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED)) + return; + g_warning ("Failed to disconnect interface: %s", error->message); + } + + update_state (self); + enable_controls (self); +} + +static void +disconnect_plug (CcSnapRow *self) +{ + g_autoptr(SnapdClient) client = NULL; + + /* already disconnected */ + if (self->connected_slot == NULL) + return; + + disable_controls (self); + + client = snapd_client_new (); + snapd_client_disconnect_interface_async (client, + snapd_plug_get_snap (self->plug), snapd_plug_get_name (self->plug), + NULL, NULL, + NULL, NULL, + self->cancellable, + disconnect_interface_cb, self); +} + +static void +switch_changed_cb (CcSnapRow *self) +{ + if (gtk_switch_get_active (self->slot_toggle)) + { + if (self->slots->len == 1) + connect_plug (self, g_ptr_array_index (self->slots, 0)); + } + else + { + disconnect_plug (self); + } +} + +static void +combo_changed_cb (CcSnapRow *self) +{ + GtkTreeIter iter; + SnapdSlot *slot = NULL; + + if (!gtk_combo_box_get_active_iter (self->slots_combo, &iter)) + return; + + gtk_tree_model_get (GTK_TREE_MODEL (self->slots_combo_model), &iter, 0, &slot, -1); + if (slot != NULL) + connect_plug (self, slot); + else + disconnect_plug (self); +} + +static void +cc_snap_row_finalize (GObject *object) +{ + CcSnapRow *self = CC_SNAP_ROW (object); + + g_clear_object (&self->cancellable); + g_clear_object (&self->plug); + g_clear_pointer (&self->slots, g_ptr_array_unref); + + G_OBJECT_CLASS (cc_snap_row_parent_class)->finalize (object); +} + +static void +cc_snap_row_class_init (CcSnapRowClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass); + + object_class->finalize = cc_snap_row_finalize; + + gtk_widget_class_set_template_from_resource (widget_class, "/org/gnome/control-center/applications/cc-snap-row.ui"); + + gtk_widget_class_bind_template_child (widget_class, CcSnapRow, title_label); + gtk_widget_class_bind_template_child (widget_class, CcSnapRow, slot_toggle); + gtk_widget_class_bind_template_child (widget_class, CcSnapRow, slots_combo); + gtk_widget_class_bind_template_child (widget_class, CcSnapRow, slots_combo_model); + + gtk_widget_class_bind_template_callback (widget_class, combo_changed_cb); + gtk_widget_class_bind_template_callback (widget_class, switch_changed_cb); +} + +static void +cc_snap_row_init (CcSnapRow *self) +{ + gtk_widget_init_template (GTK_WIDGET (self)); +} + +CcSnapRow * +cc_snap_row_new (GCancellable *cancellable, SnapdInterface *interface, SnapdPlug *plug, GPtrArray *slots) +{ + CcSnapRow *self; + GPtrArray *connected_slots; + g_autofree gchar *label = NULL; + GtkTreeIter iter; + + g_return_val_if_fail (SNAPD_IS_PLUG (plug), NULL); + + self = CC_SNAP_ROW (g_object_new (CC_TYPE_SNAP_ROW, NULL)); + + self->cancellable = g_object_ref (cancellable); + self->plug = g_object_ref (plug); + self->slots = g_ptr_array_ref (slots); + + connected_slots = snapd_plug_get_connected_slots (plug); + if (connected_slots->len > 0) + { + SnapdSlotRef *connected_slot_ref = g_ptr_array_index (connected_slots, 0); + + for (int i = 0; i < slots->len; i++) + { + SnapdSlot *slot = g_ptr_array_index (slots, i); + + if (g_strcmp0 (snapd_slot_get_snap (slot), snapd_slot_ref_get_snap (connected_slot_ref)) == 0 && + g_strcmp0 (snapd_slot_get_name (slot), snapd_slot_ref_get_slot (connected_slot_ref)) == 0) + self->connected_slot = slot; + } + } + + if (interface != NULL) + label = snapd_interface_make_label (interface); + else + label = g_strdup (snapd_plug_get_interface (plug)); + gtk_label_set_label (self->title_label, label); + + /* Add option into combo box */ + gtk_list_store_append (self->slots_combo_model, &iter); + gtk_list_store_set (self->slots_combo_model, &iter, 1, "--", -1); + for (int i = 0; i < slots->len; i++) + { + SnapdSlot *slot = g_ptr_array_index (slots, i); + g_autofree gchar *label = NULL; + + label = g_strdup_printf ("%s:%s", snapd_slot_get_snap (slot), snapd_slot_get_name (slot)); + gtk_list_store_append (self->slots_combo_model, &iter); + gtk_list_store_set (self->slots_combo_model, &iter, 0, slot, 1, label, -1); + } + + update_state (self); + + return self; +} diff --git a/panels/applications/cc-snap-row.h b/panels/applications/cc-snap-row.h new file mode 100644 index 0000000..d37bb80 --- /dev/null +++ b/panels/applications/cc-snap-row.h @@ -0,0 +1,36 @@ +/* cc-snap-row.h + * + * Copyright 2019 Canonical Ltd. + * + * 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 <http://www.gnu.org/licenses/>. + * + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +#pragma once + +#include <gtk/gtk.h> +#include <snapd-glib/snapd-glib.h> + +G_BEGIN_DECLS + +#define CC_TYPE_SNAP_ROW (cc_snap_row_get_type()) +G_DECLARE_FINAL_TYPE (CcSnapRow, cc_snap_row, CC, SNAP_ROW, GtkListBoxRow) + +CcSnapRow* cc_snap_row_new (GCancellable *cancellable, + SnapdInterface *interface, + SnapdPlug *plug, + GPtrArray *slots); + +G_END_DECLS diff --git a/panels/applications/cc-snap-row.ui b/panels/applications/cc-snap-row.ui new file mode 100644 index 0000000..2d83f37 --- /dev/null +++ b/panels/applications/cc-snap-row.ui @@ -0,0 +1,54 @@ +<?xml version="1.0" encoding="UTF-8"?> +<interface> + <object class="GtkListStore" id="slots_combo_model"> + <columns> + <!-- column-name slot --> + <column type="GObject"/> + <!-- column-name label --> + <column type="gchararray"/> + </columns> + </object> + <template class="CcSnapRow" parent="GtkListBoxRow"> + <property name="visible">True</property> + <property name="can-focus">True</property> + <property name="activatable">False</property> + <child> + <object class="GtkBox"> + <property name="visible">True</property> + <property name="border-width">12</property> + <property name="spacing">12</property> + <child> + <object class="GtkLabel" id="title_label"> + <property name="visible">True</property> + <property name="xalign">0</property> + <property name="hexpand">1</property> + <property name="ellipsize">end</property> + </object> + </child> + <child> + <object class="GtkSwitch" id="slot_toggle"> + <property name="visible">True</property> + <property name="valign">center</property> + <signal name="notify::active" handler="switch_changed_cb" swapped="yes"/> + </object> + </child> + <child> + <object class="GtkComboBox" id="slots_combo"> + <property name="visible">True</property> + <property name="valign">center</property> + <property name="model">slots_combo_model</property> + <signal name="changed" handler="combo_changed_cb" swapped="yes"/> + <child> + <object class="GtkCellRendererText"> + <property name="ellipsize">end</property> + </object> + <attributes> + <attribute name="text">1</attribute> + </attributes> + </child> + </object> + </child> + </object> + </child> + </template> +</interface> diff --git a/panels/applications/cc-toggle-row.c b/panels/applications/cc-toggle-row.c new file mode 100644 index 0000000..4895cc4 --- /dev/null +++ b/panels/applications/cc-toggle-row.c @@ -0,0 +1,144 @@ +/* cc-toggle-row.c + * + * Copyright 2018 Matthias Clasen <matthias.clasen@gmail.com> + * + * 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 <http://www.gnu.org/licenses/>. + * + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +#include <config.h> +#include <glib/gi18n.h> + +#include "cc-toggle-row.h" +#include "cc-applications-resources.h" + +struct _CcToggleRow +{ + GtkListBoxRow parent; + + GtkWidget *title; + GtkWidget *toggle; +}; + +G_DEFINE_TYPE (CcToggleRow, cc_toggle_row, GTK_TYPE_LIST_BOX_ROW) + +enum +{ + PROP_0, + PROP_TITLE, + PROP_ALLOWED +}; + +static void +changed_cb (CcToggleRow *row) +{ + g_object_notify (G_OBJECT (row), "allowed"); +} + +static void +cc_toggle_row_get_property (GObject *object, + guint prop_id, + GValue *value, + GParamSpec *pspec) +{ + CcToggleRow *row = CC_TOGGLE_ROW (object); + + switch (prop_id) + { + case PROP_TITLE: + g_value_set_string (value, gtk_label_get_label (GTK_LABEL (row->title))); + break; + case PROP_ALLOWED: + g_value_set_boolean (value, cc_toggle_row_get_allowed (row)); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} + +static void +cc_toggle_row_set_property (GObject *object, + guint prop_id, + const GValue *value, + GParamSpec *pspec) +{ + CcToggleRow *row = CC_TOGGLE_ROW (object); + + switch (prop_id) + { + case PROP_TITLE: + gtk_label_set_label (GTK_LABEL (row->title), g_value_get_string (value)); + break; + case PROP_ALLOWED: + cc_toggle_row_set_allowed (row, g_value_get_boolean (value)); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} + +static void +cc_toggle_row_class_init (CcToggleRowClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass); + + object_class->get_property = cc_toggle_row_get_property; + object_class->set_property = cc_toggle_row_set_property; + + gtk_widget_class_set_template_from_resource (widget_class, "/org/gnome/control-center/applications/cc-toggle-row.ui"); + + g_object_class_install_property (object_class, + PROP_TITLE, + g_param_spec_string ("title", "title", "title", + NULL, G_PARAM_READWRITE)); + + g_object_class_install_property (object_class, + PROP_ALLOWED, + g_param_spec_boolean ("allowed", "allowed", "allowed", + FALSE, G_PARAM_READWRITE)); + + gtk_widget_class_bind_template_child (widget_class, CcToggleRow, title); + gtk_widget_class_bind_template_child (widget_class, CcToggleRow, toggle); + + gtk_widget_class_bind_template_callback (widget_class, changed_cb); +} + +static void +cc_toggle_row_init (CcToggleRow *self) +{ + gtk_widget_init_template (GTK_WIDGET (self)); +} + +CcToggleRow * +cc_toggle_row_new (void) +{ + return CC_TOGGLE_ROW (g_object_new (CC_TYPE_TOGGLE_ROW, NULL)); +} + +void +cc_toggle_row_set_allowed (CcToggleRow *self, + gboolean allowed) +{ + gtk_switch_set_active (GTK_SWITCH (self->toggle), allowed); +} + +gboolean +cc_toggle_row_get_allowed (CcToggleRow *self) +{ + return gtk_switch_get_active (GTK_SWITCH (self->toggle)); +} diff --git a/panels/applications/cc-toggle-row.h b/panels/applications/cc-toggle-row.h new file mode 100644 index 0000000..cfc66b9 --- /dev/null +++ b/panels/applications/cc-toggle-row.h @@ -0,0 +1,37 @@ +/* cc-toggle-row.h + * + * Copyright 2018 Matthias Clasen <matthias.clasen@gmail.com> + * + * 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 <http://www.gnu.org/licenses/>. + * + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +#pragma once + +#include <gtk/gtk.h> + +G_BEGIN_DECLS + +#define CC_TYPE_TOGGLE_ROW (cc_toggle_row_get_type()) +G_DECLARE_FINAL_TYPE (CcToggleRow, cc_toggle_row, CC, TOGGLE_ROW, GtkListBoxRow) + +CcToggleRow* cc_toggle_row_new (void); + +void cc_toggle_row_set_allowed (CcToggleRow *row, + gboolean allowed); + +gboolean cc_toggle_row_get_allowed (CcToggleRow *row); + +G_END_DECLS diff --git a/panels/applications/cc-toggle-row.ui b/panels/applications/cc-toggle-row.ui new file mode 100644 index 0000000..8dea8f0 --- /dev/null +++ b/panels/applications/cc-toggle-row.ui @@ -0,0 +1,30 @@ +<?xml version="1.0" encoding="UTF-8"?> +<interface> + <template class="CcToggleRow" parent="GtkListBoxRow"> + <property name="visible">True</property> + <property name="can-focus">True</property> + <property name="activatable">False</property> + <child> + <object class="GtkBox"> + <property name="visible">1</property> + <property name="border-width">12</property> + <property name="spacing">12</property> + <child> + <object class="GtkLabel" id="title"> + <property name="visible">1</property> + <property name="xalign">0</property> + <property name="hexpand">1</property> + <property name="ellipsize">end</property> + </object> + </child> + <child> + <object class="GtkSwitch" id="toggle"> + <property name="visible">1</property> + <property name="valign">center</property> + <signal name="notify::active" handler="changed_cb" swapped="yes"/> + </object> + </child> + </object> + </child> + </template> +</interface> diff --git a/panels/applications/globs.c b/panels/applications/globs.c new file mode 100644 index 0000000..4d2c939 --- /dev/null +++ b/panels/applications/globs.c @@ -0,0 +1,62 @@ +/* globs.c + * + * Copyright 2018 Matthias Clasen <matthias.clasen@gmail.com> + * + * 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 <http://www.gnu.org/licenses/>. + * + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +#include <config.h> + +#include "globs.h" + +/* parse mime/globs and return a string->string hash table */ +GHashTable * +parse_globs (void) +{ + GHashTable *globs; + const gchar * const *dirs; + gint i; + + globs = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, g_free); + + dirs = g_get_system_data_dirs (); + + for (i = 0; dirs[i]; i++) + { + g_autofree gchar *file = g_build_filename (dirs[i], "mime", "globs", NULL); + g_autofree gchar *contents = NULL; + + if (g_file_get_contents (file, &contents, NULL, NULL)) + { + g_auto(GStrv) strv = NULL; + int i; + + strv = g_strsplit (contents, "\n", 0); + for (i = 0; strv[i]; i++) + { + g_auto(GStrv) parts = NULL; + + if (strv[i][0] == '#' || strv[i][0] == '\0') + continue; + + parts = g_strsplit (strv[i], ":", 2); + g_hash_table_insert (globs, g_strdup (parts[0]), g_strdup (parts[1])); + } + } + } + + return globs; +} diff --git a/panels/applications/globs.h b/panels/applications/globs.h new file mode 100644 index 0000000..0a54588 --- /dev/null +++ b/panels/applications/globs.h @@ -0,0 +1,29 @@ +/* globs.h + * + * Copyright 2018 Matthias Clasen <matthias.clasen@gmail.com> + * + * 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 <http://www.gnu.org/licenses/>. + * + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +#pragma once + +#include <gio/gio.h> + +G_BEGIN_DECLS + +GHashTable* parse_globs (void); + +G_END_DECLS diff --git a/panels/applications/gnome-applications-panel.desktop.in.in b/panels/applications/gnome-applications-panel.desktop.in.in new file mode 100644 index 0000000..86e8166 --- /dev/null +++ b/panels/applications/gnome-applications-panel.desktop.in.in @@ -0,0 +1,16 @@ +[Desktop Entry] +Name=Applications +Comment=Control various application permissions and settings +Exec=gnome-control-center applications +# FIXME +# Translators: Do NOT translate or transliterate this text (this is an icon file name)! +Icon=preferences-desktop-apps +Terminal=false +Type=Application +NoDisplay=true +StartupNotify=true +Categories=GNOME;GTK;Settings;DesktopSettings;X-GNOME-Settings-Panel;X-GNOME-AccountSettings; +OnlyShowIn=GNOME;Unity; +# Translators: Search terms to find the Privacy panel. Do NOT translate or localize the semicolons! The list MUST also end with a semicolon! +Keywords=application;flatpak;permission;setting; +X-GNOME-ControlCenter-HasSidebar=true
\ No newline at end of file diff --git a/panels/applications/meson.build b/panels/applications/meson.build new file mode 100644 index 0000000..d511bf6 --- /dev/null +++ b/panels/applications/meson.build @@ -0,0 +1,58 @@ +panels_list += cappletname +desktop = 'gnome-@0@-panel.desktop'.format(cappletname) + +desktop_in = configure_file( + input : desktop + '.in.in', + output : desktop + '.in', + configuration : desktop_conf +) + +i18n.merge_file( + desktop, + type : 'desktop', + input : desktop_in, + output : desktop, + po_dir : po_dir, + install : true, + install_dir : control_center_desktopdir +) + +sources = files( + 'cc-applications-panel.c', + 'cc-applications-row.c', + 'cc-toggle-row.c', + 'cc-info-row.c', + 'cc-action-row.c', + 'globs.c', + 'search.c', + 'utils.c', +) + +resource_data = files('cc-applications-panel.ui') + +sources += gnome.compile_resources( + 'cc-' + cappletname + '-resources', + cappletname + '.gresource.xml', + c_name : 'cc_' + cappletname, + dependencies : resource_data, + export : true +) + +deps = common_deps + +if enable_snap + deps += snapd_glib_deps + sources += files('cc-snap-row.c') +endif + +if enable_malcontent + deps += malcontent_dep +endif + +panels_libs += static_library( + cappletname, + sources : sources, + include_directories : [ top_inc, common_inc ], + dependencies : deps, + c_args : cflags +) diff --git a/panels/applications/search.c b/panels/applications/search.c new file mode 100644 index 0000000..448918b --- /dev/null +++ b/panels/applications/search.c @@ -0,0 +1,133 @@ +/* search.c + * + * Copyright 2018 Matthias Clasen <matthias.clasen@gmail.com> + * + * 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 <http://www.gnu.org/licenses/>. + * + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +#include <config.h> + +#include "search.h" + + +#define SHELL_PROVIDER_GROUP "Shell Search Provider" + +static void +add_one_provider (GHashTable *search_providers, + GFile *file) +{ + g_autoptr(GKeyFile) keyfile = NULL; + g_autoptr(GError) error = NULL; + g_autofree gchar *app_id = NULL; + g_autofree gchar *path = NULL; + gboolean default_disabled; + + path = g_file_get_path (file); + keyfile = g_key_file_new (); + g_key_file_load_from_file (keyfile, path, G_KEY_FILE_NONE, &error); + + if (error != NULL) + { + g_warning ("Error loading %s: %s - search provider will be ignored", + path, error->message); + return; + } + + if (!g_key_file_has_group (keyfile, SHELL_PROVIDER_GROUP)) + { + g_debug ("Shell search provider group missing from '%s', ignoring", path); + return; + } + + app_id = g_key_file_get_string (keyfile, SHELL_PROVIDER_GROUP, "DesktopId", &error); + + if (error != NULL) + { + g_warning ("Unable to read desktop ID from %s: %s - search provider will be ignored", + path, error->message); + return; + } + + if (g_str_has_suffix (app_id, ".desktop")) + app_id[strlen (app_id) - strlen (".desktop")] = '\0'; + + default_disabled = g_key_file_get_boolean (keyfile, SHELL_PROVIDER_GROUP, "DefaultDisabled", NULL); + + g_hash_table_insert (search_providers, g_strdup (app_id), GINT_TO_POINTER (default_disabled)); +} + +static void +parse_search_providers_one_dir (GHashTable *search_providers, + const gchar *system_dir) +{ + g_autoptr(GFileEnumerator) enumerator = NULL; + g_autoptr(GError) error = NULL; + g_autoptr(GFile) providers_location = NULL; + g_autofree gchar *providers_path = NULL; + + providers_path = g_build_filename (system_dir, "gnome-shell", "search-providers", NULL); + providers_location = g_file_new_for_path (providers_path); + + enumerator = g_file_enumerate_children (providers_location, + "standard::type,standard::name,standard::content-type", + G_FILE_QUERY_INFO_NONE, + NULL, &error); + + if (error != NULL) + { + if (!g_error_matches (error, G_IO_ERROR, G_IO_ERROR_NOT_FOUND) && + !g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED)) + g_warning ("Error opening %s: %s - search provider configuration won't be possible", + providers_path, error->message); + return; + } + + while (TRUE) + { + GFile *provider = NULL; + + if (!g_file_enumerator_iterate (enumerator, NULL, &provider, NULL, &error)) + { + g_warning ("Error while reading %s: %s - search provider configuration won't be possible", + providers_path, error->message); + return; + } + + if (provider == NULL) + break; + + add_one_provider (search_providers, provider); + } +} + +/* parse gnome-shell/search-provider files and return a string->boolean hash table */ +GHashTable * +parse_search_providers (void) +{ + GHashTable *search_providers; + const gchar * const *dirs; + gint i; + + search_providers = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, NULL); + + dirs = g_get_system_data_dirs (); + + for (i = 0; dirs[i]; i++) + parse_search_providers_one_dir (search_providers, dirs[i]); + + return search_providers; +} + diff --git a/panels/applications/search.h b/panels/applications/search.h new file mode 100644 index 0000000..b7ade82 --- /dev/null +++ b/panels/applications/search.h @@ -0,0 +1,29 @@ +/* globs.h + * + * Copyright 2018 Matthias Clasen <matthias.clasen@gmail.com> + * + * 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 <http://www.gnu.org/licenses/>. + * + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +#pragma once + +#include <gio/gio.h> + +G_BEGIN_DECLS + +GHashTable* parse_search_providers (void); + +G_END_DECLS diff --git a/panels/applications/utils.c b/panels/applications/utils.c new file mode 100644 index 0000000..96704a4 --- /dev/null +++ b/panels/applications/utils.c @@ -0,0 +1,268 @@ +/* utils.c + * + * Copyright 2018 Matthias Clasen <matthias.clasen@gmail.com> + * + * 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 <http://www.gnu.org/licenses/>. + * + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +#ifndef _XOPEN_SOURCE +#define _XOPEN_SOURCE 600 +#endif + +#include <config.h> +#include <glib/gi18n.h> +#ifdef HAVE_SNAP +#include <snapd-glib/snapd-glib.h> +#endif +#include <sys/types.h> +#include <sys/stat.h> + +#include <ftw.h> + +#include "utils.h" + +static gint +ftw_remove_cb (const gchar *path, + const struct stat *sb, + gint typeflags, + struct FTW *ftwbuf) +{ + remove (path); + return 0; +} + +static void +file_remove_thread_func (GTask *task, + gpointer source_object, + gpointer task_data, + GCancellable *cancellable) +{ + GFile *file = source_object; + g_autofree gchar *path = g_file_get_path (file); + + nftw (path, ftw_remove_cb, 20, FTW_DEPTH); + + if (g_task_set_return_on_cancel (task, FALSE)) + g_task_return_boolean (task, TRUE); +} + +void +file_remove_async (GFile *file, + GCancellable *cancellable, + GAsyncReadyCallback callback, + gpointer data) +{ + g_autoptr(GTask) task = g_task_new (file, cancellable, callback, data); + g_task_set_return_on_cancel (task, TRUE); + g_task_run_in_thread (task, file_remove_thread_func); +} + +gboolean +file_remove_finish (GFile *file, + GAsyncResult *result, + GError **error) +{ + g_return_val_if_fail (g_task_is_valid (result, file), FALSE); + return g_task_propagate_boolean (G_TASK (result), error); +} + +static GPrivate size_key = G_PRIVATE_INIT (g_free); + +static gint +ftw_size_cb (const gchar *path, + const struct stat *sb, + gint typeflags, + struct FTW *ftwbuf) +{ + guint64 *size = (guint64*)g_private_get (&size_key); + if (typeflags == FTW_F) + *size += sb->st_size; + return 0; +} + +static void +file_size_thread_func (GTask *task, + gpointer source_object, + gpointer task_data, + GCancellable *cancellable) +{ + GFile *file = source_object; + g_autofree gchar *path = g_file_get_path (file); + guint64 *total; + + g_private_replace (&size_key, g_new0 (guint64, 1)); + + nftw (path, ftw_size_cb, 20, FTW_DEPTH); + + total = g_new0 (guint64, 1); + *total = *(guint64*)g_private_get (&size_key); + + if (g_task_set_return_on_cancel (task, FALSE)) + g_task_return_pointer (task, total, g_free); +} + +void +file_size_async (GFile *file, + GCancellable *cancellable, + GAsyncReadyCallback callback, + gpointer data) +{ + g_autoptr(GTask) task = g_task_new (file, cancellable, callback, data); + g_task_set_return_on_cancel (task, TRUE); + g_task_run_in_thread (task, file_size_thread_func); +} + +gboolean +file_size_finish (GFile *file, + GAsyncResult *result, + guint64 *size, + GError **error) +{ + g_autofree guint64 *data = NULL; + + g_return_val_if_fail (g_task_is_valid (result, file), FALSE); + data = g_task_propagate_pointer (G_TASK (result), error); + if (data == NULL) + return FALSE; + if (size != NULL) + *size = *data; + return TRUE; +} + +void +container_remove_all (GtkContainer *container) +{ + g_autoptr(GList) children = NULL; + GList *l; + + children = gtk_container_get_children (container); + for (l = children; l; l = l->next) + gtk_widget_destroy (GTK_WIDGET (l->data)); +} + +static gchar * +get_output_of (const gchar **argv) +{ + g_autofree gchar *output = NULL; + int status; + + if (!g_spawn_sync (NULL, + (gchar**) argv, + NULL, + G_SPAWN_SEARCH_PATH, + NULL, NULL, + &output, NULL, + &status, NULL)) + return NULL; + + if (!g_spawn_check_exit_status (status, NULL)) + return NULL; + + return g_steal_pointer (&output); +} + +GKeyFile * +get_flatpak_metadata (const gchar *app_id) +{ + const gchar *argv[5] = { "flatpak", "info", "-m", "app", NULL }; + g_autofree gchar *data = NULL; + g_autoptr(GError) error = NULL; + g_autoptr(GKeyFile) keyfile = NULL; + + argv[3] = app_id; + + data = get_output_of (argv); + if (data == NULL) + return NULL; + + keyfile = g_key_file_new (); + if (!g_key_file_load_from_data (keyfile, data, -1, 0, &error)) + { + g_warning ("%s", error->message); + return NULL; + } + + return g_steal_pointer (&keyfile); +} + +guint64 +get_flatpak_app_size (const gchar *app_id) +{ + const gchar *argv[5] = { "flatpak", "info", "-s", "app", NULL }; + g_autofree gchar *data = NULL; + guint64 factor; + double val; + + argv[3] = app_id; + + data = get_output_of (argv); + if (data == NULL) + return 0; + + data = g_strstrip (data); + + if (g_str_has_suffix (data, "kB") || g_str_has_suffix (data, "kb")) + factor = 1000; + else if (g_str_has_suffix (data, "MB") || g_str_has_suffix (data, "Mb")) + factor = 1000 * 1000; + else if (g_str_has_suffix (data, "GB") || g_str_has_suffix (data, "Gb")) + factor = 1000 * 1000 * 1000; + else if (g_str_has_suffix (data, "KiB") || g_str_has_suffix (data, "Kib")) + factor = 1024; + else if (g_str_has_suffix (data, "MiB") || g_str_has_suffix (data, "Mib")) + factor = 1024 * 1024; + else if (g_str_has_suffix (data, "GiB") || g_str_has_suffix (data, "Gib")) + factor = 1024 * 1024 * 1024; + else + factor = 1; + + val = g_ascii_strtod (data, NULL); + + return (guint64)(val * factor); +} + +guint64 +get_snap_app_size (const gchar *snap_name) +{ +#ifdef HAVE_SNAP + g_autoptr(SnapdClient) client = NULL; + g_autoptr(SnapdSnap) snap = NULL; + g_autoptr(GError) error = NULL; + + client = snapd_client_new (); + snap = snapd_client_get_snap_sync (client, snap_name, NULL, &error); + if (snap == NULL) + { + g_warning ("Failed to get snap size: %s", error->message); + return 0; + } + + return snapd_snap_get_installed_size (snap); +#else + return 0; +#endif +} + +char * +get_app_id (GAppInfo *info) +{ + gchar *app_id = g_strdup (g_app_info_get_id (info)); + + if (g_str_has_suffix (app_id, ".desktop")) + app_id[strlen (app_id) - strlen (".desktop")] = '\0'; + + return app_id; +} diff --git a/panels/applications/utils.h b/panels/applications/utils.h new file mode 100644 index 0000000..5f899d7 --- /dev/null +++ b/panels/applications/utils.h @@ -0,0 +1,57 @@ +/* utils.h + * + * Copyright 2018 Matthias Clasen <matthias.clasen@gmail.com> + * + * 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 <http://www.gnu.org/licenses/>. + * + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +#pragma once + +#include <gio/gio.h> +#include <gtk/gtk.h> + +G_BEGIN_DECLS + +void file_remove_async (GFile *file, + GCancellable *cancellable, + GAsyncReadyCallback callback, + gpointer data); + +gboolean file_remove_finish (GFile *file, + GAsyncResult *result, + GError **error); + +void file_size_async (GFile *file, + GCancellable *cancellable, + GAsyncReadyCallback callback, + gpointer data); + +gboolean file_size_finish (GFile *file, + GAsyncResult *result, + guint64 *size, + GError **error); + +void container_remove_all (GtkContainer *container); + +GKeyFile* get_flatpak_metadata (const gchar *app_id); + +guint64 get_flatpak_app_size (const gchar *app_id); + +guint64 get_snap_app_size (const gchar *snap_name); + +gchar* get_app_id (GAppInfo *info); + +G_END_DECLS |