[gnome-software/wip/rancell/permissions: 139/139] Add a basic permissions system



commit 1d226097ed37e82c5173651f9d3802a90e465784
Author: Robert Ancell <robert ancell canonical com>
Date:   Fri May 26 16:30:56 2017 +1200

    Add a basic permissions system

 lib/gs-app.c                  |   47 ++++++++
 lib/gs-app.h                  |    4 +
 lib/gs-permission.c           |  252 +++++++++++++++++++++++++++++++++++++++++
 lib/gs-permission.h           |   57 +++++++++
 lib/meson.build               |    2 +
 plugins/snap/gs-plugin-snap.c |   50 ++++++++
 src/gs-details-page.c         |   40 +++++++
 src/gs-details-page.ui        |   55 ++++++++-
 src/gtk-style-hc.css          |    5 +
 src/gtk-style.css             |    5 +
 10 files changed, 512 insertions(+), 5 deletions(-)
---
diff --git a/lib/gs-app.c b/lib/gs-app.c
index d63654a..9e15f35 100644
--- a/lib/gs-app.c
+++ b/lib/gs-app.c
@@ -87,6 +87,7 @@ typedef struct
        gchar                   **menu_path;
        gchar                   *origin;
        gchar                   *origin_hostname;
+       GPtrArray               *permissions;
        gchar                   *update_version;
        gchar                   *update_version_ui;
        gchar                   *update_details;
@@ -441,6 +442,16 @@ gs_app_to_string_append (GsApp *app, GString *str)
                        gs_app_kv_lpad (str, "icon-filename",
                                        as_icon_get_filename (icon));
        }
+       for (i = 0; i < priv->permissions->len; i++) {
+               GsPermission *permission;
+               g_autofree gchar *key = NULL;
+
+               permission = g_ptr_array_index (priv->permissions, i);
+               key = g_strdup_printf ("permission-%02u", i);
+               gs_app_kv_printf (str, key, "[%s] %s",
+                                 gs_permission_get_label (permission),
+                                 gs_permission_get_enabled (permission) ? "true" : "false");
+       }
        if (priv->match_value != 0)
                gs_app_kv_printf (str, "match-value", "%05x", priv->match_value);
        if (priv->priority != 0)
