From 1245fd787be1d623d7d01fb1acaabdcb27732ff4 Mon Sep 17 00:00:00 2001 From: Georges Basile Stavracas Neto Date: Fri, 16 Nov 2018 15:42:54 -0200 Subject: [PATCH] Introduce Applications panel This is an initial implementation of most of the intended functionality for this panel. Flatpak integration itself is implemented by spawning "flatpak info -m $appid", which gives us the metadata in key file format, and allows flatpak to be a runtime dependency. Even after removing the .desktop suffix, there can still be a difference between the app ID generated in this way and the flatpak ID, since flatpaks are allowed to export files whose prefix is the flatpak ID. Fix this by pulling the X-Flatpak key out of the desktop file. This would cause trouble for org.libreoffice.LibreOffice-math. --- .../applications/applications.gresource.xml | 11 + panels/applications/cc-action-row.c | 218 +++ panels/applications/cc-action-row.h | 42 + panels/applications/cc-action-row.ui | 49 + panels/applications/cc-applications-panel.c | 1710 +++++++++++++++++ panels/applications/cc-applications-panel.css | 7 + panels/applications/cc-applications-panel.h | 30 + panels/applications/cc-applications-panel.ui | 531 +++++ panels/applications/cc-applications-row.c | 102 + panels/applications/cc-applications-row.h | 36 + panels/applications/cc-applications-row.ui | 30 + panels/applications/cc-info-row.c | 215 +++ panels/applications/cc-info-row.h | 37 + panels/applications/cc-info-row.ui | 40 + panels/applications/cc-toggle-row.c | 144 ++ panels/applications/cc-toggle-row.h | 37 + panels/applications/cc-toggle-row.ui | 29 + panels/applications/globs.c | 62 + panels/applications/globs.h | 29 + .../gnome-applications-panel.desktop.in.in | 15 + panels/applications/meson.build | 49 + panels/applications/search.c | 133 ++ panels/applications/search.h | 29 + panels/applications/utils.c | 209 ++ panels/applications/utils.h | 44 + panels/meson.build | 1 + shell/cc-panel-list.c | 1 + shell/cc-panel-loader.c | 2 + 28 files changed, 3842 insertions(+) create mode 100644 panels/applications/applications.gresource.xml create mode 100644 panels/applications/cc-action-row.c create mode 100644 panels/applications/cc-action-row.h create mode 100644 panels/applications/cc-action-row.ui create mode 100644 panels/applications/cc-applications-panel.c create mode 100644 panels/applications/cc-applications-panel.css create mode 100644 panels/applications/cc-applications-panel.h create mode 100644 panels/applications/cc-applications-panel.ui create mode 100644 panels/applications/cc-applications-row.c create mode 100644 panels/applications/cc-applications-row.h create mode 100644 panels/applications/cc-applications-row.ui create mode 100644 panels/applications/cc-info-row.c create mode 100644 panels/applications/cc-info-row.h create mode 100644 panels/applications/cc-info-row.ui create mode 100644 panels/applications/cc-toggle-row.c create mode 100644 panels/applications/cc-toggle-row.h create mode 100644 panels/applications/cc-toggle-row.ui create mode 100644 panels/applications/globs.c create mode 100644 panels/applications/globs.h create mode 100644 panels/applications/gnome-applications-panel.desktop.in.in create mode 100644 panels/applications/meson.build create mode 100644 panels/applications/search.c create mode 100644 panels/applications/search.h create mode 100644 panels/applications/utils.c create mode 100644 panels/applications/utils.h diff --git a/panels/applications/applications.gresource.xml b/panels/applications/applications.gresource.xml new file mode 100644 index 000000000..d23f1b2e0 --- /dev/null +++ b/panels/applications/applications.gresource.xml @@ -0,0 +1,11 @@ + + + + cc-action-row.ui + cc-applications-panel.ui + cc-applications-row.ui + cc-info-row.ui + cc-toggle-row.ui + cc-applications-panel.css + + diff --git a/panels/applications/cc-action-row.c b/panels/applications/cc-action-row.c new file mode 100644 index 000000000..b541f79a7 --- /dev/null +++ b/panels/applications/cc-action-row.c @@ -0,0 +1,218 @@ +/* cc-action-row.c + * + * Copyright 2018 Matthias Clasen + * + * 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 . + * + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +#include +#include + +#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 000000000..2912adbf9 --- /dev/null +++ b/panels/applications/cc-action-row.h @@ -0,0 +1,42 @@ +/* cc-action-row.h + * + * Copyright 2018 Matthias Clasen + * + * 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 . + * + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +#pragma once + +#include + +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 000000000..54385a325 --- /dev/null +++ b/panels/applications/cc-action-row.ui @@ -0,0 +1,49 @@ + + + + diff --git a/panels/applications/cc-applications-panel.c b/panels/applications/cc-applications-panel.c new file mode 100644 index 000000000..d177902f0 --- /dev/null +++ b/panels/applications/cc-applications-panel.c @@ -0,0 +1,1710 @@ +/* cc-applications-panel.c + * + * Copyright 2018 Georges Basile Stavracas Neto + * + * 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 . + * + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +#define G_LOG_DOMAIN "cc-applications-panel" + +#include +#include + +#include + +#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 "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/" + +struct _CcApplicationsPanel +{ + CcPanel parent; + + GtkListBox *sidebar_listbox; + GtkWidget *header_button; + GtkWidget *title_label; + GAppInfoMonitor *monitor; + gulong monitor_id; + + GCancellable *cancellable; + + gchar *current_app_id; + gchar *current_flatpak_id; + + GHashTable *globs; + GHashTable *search_providers; + + GDBusProxy *perm_store; + GSettings *notification_settings; + GSettings *location_settings; + GSettings *privacy_settings; + GSettings *search_settings; + + GtkListBox *stack; + + GtkWidget *permission_section; + GtkWidget *permission_list; + GtkWidget *camera; + GtkWidget *no_camera; + GtkWidget *location; + GtkWidget *no_location; + GtkWidget *microphone; + GtkWidget *no_microphone; + GtkWidget *builtin; + GtkWidget *builtin_dialog; + GtkWidget *builtin_label; + GtkWidget *builtin_list; + + GtkWidget *integration_section; + GtkWidget *integration_list; + GtkWidget *notification; + GtkWidget *sound; + GtkWidget *no_sound; + GtkWidget *search; + GtkWidget *no_search; + + GtkWidget *handler_section; + GtkWidget *handler_reset; + GtkWidget *handler_list; + GtkWidget *hypertext; + GtkWidget *text; + GtkWidget *images; + GtkWidget *fonts; + GtkWidget *archives; + GtkWidget *packages; + GtkWidget *audio; + GtkWidget *video; + GtkWidget *other; + GtkWidget *link; + + GtkWidget *usage_section; + GtkWidget *usage_list; + GtkWidget *storage; + GtkWidget *storage_dialog; + GtkWidget *storage_list; + GtkWidget *app; + GtkWidget *data; + GtkWidget *cache; + GtkWidget *total; + GtkWidget *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, "privacy", NULL, &error)) + g_warning ("Failed to switch to privacy panel: %s", error->message); + + return TRUE; +} + +static void +open_software_cb (GtkButton *button, + 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); +} + +/* --- flatpak permissions and utilities --- */ + +static gchar ** +get_flatpak_permissions (CcApplicationsPanel *self, + const gchar *table, + const gchar *id, + const gchar *app_id) +{ + g_autoptr(GVariant) ret = NULL; + g_autoptr(GVariantIter) iter = NULL; + g_autoptr(GVariant) val = NULL; + g_autofree gchar *key = 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@as}", &key, &val)) + { + if (strcmp (key, app_id) == 0) + return g_variant_dup_strv (val, NULL); + } + + val = NULL; /* freed by g_variant_iter_loop */ + key = NULL; + + return NULL; +} + +static void +set_flatpak_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 Flatpak permissions: %s", error->message); +} + +static char * +get_flatpak_id (GAppInfo *info) +{ + if (G_IS_DESKTOP_APP_INFO (info)) + return g_desktop_app_info_get_string (G_DESKTOP_APP_INFO (info), "X-Flatpak"); + + 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 (CC_TOGGLE_ROW (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_flatpak_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_flatpak_permissions (self, "notifications", "notification", self->current_flatpak_id, perms); + } +} + +static void +notification_cb (CcApplicationsPanel *self) +{ + if (self->current_app_id) + set_notification_allowed (self, cc_toggle_row_get_allowed (CC_TOGGLE_ROW (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); +} + +/* --- 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_flatpak_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_flatpak_permissions (self, "devices", device, self->current_flatpak_id, perms); +} + +static void +microphone_cb (CcApplicationsPanel *self) +{ + if (self->current_flatpak_id) + set_device_allowed (self, "microphone", cc_toggle_row_get_allowed (CC_TOGGLE_ROW (self->microphone))); +} + +static void +sound_cb (CcApplicationsPanel *self) +{ + if (self->current_flatpak_id) + set_device_allowed (self, "speakers", cc_toggle_row_get_allowed (CC_TOGGLE_ROW (self->sound))); +} + +static void +camera_cb (CcApplicationsPanel *self) +{ + if (self->current_flatpak_id) + set_device_allowed (self, "camera", cc_toggle_row_get_allowed (CC_TOGGLE_ROW (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_flatpak_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_flatpak_permissions (self, "location", "location", self->current_app_id, perms); +} + +static void +location_cb (CcApplicationsPanel *self) +{ + if (self->current_app_id) + set_location_allowed (self, cc_toggle_row_get_allowed (CC_TOGGLE_ROW (self->location))); +} + +/* --- permissions section --- */ + +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 (GtkListBox *list, + GtkListBoxRow *list_row, + CcApplicationsPanel *self) +{ + GtkWidget *row = GTK_WIDGET (list_row); + + if (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; + gchar **strv; + gchar *str; + gint added = 0; + g_autofree gchar *text = NULL; + + keyfile = get_flatpak_metadata (app_id); + if (keyfile == NULL) + return FALSE; + + strv = g_key_file_get_string_list (keyfile, "Context", "sockets", NULL, NULL); + if (strv && g_strv_contains ((const gchar * const*)strv, "system-bus")) + added += add_static_permission_row (self, _("System Bus"), _("Full access")); + if (strv && g_strv_contains ((const gchar * const*)strv, "session-bus")) + added += add_static_permission_row (self, _("Session Bus"), _("Full access")); + g_strfreev (strv); + + strv = g_key_file_get_string_list (keyfile, "Context", "devices", NULL, NULL); + if (strv && g_strv_contains ((const gchar * const*)strv, "all")) + added += add_static_permission_row (self, _("Devices"), _("Full access to /dev")); + g_strfreev (strv); + + strv = g_key_file_get_string_list (keyfile, "Context", "shared", NULL, NULL); + if (strv && g_strv_contains ((const gchar * const*)strv, "network")) + added += add_static_permission_row (self, _("Network"), _("Has network access")); + g_strfreev (strv); + + strv = g_key_file_get_string_list (keyfile, "Context", "filesystems", NULL, NULL); + if (strv && (g_strv_contains ((const gchar * const *)strv, "home") || + g_strv_contains ((const gchar * const *)strv, "home:rw"))) + added += add_static_permission_row (self, _("Home"), _("Full access")); + else if (strv && g_strv_contains ((const gchar * const *)strv, "home:ro")) + added += add_static_permission_row (self, _("Home"), _("Read-only")); + if (strv && (g_strv_contains ((const gchar * const *)strv, "host") || + g_strv_contains ((const gchar * const *)strv, "host:rw"))) + added += add_static_permission_row (self, _("File System"), _("Full access")); + else if (strv && g_strv_contains ((const gchar * const *)strv, "host:ro")) + added += add_static_permission_row (self, _("File System"), _("Read-only")); + g_strfreev (strv); + + 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")); + g_free (str); + + gtk_widget_set_visible (self->builtin, added > 0); + + 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 (GTK_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 *flatpak_id = get_flatpak_id (info); + gboolean disabled, allowed, set; + gboolean has_any = FALSE; + + if (flatpak_id == NULL) + { + gtk_widget_hide (self->permission_section); + return; + } + + disabled = g_settings_get_boolean (self->privacy_settings, "disable-camera"); + get_device_allowed (self, "camera", flatpak_id, &set, &allowed); + cc_toggle_row_set_allowed (CC_TOGGLE_ROW (self->camera), allowed); + gtk_widget_set_visible (self->camera, set && !disabled); + gtk_widget_set_visible (self->no_camera, set && disabled); + has_any |= set; + + disabled = g_settings_get_boolean (self->privacy_settings, "disable-microphone"); + get_device_allowed (self, "microphone", flatpak_id, &set, &allowed); + cc_toggle_row_set_allowed (CC_TOGGLE_ROW (self->microphone), allowed); + gtk_widget_set_visible (self->microphone, set && !disabled); + gtk_widget_set_visible (self->no_microphone, set && disabled); + has_any |= set; + + disabled = !g_settings_get_boolean (self->location_settings, "enabled"); + get_location_allowed (self, flatpak_id, &set, &allowed); + cc_toggle_row_set_allowed (CC_TOGGLE_ROW (self->location), allowed); + gtk_widget_set_visible (self->location, set && !disabled); + gtk_widget_set_visible (self->no_location, set && disabled); + has_any |= set; + + remove_static_permissions (self); + has_any |= add_static_permissions (self, info, flatpak_id); + + gtk_widget_set_visible (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 *flatpak_id = get_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 (CC_TOGGLE_ROW (self->search), allowed); + gtk_widget_set_visible (self->search, set && !disabled); + gtk_widget_set_visible (self->no_search, set && disabled); + + if (flatpak_id != NULL) + { + g_clear_object (&self->notification_settings); + get_notification_allowed (self, flatpak_id, &set, &allowed); + cc_toggle_row_set_allowed (CC_TOGGLE_ROW (self->notification), allowed); + gtk_widget_set_visible (self->notification, set); + has_any |= set; + + disabled = g_settings_get_boolean (self->privacy_settings, "disable-sound-output"); + get_device_allowed (self, "speakers", flatpak_id, &set, &allowed); + cc_toggle_row_set_allowed (CC_TOGGLE_ROW (self->sound), allowed); + gtk_widget_set_visible (self->sound, set && !disabled); + gtk_widget_set_visible (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 (CC_TOGGLE_ROW (self->notification), allowed); + gtk_widget_set_visible (self->notification, set); + has_any |= set; + + gtk_widget_hide (self->sound); + gtk_widget_hide (self->no_sound); + } + + gtk_widget_set_visible (self->integration_section, has_any); +} + +/* --- handler section --- */ + +static void +unset_cb (CcActionRow *row, + CcApplicationsPanel *self) +{ + const gchar *type; + GtkListBoxRow *selected; + GAppInfo *info; + + selected = gtk_list_box_get_selected_row (GTK_LIST_BOX (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 (GtkWidget *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, + GtkWidget *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, 0); + + 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 (GTK_LIST_BOX (self->handler_list), GTK_WIDGET (row), pos); + update_group_row_count (after, 1); +} + +static void +add_file_type (CcApplicationsPanel *self, + GtkWidget *after, + const gchar *type) +{ + CcActionRow *row; + const gchar *desc; + 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 (row, "activated", G_CALLBACK (unset_cb), self); + + 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 (GTK_LIST_BOX (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, + GtkWidget **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 (GTK_LIST_BOX (self->handler_list), GTK_WIDGET (r), -1); + *row = GTK_WIDGET (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 (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 (GtkListBox *list, + GtkListBoxRow *list_row, + CcApplicationsPanel *self) +{ + GtkWidget *row = GTK_WIDGET (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 (CC_INFO_ROW (row), + !cc_info_row_get_expanded (CC_INFO_ROW (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 (GtkButton *button, + CcApplicationsPanel *self) +{ + GtkListBoxRow *selected; + GAppInfo *info; + const gchar **types; + gint i; + + selected = gtk_list_box_get_selected_row (GTK_LIST_BOX (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 (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 (self->handler_reset, FALSE); + for (i = 0; types[i]; i++) + { + gchar *ctype = g_content_type_from_mime_type (types[i]); + + if (g_hash_table_contains (hash, ctype)) + { + g_free (ctype); + continue; + } + + if (!app_info_recommended_for (info, ctype)) + { + gtk_widget_set_sensitive (self->handler_reset, TRUE); + g_free (ctype); + continue; + } + + g_hash_table_add (hash, ctype); + add_handler_row (self, ctype); + } +} + +/* --- usage section --- */ + +static void +storage_row_activated_cb (GtkListBox *list, + GtkListBoxRow *list_row, + CcApplicationsPanel *self) +{ + GtkWidget *row = GTK_WIDGET (list_row); + + if (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; + + size = g_object_get_data (G_OBJECT (res), "size"); + 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 (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, 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; + + size = g_object_get_data (G_OBJECT (res), "size"); + 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, set_data_size, self); +} + +static void +cache_cleared (GObject *source, + GAsyncResult *res, + gpointer data) +{ + CcApplicationsPanel *self = data; + + 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, cache_cleared, self); +} +static void +update_app_row (CcApplicationsPanel *self, + const gchar *app_id) +{ + g_autofree gchar *formatted_size = NULL; + + 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_flatpak_sizes (CcApplicationsPanel *self, + const gchar *app_id) +{ + gtk_widget_set_sensitive (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 *flatpak_id = get_flatpak_id (info); + + if (flatpak_id != NULL) + { + gtk_widget_show (self->usage_section); + update_flatpak_sizes (self, flatpak_id); + } + else + { + gtk_widget_hide (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 (GTK_LABEL (self->title_label), _("Applications")); + gtk_stack_set_visible_child_name (GTK_STACK (self->stack), "empty"); + gtk_widget_hide (self->header_button); + return; + } + + info = cc_applications_row_get_info (CC_APPLICATIONS_ROW (row)); + + gtk_label_set_label (GTK_LABEL (self->title_label), g_app_info_get_display_name (info)); + gtk_stack_set_visible_child_name (GTK_STACK (self->stack), "settings"); + gtk_widget_show (self->header_button); + + g_clear_pointer (&self->current_app_id, g_free); + g_clear_pointer (&self->current_flatpak_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_flatpak_id = get_flatpak_id (info); +} + +static void +populate_applications (CcApplicationsPanel *self) +{ + g_autolist(GObject) infos = NULL; + GList *l; + + container_remove_all (GTK_CONTAINER (self->sidebar_listbox)); + + 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; + + row = GTK_WIDGET (cc_applications_row_new (info)); + gtk_list_box_insert (GTK_LIST_BOX (self->sidebar_listbox), row, -1); + + id = get_app_id (info); + if (g_strcmp0 (id, self->current_app_id) == 0) + gtk_list_box_select_row (GTK_LIST_BOX (self->sidebar_listbox), GTK_LIST_BOX_ROW (row)); + } +} + +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 void +apps_changed (GAppInfoMonitor *monitor, + CcApplicationsPanel *self) +{ + populate_applications (self); +} + +static void +row_selected_cb (GtkListBox *list, + GtkListBoxRow *row, + CcApplicationsPanel *self) +{ + update_panel (self, row); +} + +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 flatpak 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 +cc_applications_panel_dispose (GObject *object) +{ + CcApplicationsPanel *self = CC_APPLICATIONS_PANEL (object); + + g_clear_object (&self->monitor); + g_clear_object (&self->perm_store); + + g_cancellable_cancel (self->cancellable); + + G_OBJECT_CLASS (cc_applications_panel_parent_class)->dispose (object); +} + +static void +cc_applications_panel_finalize (GObject *object) +{ + CcApplicationsPanel *self = CC_APPLICATIONS_PANEL (object); + + 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_object (&self->cancellable); + + g_free (self->current_app_id); + g_hash_table_unref (self->globs); + g_hash_table_unref (self->search_providers); + + 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, self->header_button); +} + +static GtkWidget* +cc_applications_panel_get_sidebar_widget (CcPanel *panel) +{ + CcApplicationsPanel *self = CC_APPLICATIONS_PANEL (panel); + return GTK_WIDGET (self->sidebar_listbox); +} + +static GtkWidget * +cc_applications_panel_get_title_widget (CcPanel *panel) +{ + CcApplicationsPanel *self = CC_APPLICATIONS_PANEL (panel); + return 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, 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, permission_section); + gtk_widget_class_bind_template_child (widget_class, CcApplicationsPanel, permission_list); + gtk_widget_class_bind_template_child (widget_class, CcApplicationsPanel, sidebar_listbox); + gtk_widget_class_bind_template_child (widget_class, CcApplicationsPanel, search); + 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, 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); +} + +static void +cc_applications_panel_init (CcApplicationsPanel *self) +{ + g_autoptr(GtkStyleProvider) provider = NULL; + + g_resources_register (cc_applications_get_resource ()); + + 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 (self->sidebar_listbox, "row-selected", + G_CALLBACK (row_selected_cb), self); + + g_signal_connect (self->header_button, "clicked", G_CALLBACK (open_software_cb), self); + + gtk_list_box_set_header_func (GTK_LIST_BOX (self->permission_list), + cc_list_box_update_header_func, + NULL, NULL); + + gtk_list_box_set_header_func (GTK_LIST_BOX (self->integration_list), + cc_list_box_update_header_func, + NULL, NULL); + + gtk_list_box_set_header_func (GTK_LIST_BOX (self->handler_list), + cc_list_box_update_header_func, + NULL, NULL); + + gtk_list_box_set_header_func (GTK_LIST_BOX (self->usage_list), + cc_list_box_update_header_func, + NULL, NULL); + + gtk_list_box_set_header_func (GTK_LIST_BOX (self->builtin_list), + cc_list_box_update_header_func, + NULL, NULL); + + gtk_list_box_set_header_func (GTK_LIST_BOX (self->storage_list), + cc_list_box_update_header_func, + NULL, NULL); + + gtk_list_box_set_sort_func (GTK_LIST_BOX (self->sidebar_listbox), + compare_rows, + NULL, 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"); + + populate_applications (self); + + self->monitor = g_app_info_monitor_get (); + self->monitor_id = g_signal_connect (self->monitor, "changed", G_CALLBACK (apps_changed), self); + + 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", + self->cancellable, + on_perm_store_ready, + self); + + self->globs = parse_globs (); + self->search_providers = parse_search_providers (); +} diff --git a/panels/applications/cc-applications-panel.css b/panels/applications/cc-applications-panel.css new file mode 100644 index 000000000..c28771d9f --- /dev/null +++ b/panels/applications/cc-applications-panel.css @@ -0,0 +1,7 @@ +.section-title { + font-weight: bold; +} + +.section-subtitle { + opacity: 0.55; +} diff --git a/panels/applications/cc-applications-panel.h b/panels/applications/cc-applications-panel.h new file mode 100644 index 000000000..c6c88825b --- /dev/null +++ b/panels/applications/cc-applications-panel.h @@ -0,0 +1,30 @@ +/* cc-applications-panel.h + * + * Copyright 2018 Georges Basile Stavracas Neto + * + * 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 . + * + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +#pragma once + +#include + +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 000000000..b4fbbfc79 --- /dev/null +++ b/panels/applications/cc-applications-panel.ui @@ -0,0 +1,531 @@ + + + + + 1 + Applications + + + + 1 + Open in Software + + + 1 + browse + + + + + Built-in Permissions + 1 + dialog + 1 + 0 + 24 + + + + 1 + vertical + 12 + + + 1 + 1 + 50 + Yadda Yadda + + + + + 1 + none + + + + + + + + + + Storage + 1 + dialog + 1 + 0 + 24 + + + + 1 + vertical + 12 + + + 1 + 1 + 50 + How much disk space this application is occupying with app data and caches. + + + + + 1 + none + + + Application + Unknown + + + + + Data + Unknown + + + + + Cache + Unknown + + + + + <b>Total</b> + 1 + Unknown + + + + + + + + 1 + + + 1 + Clear Cache… + + + + end + + + + + + + + diff --git a/panels/applications/cc-applications-row.c b/panels/applications/cc-applications-row.c new file mode 100644 index 000000000..02a5208f9 --- /dev/null +++ b/panels/applications/cc-applications-row.c @@ -0,0 +1,102 @@ +/* cc-applications-row.c + * + * Copyright 2018 Matthias Clasen + * + * 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 . + * + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +#include +#include + +#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; + + 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); + + gtk_image_set_from_gicon (GTK_IMAGE (self->image), g_app_info_get_icon (info), 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 000000000..3080751d1 --- /dev/null +++ b/panels/applications/cc-applications-row.h @@ -0,0 +1,36 @@ +/* cc-applications-row.h + * + * Copyright 2018 Matthias Clasen + * + * 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 . + * + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +#pragma once + +#include + +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 000000000..fb483a1b9 --- /dev/null +++ b/panels/applications/cc-applications-row.ui @@ -0,0 +1,30 @@ + + + + diff --git a/panels/applications/cc-info-row.c b/panels/applications/cc-info-row.c new file mode 100644 index 000000000..3a8d88a76 --- /dev/null +++ b/panels/applications/cc-info-row.c @@ -0,0 +1,215 @@ +/* cc-info-row.c + * + * Copyright 2018 Matthias Clasen + * + * 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 . + * + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +#include +#include + +#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 000000000..57b9d4a9e --- /dev/null +++ b/panels/applications/cc-info-row.h @@ -0,0 +1,37 @@ +/* cc-info-row.h + * + * Copyright 2018 Matthias Clasen + * + * 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 . + * + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +#pragma once + +#include + +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 000000000..11ce000d2 --- /dev/null +++ b/panels/applications/cc-info-row.ui @@ -0,0 +1,40 @@ + + + + diff --git a/panels/applications/cc-toggle-row.c b/panels/applications/cc-toggle-row.c new file mode 100644 index 000000000..4895cc45d --- /dev/null +++ b/panels/applications/cc-toggle-row.c @@ -0,0 +1,144 @@ +/* cc-toggle-row.c + * + * Copyright 2018 Matthias Clasen + * + * 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 . + * + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +#include +#include + +#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 000000000..cfc66b91e --- /dev/null +++ b/panels/applications/cc-toggle-row.h @@ -0,0 +1,37 @@ +/* cc-toggle-row.h + * + * Copyright 2018 Matthias Clasen + * + * 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 . + * + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +#pragma once + +#include + +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 000000000..679bf6827 --- /dev/null +++ b/panels/applications/cc-toggle-row.ui @@ -0,0 +1,29 @@ + + + + diff --git a/panels/applications/globs.c b/panels/applications/globs.c new file mode 100644 index 000000000..4d2c939e2 --- /dev/null +++ b/panels/applications/globs.c @@ -0,0 +1,62 @@ +/* globs.c + * + * Copyright 2018 Matthias Clasen + * + * 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 . + * + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +#include + +#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 000000000..0a54588c0 --- /dev/null +++ b/panels/applications/globs.h @@ -0,0 +1,29 @@ +/* globs.h + * + * Copyright 2018 Matthias Clasen + * + * 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 . + * + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +#pragma once + +#include + +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 000000000..76ccda921 --- /dev/null +++ b/panels/applications/gnome-applications-panel.desktop.in.in @@ -0,0 +1,15 @@ +[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=application-x-executable-symbolic +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; diff --git a/panels/applications/meson.build b/panels/applications/meson.build new file mode 100644 index 000000000..49796a322 --- /dev/null +++ b/panels/applications/meson.build @@ -0,0 +1,49 @@ +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 +) + +cflags += '-DGNOMELOCALEDIR="@0@"'.format(control_center_localedir) + +panels_libs += static_library( + cappletname, + sources : sources, + include_directories : [ top_inc, common_inc ], + dependencies : common_deps, + c_args : cflags +) diff --git a/panels/applications/search.c b/panels/applications/search.c new file mode 100644 index 000000000..448918ba5 --- /dev/null +++ b/panels/applications/search.c @@ -0,0 +1,133 @@ +/* search.c + * + * Copyright 2018 Matthias Clasen + * + * 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 . + * + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +#include + +#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 000000000..b7ade8240 --- /dev/null +++ b/panels/applications/search.h @@ -0,0 +1,29 @@ +/* globs.h + * + * Copyright 2018 Matthias Clasen + * + * 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 . + * + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +#pragma once + +#include + +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 000000000..0f8cc2976 --- /dev/null +++ b/panels/applications/utils.c @@ -0,0 +1,209 @@ +/* utils.c + * + * Copyright 2018 Matthias Clasen + * + * 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 . + * + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +#ifndef _XOPEN_SOURCE +#define _XOPEN_SOURCE 600 +#endif + +#include +#include +#include +#include + +#include + +#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); +} + +void +file_remove_async (GFile *file, + GAsyncReadyCallback callback, + gpointer data) +{ + g_autoptr(GTask) task = g_task_new (file, NULL, callback, data); + g_task_run_in_thread (task, file_remove_thread_func); +} + +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); + + g_object_set_data_full (G_OBJECT (task), "size", total, g_free); +} + +void +file_size_async (GFile *file, + GAsyncReadyCallback callback, + gpointer data) +{ + g_autoptr(GTask) task = g_task_new (file, NULL, callback, data); + g_task_run_in_thread (task, file_size_thread_func); +} + +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); +} + +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 000000000..557e455b6 --- /dev/null +++ b/panels/applications/utils.h @@ -0,0 +1,44 @@ +/* utils.h + * + * Copyright 2018 Matthias Clasen + * + * 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 . + * + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +#pragma once + +#include +#include + +G_BEGIN_DECLS + +void file_remove_async (GFile *file, + GAsyncReadyCallback callback, + gpointer data); + +void file_size_async (GFile *file, + GAsyncReadyCallback callback, + gpointer data); + +void container_remove_all (GtkContainer *container); + +GKeyFile* get_flatpak_metadata (const gchar *app_id); + +guint64 get_flatpak_app_size (const gchar *app_id); + +gchar* get_app_id (GAppInfo *info); + +G_END_DECLS diff --git a/panels/meson.build b/panels/meson.build index 37a343642..95a44bb9d 100644 --- a/panels/meson.build +++ b/panels/meson.build @@ -1,6 +1,7 @@ subdir('common') panels = [ + 'applications', 'background', 'color', 'datetime', diff --git a/shell/cc-panel-list.c b/shell/cc-panel-list.c index 17899d44e..5bdc4c899 100644 --- a/shell/cc-panel-list.c +++ b/shell/cc-panel-list.c @@ -386,6 +386,7 @@ static const gchar * const panel_order[] = { "universal-access", "online-accounts", "privacy", + "applications", "sharing", "sound", "power", diff --git a/shell/cc-panel-loader.c b/shell/cc-panel-loader.c index 2798735c4..5eeb44e60 100644 --- a/shell/cc-panel-loader.c +++ b/shell/cc-panel-loader.c @@ -31,6 +31,7 @@ #ifndef CC_PANEL_LOADER_NO_GTYPES /* Extension points */ +extern GType cc_applications_panel_get_type (void); extern GType cc_background_panel_get_type (void); #ifdef BUILD_BLUETOOTH extern GType cc_bluetooth_panel_get_type (void); @@ -83,6 +84,7 @@ extern void cc_wacom_panel_static_init_func (void); static CcPanelLoaderVtable default_panels[] = { + PANEL_TYPE("applications", cc_applications_panel_get_type, NULL), PANEL_TYPE("background", cc_background_panel_get_type, NULL), #ifdef BUILD_BLUETOOTH PANEL_TYPE("bluetooth", cc_bluetooth_panel_get_type, NULL),