@@ -2365,6 +2376,40 @@ gs_app_set_origin_hostname (GsApp *app, const gchar *origin_hostname)
 }
 
 /**
+ * gs_app_add_permission:
+ * @app: a #GsApp
+ * @permission: a #GsPermission
+ *
+ * Adds a permission to the applicaton.
+ *
+ * Since: 3.26
+ **/
+void
+gs_app_add_permission (GsApp *app, GsPermission *permission)
+{
+       g_return_if_fail (GS_IS_APP (app));
+       g_return_if_fail (GS_IS_PERMISSION (permission));
+       g_ptr_array_add (app->permissions, g_object_ref (permission));
+}
+
+/**
+ * gs_app_get_permissions:
+ * @app: a #GsApp
+ *
+ * Gets the list of permissions.
+ *
+ * Returns: (element-type GsPermission) (transfer none): a list
+ *
+ * Since: 3.26
+ **/
+GPtrArray *
+gs_app_get_permissions (GsApp *app)
+{
+       g_return_val_if_fail (GS_IS_APP (app), NULL);
+       return app->permissions;
+}
+
+/**
  * gs_app_add_screenshot:
  * @app: a #GsApp
  * @screenshot: a #AsScreenshot
@@ -3807,6 +3852,7 @@ gs_app_dispose (GObject *object)
        g_clear_pointer (&priv->reviews, g_ptr_array_unref);
        g_clear_pointer (&priv->provides, g_ptr_array_unref);
        g_clear_pointer (&priv->icons, g_ptr_array_unref);
+       g_clear_pointer (&priv->permissions, g_ptr_array_unref);
 
        G_OBJECT_CLASS (gs_app_parent_class)->dispose (object);
 }
@@ -3977,6 +4023,7 @@ gs_app_init (GsApp *app)
        priv->reviews = g_ptr_array_new_with_free_func ((GDestroyNotify) g_object_unref);
        priv->provides = g_ptr_array_new_with_free_func ((GDestroyNotify) g_object_unref);
        priv->icons = g_ptr_array_new_with_free_func ((GDestroyNotify) g_object_unref);
+       priv->permissions = g_ptr_array_new_with_free_func ((GDestroyNotify) g_object_unref);
        priv->metadata = g_hash_table_new_full (g_str_hash,
                                                g_str_equal,
                                                g_free,
diff --git a/lib/gs-app.h b/lib/gs-app.h
index a217be0..a20e1bd 100644
--- a/lib/gs-app.h
+++ b/lib/gs-app.h
@@ -28,6 +28,7 @@
 #include <appstream-glib.h>
 
 #include "gs-price.h"
+#include "gs-permission.h"
 
 G_BEGIN_DECLS
 
@@ -196,6 +197,9 @@ void                 gs_app_set_origin              (GsApp          *app,
 const gchar    *gs_app_get_origin_hostname     (GsApp          *app);
 void            gs_app_set_origin_hostname     (GsApp          *app,
                                                 const gchar    *origin_hostname);
+GPtrArray      *gs_app_get_permissions         (GsApp          *app);
+void            gs_app_add_permission          (GsApp          *app,
+                                                GsPermission   *permission);
 GPtrArray      *gs_app_get_screenshots         (GsApp          *app);
 void            gs_app_add_screenshot          (GsApp          *app,
                                                 AsScreenshot   *screenshot);
diff --git a/lib/gs-permission.c b/lib/gs-permission.c
new file mode 100644
index 0000000..397d783
--- /dev/null
+++ b/lib/gs-permission.c
@@ -0,0 +1,252 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*-
+ *
+ * Copyright (C) 2017 Canonical Ltd.
+ *
+ * Licensed under the GNU General Public License Version 2
+ *
+ * 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 2 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, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#include "config.h"
+
+#include <glib/gi18n.h>
+
+#include "gs-permission.h"
+
+struct _GsPermission
+{
+       GObject                  parent_instance;
+
+       gchar                   *label;
+       gboolean                 enabled;
+       GHashTable              *metadata;      /* utf8: utf8 */
+};
+
+G_DEFINE_TYPE (GsPermission, gs_permission, G_TYPE_OBJECT)
+
+/**
+ * gs_permission_get_metadata_item:
+ * @auth: a #GsPermission
+ * @key: a string
+ *
+ * Gets some metadata from a permission object.
+ * It is left for the the plugin to use this method as required, but a
+ * typical use would be to retrieve an ID for this permission.
+ *
+ * Returns: A string value, or %NULL for not found
+ */
+const gchar *
+gs_permission_get_metadata_item (GsPermission *auth, const gchar *key)
+{
+       g_return_val_if_fail (GS_IS_PERMISSION (auth), NULL);
+       g_return_val_if_fail (key != NULL, NULL);
+       return g_hash_table_lookup (auth->metadata, key);
+}
+
+/**
+ * gs_permission_add_metadata:
+ * @auth: a #GsPermission
+ * @key: a string
+ * @value: a string
+ *
+ * Adds metadata to the permission object.
+ * It is left for the the plugin to use this method as required, but a
+ * typical use would be to store an ID for this permission.
+ */
+void
+gs_permission_add_metadata (GsPermission *auth, const gchar *key, const gchar *value)
+{
+       g_return_if_fail (GS_IS_PERMISSION (auth));
+       g_hash_table_insert (auth->metadata, g_strdup (key), g_strdup (value));
+}
+
+/**
+ * gs_permission_get_label:
+ * @permission: a #GsPermission
+ *
+ * Get the label for this permission.
+ *
+ * Returns: a label string.
+ */
+const gchar *
+gs_permission_get_label (GsPermission *permission)
+{
+       g_return_val_if_fail (GS_IS_PERMISSION (permission), NULL);
+       return permission->label;
+}
+
+/**
+ * gs_permission_get_enabled:
+ * @permission: a #GsPermission
+ *
+ * Get if this permission is enabled.
+ *
+ * Returns: %TRUE if enabled
+ */
+gboolean
+gs_permission_get_enabled (GsPermission *permission)
+{
+       g_return_val_if_fail (GS_IS_PERMISSION (permission), 0);
+       return permission->enabled;
+}
+
+/**
+ * gs_permission_set_enabled:
+ * @permission: a #GsPermission
+ * @enabled: %TRUE if this permission is enabled.
+ *
+ * Set if this permission is enabled.
+ */
+void
+gs_permission_set_enabled (GsPermission *permission, gboolean enabled)
+{
+       g_return_if_fail (GS_IS_PERMISSION (permission));
+       permission->enabled = enabled;
+}
+
+static void
+gs_permission_finalize (GObject *object)
+{
+       GsPermission *permission = GS_PERMISSION (object);
+
+       g_free (permission->label);
+       g_hash_table_unref (permission->metadata);
+
+       G_OBJECT_CLASS (gs_permission_parent_class)->finalize (object);
+}
+
+static void
+gs_permission_class_init (GsPermissionClass *klass)
+{
+       GObjectClass *object_class = G_OBJECT_CLASS (klass);
+       object_class->finalize = gs_permission_finalize;
+}
+
+static void
+gs_permission_init (GsPermission *permission)
+{
+       permission->metadata = g_hash_table_new_full (g_str_hash, g_str_equal,
+                                                     g_free, g_free);
+}
+
+static GsPermission *
+gs_permission_new (const gchar *label, gboolean enabled)
+{
+       GsPermission *permission;
+       permission = g_object_new (GS_TYPE_PERMISSION, NULL);
+       permission->label = g_strdup (label);
+       permission->enabled = enabled;
+       return GS_PERMISSION (permission);
+}
+
+/**
+ * gs_permission_new_camera:
+ * @enabled: %TRUE if camera access is enabled.
+ *
+ * Creates a new permission object for camera access.
+ *
+ * Return value: a new #GsPermission object.
+ **/
+GsPermission *
+gs_permission_new_camera (gboolean enabled)
+{
+       return gs_permission_new (_("Can acquire photos / video from cameras"), enabled);
+}
+
+/**
+ * gs_permission_new_bluetooth:
+ * @enabled: %TRUE if Bluetooth access is enabled.
+ *
+ * Creates a new permission object for Bluetooth access.
+ *
+ * Return value: a new #GsPermission object.
+ **/
+GsPermission *
+gs_permission_new_bluetooth (gboolean enabled)
+{
+       return gs_permission_new (_("Can access Bluetooth devices"), enabled);
+}
+
+/**
+ * gs_permission_new_network:
+ * @enabled: %TRUE if network access is enabled.
+ *
+ * Creates a new permission object for network access.
+ *
+ * Return value: a new #GsPermission object.
+ **/
+GsPermission *
+gs_permission_new_network (gboolean enabled)
+{
+       return gs_permission_new (_("Can access the network"), enabled);
+}
+
+/**
+ * gs_permission_new_media:
+ * @enabled: %TRUE if removable media is enabled.
+ *
+ * Creates a new permission object for removable media.
+ *
+ * Return value: a new #GsPermission object.
+ **/
+GsPermission *
+gs_permission_new_media (gboolean enabled)
+{
+       return gs_permission_new (_("Can access removable media (USB drives etc)"), enabled);
+}
+
+/**
+ * gs_permission_new_optical_drive:
+ * @enabled: %TRUE if optical drive access is enabled.
+ *
+ * Creates a new permission object for optical drive access.
+ *
+ * Return value: a new #GsPermission object.
+ **/
+GsPermission *
+gs_permission_new_optical_drive (gboolean enabled)
+{
+       return gs_permission_new (_("Can access optical drives (CD, DVD etc)"), enabled);
+}
+
+/**
+ * gs_permission_new_printing:
+ * @enabled: %TRUE if printing access is enabled.
+ *
+ * Creates a new permission object for printing access.
+ *
+ * Return value: a new #GsPermission object.
+ **/
+GsPermission *
+gs_permission_new_printing (gboolean enabled)
+{
+       return gs_permission_new (_("Can print documents"), enabled);
+}
+
+/**
+ * gs_permission_new_shutdown:
+ * @enabled: %TRUE if shutdown / restart is allowed.
+ *
+ * Creates a new permission object for shutdown / restart of this machine.
+ *
+ * Return value: a new #GsPermission object.
+ **/
+GsPermission *
+gs_permission_new_shutdown (gboolean enabled)
+{
+       return gs_permission_new (_("Can shutdown / restart this computer"), enabled);
+}
+
+/* vim: set noexpandtab: */
diff --git a/lib/gs-permission.h b/lib/gs-permission.h
new file mode 100644
index 0000000..c952ac3
--- /dev/null
+++ b/lib/gs-permission.h
@@ -0,0 +1,57 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*-
+ *
+ * Copyright (C) 2017 Canonical Ltd.
+ *
+ * Licensed under the GNU General Public License Version 2
+ *
+ * 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 2 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, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#ifndef __GS_PERMISSION_H
+#define __GS_PERMISSION_H
+
+#include <glib-object.h>
+#include <gdk/gdk.h>
+
+G_BEGIN_DECLS
+
+#define GS_TYPE_PERMISSION (gs_permission_get_type ())
+
+G_DECLARE_FINAL_TYPE (GsPermission, gs_permission, GS, PERMISSION, GObject)
+
+GsPermission   *gs_permission_new_bluetooth            (gboolean        enabled);
+GsPermission   *gs_permission_new_camera               (gboolean        enabled);
+GsPermission   *gs_permission_new_network              (gboolean        enabled);
+GsPermission   *gs_permission_new_media                (gboolean        enabled);
+GsPermission   *gs_permission_new_optical_drive        (gboolean        enabled);
+GsPermission   *gs_permission_new_printing             (gboolean        enabled);
+GsPermission   *gs_permission_new_shutdown             (gboolean        enabled);
+
+const gchar    *gs_permission_get_metadata_item        (GsPermission   *permission,
+                                                        const gchar    *key);
+void            gs_permission_add_metadata             (GsPermission   *permission,
+                                                        const gchar    *key,
+                                                        const gchar    *value);
+
+const gchar    *gs_permission_get_label                (GsPermission   *permission);
+gboolean        gs_permission_get_enabled              (GsPermission   *permission);
+void            gs_permission_set_enabled              (GsPermission   *permission,
+                                                        gboolean        enabled);
+
+G_END_DECLS
+
+#endif /* __GS_PERMISSION_H */
+
+/* vim: set noexpandtab: */
diff --git a/lib/meson.build b/lib/meson.build
index 92c92ac..d55b73f 100644
--- a/lib/meson.build
+++ b/lib/meson.build
@@ -43,6 +43,7 @@ install_headers([
     'gs-auth.h',
     'gs-category.h',
     'gs-os-release.h',
+    'gs-permission.h',
     'gs-plugin.h',
     'gs-plugin-event.h',
     'gs-plugin-types.h',
@@ -78,6 +79,7 @@ libgnomesoftware = static_library(
     'gs-category.c',
     'gs-debug.c',
     'gs-os-release.c',
+    'gs-permission.c',
     'gs-plugin.c',
     'gs-plugin-event.c',
     'gs-plugin-job.c',
diff --git a/plugins/snap/gs-plugin-snap.c b/plugins/snap/gs-plugin-snap.c
index 1e7813d..c0998a9 100644
--- a/plugins/snap/gs-plugin-snap.c
+++ b/plugins/snap/gs-plugin-snap.c
@@ -591,6 +591,56 @@ gs_plugin_refine_app (GsPlugin *plugin,
                }
        }
 
+       if (gs_app_get_permissions (app)->len == 0) {
+               g_autoptr(GPtrArray) plugs = NULL;
+               g_autoptr(GPtrArray) slots = NULL;
+               guint i;
+
+               if (!snapd_client_get_interfaces_sync (client, &plugs, &slots, cancellable, error))
+                       return FALSE;
+               for (i = 0; i < plugs->len; i++) {
+                       SnapdPlug *plug = plugs->pdata[i];
+                       const gchar *name;
+                       gboolean is_connected;
+                       g_autoptr(GsPermission) permission = NULL;
+
+                       /* skip if not relating to this snap */
+                       if (g_strcmp0 (snapd_plug_get_snap (plug), gs_app_get_name (app)) != 0)
+                               continue;
+
+                       /* map interfaces to known permissions */
+                       name = snapd_plug_get_name (plug);
+                       is_connected = snapd_plug_get_connections (plug)->len > 0;
+                       if (g_strcmp0 (name, "camera") == 0)
+                               permission = gs_permission_new_camera (is_connected);
+                       else if (g_strcmp0 (name, "removable-media") == 0)
+                               permission = gs_permission_new_media (is_connected);
+                       else if (g_strcmp0 (name, "optical-drive") == 0)
+                               permission = gs_permission_new_optical_drive (is_connected);
+                       else if (g_strcmp0 (name, "network") == 0)
+                               permission = gs_permission_new_network (is_connected);
+                       else if (g_strcmp0 (name, "cups-control") == 0)
+                               permission = gs_permission_new_printing (is_connected);
+                       else if (g_strcmp0 (name, "bluetooth-control") == 0)
+                               permission = gs_permission_new_bluetooth (is_connected);
+                       else if (g_strcmp0 (name, "shutdown") == 0)
+                               permission = gs_permission_new_shutdown (is_connected);
+                       else if (g_strcmp0 (name, "home") == 0 ||
+                                g_strcmp0 (name, "opengl") == 0 ||
+                                g_strcmp0 (name, "pulseaudio") == 0 ||
+                                g_strcmp0 (name, "unity7") == 0 ||
+                                g_strcmp0 (name, "x11") == 0) {
+                               g_debug ("Ignoring common plug %s:%s", snapd_plug_get_snap (plug), name);
+                               continue;
+                       } else {
+                               g_warning ("Ignoring unknown plug %s:%s", snapd_plug_get_snap (plug), name);
+                               continue;
+                       }
+
+                       gs_app_add_permission (app, permission);
+               }
+       }
+
        return TRUE;
 }
 
diff --git a/src/gs-details-page.c b/src/gs-details-page.c
index 8b882eb..72f2390 100644
--- a/src/gs-details-page.c
+++ b/src/gs-details-page.c
@@ -76,6 +76,7 @@ struct _GsDetailsPage
        GtkWidget               *box_details_screenshot_main;
        GtkWidget               *box_details_screenshot_thumbnails;
        GtkWidget               *box_details_license_list;
+       GtkWidget               *box_permissions;
        GtkWidget               *button_details_launch;
        GtkWidget               *button_details_add_shortcut;
        GtkWidget               *button_details_remove_shortcut;
@@ -121,6 +122,7 @@ struct _GsDetailsPage
        GtkWidget               *spinner_remove;
        GtkWidget               *stack_details;
        GtkWidget               *grid_details_kudo;
+       GtkWidget               *grid_permissions;
        GtkWidget               *image_details_kudo_docs;
        GtkWidget               *image_details_kudo_sandboxed;
        GtkWidget               *image_details_kudo_integration;
@@ -521,6 +523,40 @@ gs_details_page_notify_state_changed_cb (GsApp *app,
 }
 
 static void
+gs_details_page_refresh_permissions (GsDetailsPage *self)
+{
+       GPtrArray *permissions;
+       guint i;
+
+       /* nothing to show */
+       if (self->app == NULL)
+               return;
+
+       /* show or hide the entire permissions section */
+       permissions = gs_app_get_permissions (self->app);
+       gtk_widget_set_visible (self->box_permissions, permissions->len > 0);
+
+       gtk_container_foreach (GTK_CONTAINER (self->grid_permissions),
+                              (GtkCallback) gtk_widget_destroy, NULL);
+       for (i = 0; i < permissions->len; i++) {
+               GsPermission *permission = g_ptr_array_index (permissions, i);
+               GtkWidget *sw, *label;
+
+               sw = gtk_switch_new ();
+               gtk_widget_set_visible (sw, TRUE);
+               gtk_switch_set_active (GTK_SWITCH (sw), gs_permission_get_enabled (permission));
+               gtk_grid_attach (GTK_GRID (self->grid_permissions), sw, 0, i, 1, 1);
+
+               label = gtk_label_new (gs_permission_get_label (permission));
+               g_object_set (label,
+                             "xalign", 0.0,
+                             NULL);
+               gtk_widget_set_visible (label, TRUE);
+               gtk_grid_attach (GTK_GRID (self->grid_permissions), label, 1, i, 1, 1);
+       }
+}
+
+static void
 gs_details_page_screenshot_selected_cb (GtkListBox *list,
                                         GtkListBoxRow *row,
                                         GsDetailsPage *self)
@@ -1459,6 +1495,7 @@ gs_details_page_app_refine_cb (GObject *source,
        app_dump = gs_app_to_string (self->app);
        g_debug ("%s", app_dump);
 
+       gs_details_page_refresh_permissions (self);
        gs_details_page_refresh_screenshots (self);
        gs_details_page_refresh_addons (self);
        gs_details_page_refresh_reviews (self);
@@ -1508,6 +1545,7 @@ set_app (GsDetailsPage *self, GsApp *app)
 
        /* change widgets */
        gs_page_switch_to (GS_PAGE (self), TRUE);
+       gs_details_page_refresh_permissions (self);
        gs_details_page_refresh_screenshots (self);
        gs_details_page_refresh_addons (self);
        gs_details_page_refresh_reviews (self);
@@ -2250,6 +2288,7 @@ gs_details_page_class_init (GsDetailsPageClass *klass)
        gtk_widget_class_bind_template_child (widget_class, GsDetailsPage, box_details_screenshot_main);
        gtk_widget_class_bind_template_child (widget_class, GsDetailsPage, box_details_screenshot_thumbnails);
        gtk_widget_class_bind_template_child (widget_class, GsDetailsPage, box_details_license_list);
+       gtk_widget_class_bind_template_child (widget_class, GsDetailsPage, box_permissions);
        gtk_widget_class_bind_template_child (widget_class, GsDetailsPage, button_details_launch);
        gtk_widget_class_bind_template_child (widget_class, GsDetailsPage, button_details_add_shortcut);
        gtk_widget_class_bind_template_child (widget_class, GsDetailsPage, button_details_remove_shortcut);
@@ -2293,6 +2332,7 @@ gs_details_page_class_init (GsDetailsPageClass *klass)
        gtk_widget_class_bind_template_child (widget_class, GsDetailsPage, spinner_remove);
        gtk_widget_class_bind_template_child (widget_class, GsDetailsPage, stack_details);
        gtk_widget_class_bind_template_child (widget_class, GsDetailsPage, grid_details_kudo);
+       gtk_widget_class_bind_template_child (widget_class, GsDetailsPage, grid_permissions);
        gtk_widget_class_bind_template_child (widget_class, GsDetailsPage, image_details_kudo_docs);
        gtk_widget_class_bind_template_child (widget_class, GsDetailsPage, image_details_kudo_sandboxed);
        gtk_widget_class_bind_template_child (widget_class, GsDetailsPage, image_details_kudo_integration);
diff --git a/src/gs-details-page.ui b/src/gs-details-page.ui
index cacd1a0..888ca7f 100644
--- a/src/gs-details-page.ui
+++ b/src/gs-details-page.ui
@@ -571,7 +571,7 @@
                               <packing>
                                 <property name="expand">False</property>
                                 <property name="fill">False</property>
-                                <property name="position">0</property>
+                                <property name="position">1</property>
                               </packing>
                             </child>
                           </object>
@@ -582,6 +582,51 @@
                           </packing>
                         </child>
                         <child>
+                          <object class="GtkBox" id="box_permissions">
+                            <property name="visible">False</property>
+                            <property name="orientation">vertical</property>
+                            <property name="margin_bottom">26</property>
+                            <property name="spacing">30</property>
+                            <child>
+                              <object class="GtkLabel">
+                                <property name="visible">True</property>
+                                <property name="can_focus">False</property>
+                                <property name="halign">start</property>
+                                <property name="valign">start</property>
+                                <property name="hexpand">True</property>
+                                <property name="xalign">0</property>
+                                <property name="label" translatable="yes">Permissions</property>
+                                <style>
+                                  <class name="application-permissions-title"/>
+                                </style>
+                              </object>
+                              <packing>
+                                <property name="expand">False</property>
+                                <property name="fill">True</property>
+                                <property name="position">0</property>
+                              </packing>
+                            </child>
+                            <child>
+                              <object class="GtkGrid" id="grid_permissions">
+                                <property name="visible">True</property>
+                                <property name="can_focus">False</property>
+                                <property name="row_spacing">9</property>
+                                <property name="column_spacing">24</property>
+                              </object>
+                              <packing>
+                                <property name="expand">True</property>
+                                <property name="fill">True</property>
+                                <property name="position">1</property>
+                              </packing>
+                            </child>
+                          </object>
+                          <packing>
+                            <property name="expand">False</property>
+                            <property name="fill">False</property>
+                            <property name="position">11</property>
+                          </packing>
+                        </child>
+                        <child>
                           <object class="GtkLabel" id="application_details_details_title">
                             <property name="visible">True</property>
                             <property name="can_focus">False</property>
@@ -597,7 +642,7 @@
                           <packing>
                             <property name="expand">False</property>
                             <property name="fill">True</property>
-                            <property name="position">11</property>
+                            <property name="position">12</property>
                           </packing>
                         </child>
                         <child>
@@ -1135,7 +1180,7 @@
                           <packing>
                             <property name="expand">False</property>
                             <property name="fill">True</property>
-                            <property name="position">12</property>
+                            <property name="position">13</property>
                           </packing>
                         </child>
                         <child>
@@ -1198,7 +1243,7 @@
                           <packing>
                             <property name="expand">False</property>
                             <property name="fill">True</property>
-                            <property name="position">13</property>
+                            <property name="position">14</property>
                           </packing>
                         </child>
                         <child>
@@ -1263,7 +1308,7 @@
                           <packing>
                             <property name="expand">False</property>
                             <property name="fill">True</property>
-                            <property name="position">14</property>
+                            <property name="position">15</property>
                           </packing>
                         </child>
                       </object>
diff --git a/src/gtk-style-hc.css b/src/gtk-style-hc.css
index 5dd475c..fb8cfdc 100644
--- a/src/gtk-style-hc.css
+++ b/src/gtk-style-hc.css
@@ -96,6 +96,11 @@
        text-shadow: none;
 }
 
+.application-permissions-title {
+       font-weight: bold;
+       font-size: 125%;
+}
+
 .application-details-title {
        font-weight: bold;
        font-size: 125%;
diff --git a/src/gtk-style.css b/src/gtk-style.css
index cc3565d..d36747f 100644
--- a/src/gtk-style.css
+++ b/src/gtk-style.css
@@ -247,6 +247,11 @@
        text-shadow: none;
 }
 
+.application-permissions-title {
+       font-weight: bold;
+       font-size: 125%;
+}
+
 .application-details-title {
        font-weight: bold;
        font-size: 125%;


[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]