summaryrefslogtreecommitdiffstats
path: root/shell
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-27 14:36:24 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-27 14:36:24 +0000
commit9b6d8e63db85c30007b463e91f91a791969fa83f (patch)
tree0899af51d73c1bf986f73ae39a03c4436083018a /shell
parentInitial commit. (diff)
downloadgnome-control-center-9b6d8e63db85c30007b463e91f91a791969fa83f.tar.xz
gnome-control-center-9b6d8e63db85c30007b463e91f91a791969fa83f.zip
Adding upstream version 1:3.38.4.upstream/1%3.38.4upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'shell')
-rw-r--r--shell/appdata/gnome-control-center.appdata.xml.in30
-rw-r--r--shell/appdata/meson.build10
-rw-r--r--shell/cc-application.c310
-rw-r--r--shell/cc-application.h36
-rw-r--r--shell/cc-debug.h.in229
-rw-r--r--shell/cc-log.c101
-rw-r--r--shell/cc-log.h25
-rw-r--r--shell/cc-object-storage.c444
-rw-r--r--shell/cc-object-storage.h65
-rw-r--r--shell/cc-panel-list.c1112
-rw-r--r--shell/cc-panel-list.h78
-rw-r--r--shell/cc-panel-list.ui147
-rw-r--r--shell/cc-panel-loader.c316
-rw-r--r--shell/cc-panel-loader.h52
-rw-r--r--shell/cc-panel.c257
-rw-r--r--shell/cc-panel.h100
-rw-r--r--shell/cc-shell-model.c422
-rw-r--r--shell/cc-shell-model.h84
-rw-r--r--shell/cc-shell.c175
-rw-r--r--shell/cc-shell.h77
-rw-r--r--shell/cc-window.c948
-rw-r--r--shell/cc-window.h39
-rw-r--r--shell/cc-window.ui336
-rw-r--r--shell/completions/gnome-control-center.in51
-rw-r--r--shell/completions/meson.build12
-rw-r--r--shell/gnome-control-center.desktop.in.in16
-rw-r--r--shell/gnome-control-center.gresource.xml9
-rw-r--r--shell/help-overlay.ui61
-rwxr-xr-xshell/list-panel.sh8
-rw-r--r--shell/main.c66
-rw-r--r--shell/meson.build176
-rw-r--r--shell/org.gnome.ControlCenter.gschema.xml19
-rw-r--r--shell/org.gnome.ControlCenter.service.in3
-rw-r--r--shell/style.css7
34 files changed, 5821 insertions, 0 deletions
diff --git a/shell/appdata/gnome-control-center.appdata.xml.in b/shell/appdata/gnome-control-center.appdata.xml.in
new file mode 100644
index 0000000..bbed55c
--- /dev/null
+++ b/shell/appdata/gnome-control-center.appdata.xml.in
@@ -0,0 +1,30 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- Copyright 2015 Richard Hughes <richard@hughsie.com> -->
+<component type="desktop">
+ <id>gnome-control-center.desktop</id>
+ <metadata_license>CC0-1.0</metadata_license>
+ <project_license>GPL-2.0+</project_license>
+ <name>GNOME Settings</name>
+ <summary>Utility to configure the GNOME desktop</summary>
+ <description>
+ <p>
+ Settings is the primary interface for configuring your system.
+ </p>
+ </description>
+ <url type="bugtracker">https://gitlab.gnome.org/GNOME/gnome-control-center/issues/</url>
+ <url type="donation">https://www.gnome.org/friends/</url>
+ <url type="translate">https://wiki.gnome.org/TranslationProject</url>
+ <update_contact>gnomecc-list_at_gnome.org</update_contact>
+ <project_group>GNOME</project_group>
+ <launchable type="desktop-id">gnome-control-center.desktop</launchable>
+ <developer_name>The GNOME Project</developer_name>
+ <compulsory_for_desktop>GNOME</compulsory_for_desktop>
+ <kudos>
+ <kudo>HiDpiIcon</kudo>
+ <kudo>HighContrast</kudo>
+ <kudo>ModernToolkit</kudo>
+ <kudo>SearchProvider</kudo>
+ <kudo>UserDocs</kudo>
+ </kudos>
+ <translation type="gettext">gnome-control-center-2.0</translation>
+</component>
diff --git a/shell/appdata/meson.build b/shell/appdata/meson.build
new file mode 100644
index 0000000..3b36171
--- /dev/null
+++ b/shell/appdata/meson.build
@@ -0,0 +1,10 @@
+appdata = 'gnome-control-center.appdata.xml'
+
+i18n.merge_file(
+ appdata,
+ input: appdata + '.in',
+ output: appdata,
+ po_dir: po_dir,
+ install: true,
+ install_dir: join_paths(control_center_datadir, 'metainfo')
+)
diff --git a/shell/cc-application.c b/shell/cc-application.c
new file mode 100644
index 0000000..4a90157
--- /dev/null
+++ b/shell/cc-application.c
@@ -0,0 +1,310 @@
+/*
+ * Copyright © 2013 Red Hat, Inc.
+ *
+ * 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 <stdlib.h>
+
+#include <glib.h>
+#include <glib/gi18n.h>
+#include <gio/gio.h>
+#include <handy.h>
+
+#include "cc-application.h"
+#include "cc-log.h"
+#include "cc-object-storage.h"
+#include "cc-panel-loader.h"
+#include "cc-window.h"
+
+struct _CcApplication
+{
+ GtkApplication parent;
+
+ CcShellModel *model;
+
+ CcWindow *window;
+};
+
+static void cc_application_quit (GSimpleAction *simple,
+ GVariant *parameter,
+ gpointer user_data);
+
+static void launch_panel_activated (GSimpleAction *action,
+ GVariant *parameter,
+ gpointer user_data);
+
+static void help_activated (GSimpleAction *action,
+ GVariant *parameter,
+ gpointer user_data);
+
+G_DEFINE_TYPE (CcApplication, cc_application, GTK_TYPE_APPLICATION)
+
+const GOptionEntry all_options[] = {
+ { "version", 0, 0, G_OPTION_ARG_NONE, NULL, N_("Display version number"), NULL },
+ { "verbose", 'v', 0, G_OPTION_ARG_NONE, NULL, N_("Enable verbose mode"), NULL },
+ { "search", 's', 0, G_OPTION_ARG_STRING, NULL, N_("Search for the string"), "SEARCH" },
+ { "list", 'l', 0, G_OPTION_ARG_NONE, NULL, N_("List possible panel names and exit"), NULL },
+ { G_OPTION_REMAINING, '\0', 0, G_OPTION_ARG_FILENAME_ARRAY, NULL, N_("Panel to display"), N_("[PANEL] [ARGUMENT…]") },
+ { NULL, 0, 0, 0, NULL, NULL, NULL } /* end the list */
+};
+
+static const GActionEntry cc_app_actions[] = {
+ { "launch-panel", launch_panel_activated, "(sav)", NULL, NULL, { 0 } },
+ { "help", help_activated, NULL, NULL, NULL, { 0 } },
+ { "quit", cc_application_quit, NULL, NULL, NULL, { 0 } }
+};
+
+static void
+help_activated (GSimpleAction *action,
+ GVariant *parameter,
+ gpointer user_data)
+{
+ CcApplication *self = CC_APPLICATION (user_data);
+ CcPanel *panel;
+ GtkWidget *window;
+ const char *uri = NULL;
+
+ panel = cc_shell_get_active_panel (CC_SHELL (self->window));
+ if (panel)
+ uri = cc_panel_get_help_uri (panel);
+
+ window = cc_shell_get_toplevel (CC_SHELL (self->window));
+ gtk_show_uri_on_window (GTK_WINDOW (window),
+ uri ? uri : "help:gnome-help/prefs",
+ GDK_CURRENT_TIME,
+ NULL);
+}
+
+static void
+launch_panel_activated (GSimpleAction *action,
+ GVariant *parameter,
+ gpointer user_data)
+{
+ CcApplication *self = CC_APPLICATION (user_data);
+ g_autoptr(GVariant) parameters = NULL;
+ g_autoptr(GError) error = NULL;
+ gchar *panel_id;
+
+ g_variant_get (parameter, "(&s@av)", &panel_id, &parameters);
+
+ g_debug ("gnome-control-center: 'launch-panel' activated for panel '%s' with %"G_GSIZE_FORMAT" arguments",
+ panel_id,
+ g_variant_n_children (parameters));
+
+ if (!cc_shell_set_active_panel_from_id (CC_SHELL (self->window), panel_id, parameters, &error))
+ g_warning ("Failed to activate the '%s' panel: %s", panel_id, error->message);
+
+ /* Now present the window */
+ g_application_activate (G_APPLICATION (self));
+}
+
+static gint
+cc_application_handle_local_options (GApplication *application,
+ GVariantDict *options)
+{
+ if (g_variant_dict_contains (options, "version"))
+ {
+ g_print ("%s %s\n", PACKAGE, VERSION);
+ return 0;
+ }
+
+ if (g_variant_dict_contains (options, "list"))
+ {
+ cc_panel_loader_list_panels ();
+ return 0;
+ }
+
+ return -1;
+}
+
+static int
+cc_application_command_line (GApplication *application,
+ GApplicationCommandLine *command_line)
+{
+ CcApplication *self;
+ g_autofree GStrv start_panels = NULL;
+ GVariantDict *options;
+ int retval = 0;
+ char *search_str;
+ gboolean debug;
+
+ self = CC_APPLICATION (application);
+ options = g_application_command_line_get_options_dict (command_line);
+
+ debug = g_variant_dict_contains (options, "verbose");
+
+ if (debug)
+ cc_log_init ();
+
+ gtk_window_present (GTK_WINDOW (self->window));
+
+ if (g_variant_dict_lookup (options, "search", "&s", &search_str))
+ {
+ cc_window_set_search_item (self->window, search_str);
+ }
+ else if (g_variant_dict_lookup (options, G_OPTION_REMAINING, "^a&ay", &start_panels))
+ {
+ const char *start_id;
+ GError *err = NULL;
+ GVariant *parameters;
+ GVariantBuilder builder;
+ int i;
+
+ g_return_val_if_fail (start_panels[0] != NULL, 1);
+ start_id = start_panels[0];
+
+ if (start_panels[1])
+ g_debug ("Extra argument: %s", start_panels[1]);
+ else
+ g_debug ("No extra argument");
+
+ g_variant_builder_init (&builder, G_VARIANT_TYPE ("av"));
+
+ for (i = 1; start_panels[i] != NULL; i++)
+ g_variant_builder_add (&builder, "v", g_variant_new_string (start_panels[i]));
+ parameters = g_variant_builder_end (&builder);
+ if (!cc_shell_set_active_panel_from_id (CC_SHELL (self->window), start_id, parameters, &err))
+ {
+ g_warning ("Could not load setting panel \"%s\": %s", start_id,
+ (err) ? err->message : "Unknown error");
+ retval = 1;
+
+ if (err)
+ g_clear_error (&err);
+ }
+ }
+
+ return retval;
+}
+
+static void
+cc_application_quit (GSimpleAction *simple,
+ GVariant *parameter,
+ gpointer user_data)
+{
+ CcApplication *self = CC_APPLICATION (user_data);
+
+ gtk_widget_destroy (GTK_WIDGET (self->window));
+}
+
+
+static void
+cc_application_activate (GApplication *application)
+{
+ CcApplication *self = CC_APPLICATION (application);
+
+ gtk_window_present (GTK_WINDOW (self->window));
+}
+
+static void
+cc_application_startup (GApplication *application)
+{
+ CcApplication *self = CC_APPLICATION (application);
+ const gchar *help_accels[] = { "F1", NULL };
+
+ g_action_map_add_action_entries (G_ACTION_MAP (self),
+ cc_app_actions,
+ G_N_ELEMENTS (cc_app_actions),
+ self);
+
+ G_APPLICATION_CLASS (cc_application_parent_class)->startup (application);
+
+ hdy_init ();
+
+ gtk_application_set_accels_for_action (GTK_APPLICATION (application),
+ "app.help", help_accels);
+
+ self->model = cc_shell_model_new ();
+ self->window = cc_window_new (GTK_APPLICATION (application), self->model);
+}
+
+static void
+cc_application_finalize (GObject *object)
+{
+ /* Destroy the object storage cache when finalizing */
+ cc_object_storage_destroy ();
+
+ G_OBJECT_CLASS (cc_application_parent_class)->finalize (object);
+}
+
+static GObject *
+cc_application_constructor (GType type,
+ guint n_construct_params,
+ GObjectConstructParam *construct_params)
+{
+ static GObject *self = NULL;
+
+ if (self == NULL)
+ {
+ self = G_OBJECT_CLASS (cc_application_parent_class)->constructor (type,
+ n_construct_params,
+ construct_params);
+ g_object_add_weak_pointer (self, (gpointer) &self);
+ return self;
+ }
+
+ return g_object_ref (self);
+}
+
+static void
+cc_application_class_init (CcApplicationClass *klass)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (klass);
+ GApplicationClass *application_class = G_APPLICATION_CLASS (klass);
+
+ object_class->finalize = cc_application_finalize;
+ object_class->constructor = cc_application_constructor;
+ application_class->activate = cc_application_activate;
+ application_class->startup = cc_application_startup;
+ application_class->command_line = cc_application_command_line;
+ application_class->handle_local_options = cc_application_handle_local_options;
+}
+
+static void
+cc_application_init (CcApplication *self)
+{
+ g_autoptr(GtkCssProvider) provider = NULL;
+
+ cc_object_storage_initialize ();
+
+ g_application_add_main_option_entries (G_APPLICATION (self), all_options);
+
+ provider = gtk_css_provider_new ();
+ gtk_css_provider_load_from_resource (provider, "/org/gnome/ControlCenter/gtk/style.css");
+ gtk_style_context_add_provider_for_screen (gdk_screen_get_default (),
+ GTK_STYLE_PROVIDER (provider),
+ GTK_STYLE_PROVIDER_PRIORITY_APPLICATION);
+}
+
+GtkApplication *
+cc_application_new (void)
+{
+ return g_object_new (CC_TYPE_APPLICATION,
+ "application-id", "org.gnome.ControlCenter",
+ "flags", G_APPLICATION_HANDLES_COMMAND_LINE,
+ NULL);
+}
+
+CcShellModel *
+cc_application_get_model (CcApplication *self)
+{
+ g_return_val_if_fail (CC_IS_APPLICATION (self), NULL);
+
+ return self->model;
+}
diff --git a/shell/cc-application.h b/shell/cc-application.h
new file mode 100644
index 0000000..e9c8b60
--- /dev/null
+++ b/shell/cc-application.h
@@ -0,0 +1,36 @@
+/*
+ * Copyright © 2013 Red Hat, Inc.
+ *
+ * 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.
+ */
+
+#pragma once
+
+#include "cc-shell-model.h"
+
+#include <gtk/gtk.h>
+
+G_BEGIN_DECLS
+
+#define CC_TYPE_APPLICATION (cc_application_get_type())
+
+G_DECLARE_FINAL_TYPE (CcApplication, cc_application, CC, APPLICATION, GtkApplication)
+
+GtkApplication *cc_application_new (void);
+
+CcShellModel *cc_application_get_model (CcApplication *self);
+
+G_END_DECLS
diff --git a/shell/cc-debug.h.in b/shell/cc-debug.h.in
new file mode 100644
index 0000000..226f82e
--- /dev/null
+++ b/shell/cc-debug.h.in
@@ -0,0 +1,229 @@
+/* cc-debug.h.in
+ *
+ * Copyright © 2018 Georges Basile Stavracas Neto <georges.stavracas@gmail.com>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 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, see <http://www.gnu.org/licenses/>.
+ */
+
+#pragma once
+
+#include <glib.h>
+
+/**
+ * SECTION:cc-debug
+ * @short_description: Debugging macros
+ * @title:Debugging
+ * @stability:stable
+ *
+ * Macros used for tracing and debugging code. These
+ * are only valid when Settings is compiled with tracing
+ * suppoer (pass `--enable-tracing` to the configure
+ * script to do that).
+ */
+
+G_BEGIN_DECLS
+
+#ifndef CC_ENABLE_TRACE
+# define CC_ENABLE_TRACE @ENABLE_TRACING@
+#endif
+#if CC_ENABLE_TRACE != 1
+# undef CC_ENABLE_TRACE
+#endif
+
+/**
+ * CC_LOG_LEVEL_TRACE: (skip)
+ */
+#ifndef CC_LOG_LEVEL_TRACE
+# define CC_LOG_LEVEL_TRACE ((GLogLevelFlags)(1 << G_LOG_LEVEL_USER_SHIFT))
+#endif
+
+#ifdef CC_ENABLE_TRACE
+
+/**
+ * CC_TRACE_MSG:
+ * @fmt: printf-like format of the message
+ * @...: arguments for @fmt
+ *
+ * Prints a trace message.
+ */
+# define CC_TRACE_MSG(fmt, ...) \
+ g_log(G_LOG_DOMAIN, CC_LOG_LEVEL_TRACE, " MSG: %s():%d: " fmt, \
+ G_STRFUNC, __LINE__, ##__VA_ARGS__)
+
+/**
+ * CC_PROBE:
+ *
+ * Prints a probing message. Put this macro in the code when
+ * you want to check the program reaches a certain section
+ * of code.
+ */
+# define CC_PROBE \
+ g_log(G_LOG_DOMAIN, CC_LOG_LEVEL_TRACE, "PROBE: %s():%d", \
+ G_STRFUNC, __LINE__)
+
+/**
+ * CC_TODO:
+ * @_msg: the message to print
+ *
+ * Prints a TODO message.
+ */
+# define CC_TODO(_msg) \
+ g_log(G_LOG_DOMAIN, CC_LOG_LEVEL_TRACE, " TODO: %s():%d: %s", \
+ G_STRFUNC, __LINE__, _msg)
+
+/**
+ * CC_ENTRY:
+ *
+ * Prints an entry message. This shouldn't be used in
+ * critical functions. Place this at the beggining of
+ * the function, before any assertion.
+ */
+# define CC_ENTRY \
+ g_log(G_LOG_DOMAIN, CC_LOG_LEVEL_TRACE, "ENTRY: %s():%d", \
+ G_STRFUNC, __LINE__)
+
+/**
+ * CC_EXIT:
+ *
+ * Prints an exit message. This shouldn't be used in
+ * critical functions. Place this at the end of
+ * the function, after any relevant code. If the
+ * function returns something, use CC_RETURN()
+ * instead.
+ */
+# define CC_EXIT \
+ G_STMT_START { \
+ g_log(G_LOG_DOMAIN, CC_LOG_LEVEL_TRACE, " EXIT: %s():%d", \
+ G_STRFUNC, __LINE__); \
+ return; \
+ } G_STMT_END
+
+/**
+ * CC_GOTO:
+ * @_l: goto tag
+ *
+ * Logs a goto jump.
+ */
+# define CC_GOTO(_l) \
+ G_STMT_START { \
+ g_log(G_LOG_DOMAIN, CC_LOG_LEVEL_TRACE, " GOTO: %s():%d ("#_l")",\
+ G_STRFUNC, __LINE__); \
+ goto _l; \
+ } G_STMT_END
+
+/**
+ * CC_RETURN:
+ * @_r: the return value.
+ *
+ * Prints an exit message, and returns @_r. See #CC_EXIT.
+ */
+# define CC_RETURN(_r) \
+ G_STMT_START { \
+ g_log(G_LOG_DOMAIN, CC_LOG_LEVEL_TRACE, " EXIT: %s():%d ", \
+ G_STRFUNC, __LINE__); \
+ return _r; \
+ } G_STMT_END
+
+#else
+
+/**
+ * CC_TODO:
+ * @_msg: the message to print
+ *
+ * Prints a TODO message.
+ */
+# define CC_TODO(_msg)
+
+/**
+ * CC_PROBE:
+ *
+ * Prints a probing message.
+ */
+# define CC_PROBE
+
+/**
+ * CC_TRACE_MSG:
+ * @fmt: printf-like format of the message
+ * @...: arguments for @fmt
+ *
+ * Prints a trace message.
+ */
+# define CC_TRACE_MSG(fmt, ...)
+
+/**
+ * CC_ENTRY:
+ *
+ * Prints a probing message. This shouldn't be used in
+ * critical functions. Place this at the beggining of
+ * the function, before any assertion.
+ */
+# define CC_ENTRY
+
+/**
+ * CC_GOTO:
+ * @_l: goto tag
+ *
+ * Logs a goto jump.
+ */
+# define CC_GOTO(_l) goto _l
+
+/**
+ * CC_EXIT:
+ *
+ * Prints an exit message. This shouldn't be used in
+ * critical functions. Place this at the end of
+ * the function, after any relevant code. If the
+ * function returns somethin, use CC_RETURN()
+ * instead.
+ */
+# define CC_EXIT return
+
+/**
+ * CC_RETURN:
+ * @_r: the return value.
+ *
+ * Prints an exit message, and returns @_r. See #CC_EXIT.
+ */
+# define CC_RETURN(_r) return _r
+#endif
+
+/**
+ * _CC_BUG: (skip)
+ */
+#define _CC_BUG(Component, Description, File, Line, Func, ...) \
+ G_STMT_START { \
+ g_printerr ("-----------------------------------------------------------------\n"); \
+ g_printerr ("You've found a bug in Settings or one of its dependent libraries.\n"); \
+ g_printerr ("Please help us help you by filing a bug report at:\n"); \
+ g_printerr ("\n"); \
+ g_printerr ("@BUGREPORT_URL@&component=%s\n", Component); \
+ g_printerr ("\n"); \
+ g_printerr ("%s:%d in function %s()\n", File, Line, Func); \
+ g_printerr ("\n"); \
+ g_printerr (Description"\n", ##__VA_ARGS__); \
+ g_printerr ("-----------------------------------------------------------------\n"); \
+ } G_STMT_END
+
+/**
+ * CC_BUG:
+ * @Component: the component
+ * @Description: the description
+ * @...: extra arguments
+ *
+ * Logs a bug-friendly message.
+ */
+#define CC_BUG(Component, Description, ...) \
+ _CC_BUG(Component, Description, __FILE__, __LINE__, G_STRFUNC, ##__VA_ARGS__)
+
+G_END_DECLS
diff --git a/shell/cc-log.c b/shell/cc-log.c
new file mode 100644
index 0000000..dba4f5a
--- /dev/null
+++ b/shell/cc-log.c
@@ -0,0 +1,101 @@
+/* cc-shell-log.c
+ *
+ * Copyright © 2018 Georges Basile Stavracas Neto <georges.stavracas@gmail.com>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 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, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "cc-debug.h"
+#include "cc-log.h"
+
+#include <unistd.h>
+#include <glib.h>
+
+G_LOCK_DEFINE_STATIC (channel_lock);
+
+GIOChannel *standard_channel = NULL;
+
+static const gchar* ignored_domains[] =
+{
+ "GdkPixbuf",
+ NULL
+};
+
+static const gchar *
+log_level_str (GLogLevelFlags log_level)
+{
+ switch (((gulong)log_level & G_LOG_LEVEL_MASK))
+ {
+ case G_LOG_LEVEL_ERROR: return " \033[1;31mERROR\033[0m";
+ case G_LOG_LEVEL_CRITICAL: return "\033[1;35mCRITICAL\033[0m";
+ case G_LOG_LEVEL_WARNING: return " \033[1;33mWARNING\033[0m";
+ case G_LOG_LEVEL_MESSAGE: return " \033[1;34mMESSAGE\033[0m";
+ case G_LOG_LEVEL_INFO: return " \033[1;32mINFO\033[0m";
+ case G_LOG_LEVEL_DEBUG: return " \033[1;32mDEBUG\033[0m";
+ case CC_LOG_LEVEL_TRACE: return " \033[1;36mTRACE\033[0m";
+ default: return " UNKNOWN";
+ }
+}
+
+static void
+log_handler (const gchar *domain,
+ GLogLevelFlags log_level,
+ const gchar *message,
+ gpointer user_data)
+{
+ g_autoptr(GDateTime) now = NULL;
+ const gchar *level;
+ g_autofree gchar *ftime = NULL;
+ g_autofree gchar *buffer = NULL;
+
+ /* Skip ignored log domains */
+ if (domain && g_strv_contains (ignored_domains, domain))
+ return;
+
+ level = log_level_str (log_level);
+ now = g_date_time_new_now_local ();
+ ftime = g_date_time_format (now, "%H:%M:%S");
+ buffer = g_strdup_printf ("%s.%04d %24s: %s: %s\n",
+ ftime,
+ g_date_time_get_microsecond (now) / 1000,
+ domain,
+ level,
+ message);
+
+ /* Safely write to the channel */
+ G_LOCK (channel_lock);
+
+ g_io_channel_write_chars (standard_channel, buffer, -1, NULL, NULL);
+ g_io_channel_flush (standard_channel, NULL);
+
+ G_UNLOCK (channel_lock);
+}
+
+void
+cc_log_init (void)
+{
+ static gsize initialized = FALSE;
+
+ if (g_once_init_enter (&initialized))
+ {
+ standard_channel = g_io_channel_unix_new (STDOUT_FILENO);
+
+ g_setenv ("G_MESSAGES_DEBUG", "all", TRUE);
+
+ g_log_set_default_handler (log_handler, NULL);
+
+ g_once_init_leave (&initialized, TRUE);
+ }
+}
+
diff --git a/shell/cc-log.h b/shell/cc-log.h
new file mode 100644
index 0000000..2d04c0b
--- /dev/null
+++ b/shell/cc-log.h
@@ -0,0 +1,25 @@
+/* cc-log.h
+ *
+ * Copyright © 2018 Georges Basile Stavracas Neto <georges.stavracas@gmail.com>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 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, see <http://www.gnu.org/licenses/>.
+ */
+
+#pragma once
+
+G_BEGIN_DECLS
+
+void cc_log_init (void);
+
+G_END_DECLS
diff --git a/shell/cc-object-storage.c b/shell/cc-object-storage.c
new file mode 100644
index 0000000..f52ba59
--- /dev/null
+++ b/shell/cc-object-storage.c
@@ -0,0 +1,444 @@
+/* cc-object-storage.h
+ *
+ * Copyright 2018 Georges Basile Stavracas Neto <georges.stavracas@gmail.com>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 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, see <http://www.gnu.org/licenses/>.
+ */
+
+#define G_LOG_DOMAIN "cc-object-storage"
+
+#include "cc-object-storage.h"
+
+struct _CcObjectStorage
+{
+ GObject parent_instance;
+
+ GHashTable *id_to_object;
+};
+
+G_DEFINE_TYPE (CcObjectStorage, cc_object_storage, G_TYPE_OBJECT)
+
+/* Singleton instance */
+static CcObjectStorage *_instance = NULL;
+
+/* GTask API to create a new D-Bus proxy */
+typedef struct
+{
+ GBusType bus_type;
+ GDBusProxyFlags flags;
+ gchar *name;
+ gchar *path;
+ gchar *interface;
+ gboolean cached;
+} TaskData;
+
+static TaskData*
+task_data_new (GBusType bus_type,
+ GDBusProxyFlags flags,
+ const gchar *name,
+ const gchar *path,
+ const gchar *interface)
+{
+ TaskData *data = g_slice_new (TaskData);
+ data->bus_type = bus_type;
+ data->flags =flags;
+ data->name = g_strdup (name);
+ data->path = g_strdup (path);
+ data->interface = g_strdup (interface);
+ data->cached = FALSE;
+
+ return data;
+}
+
+static void
+task_data_free (TaskData *data)
+{
+ g_free (data->name);
+ g_free (data->path);
+ g_free (data->interface);
+ g_slice_free (TaskData, data);
+}
+
+static void
+create_dbus_proxy_in_thread_cb (GTask *task,
+ gpointer source_object,
+ gpointer task_data,
+ GCancellable *cancellable)
+{
+ g_autoptr(GDBusProxy) proxy = NULL;
+ g_autoptr(GError) local_error = NULL;
+ TaskData *data = task_data;
+
+ proxy = g_dbus_proxy_new_for_bus_sync (data->bus_type,
+ data->flags,
+ NULL,
+ data->name,
+ data->path,
+ data->interface,
+ cancellable,
+ &local_error);
+
+ if (local_error)
+ {
+ g_task_return_error (task, local_error);
+ return;
+ }
+
+ g_task_return_pointer (task, g_object_ref (g_steal_pointer (&proxy)), g_object_unref);
+}
+
+static void
+cc_object_storage_finalize (GObject *object)
+{
+ CcObjectStorage *self = (CcObjectStorage *)object;
+
+ g_debug ("Destroying cached objects");
+
+ g_clear_pointer (&self->id_to_object, g_hash_table_destroy);
+
+ G_OBJECT_CLASS (cc_object_storage_parent_class)->finalize (object);
+}
+
+static void
+cc_object_storage_class_init (CcObjectStorageClass *klass)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (klass);
+
+ object_class->finalize = cc_object_storage_finalize;
+}
+
+static void
+cc_object_storage_init (CcObjectStorage *self)
+{
+ self->id_to_object = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, g_object_unref);
+}
+
+/**
+ * cc_object_storage_has_object:
+ * @key: the unique string identifier of the object
+ *
+ * Checks whether there is an object associated with @key.
+ *
+ * Returns: %TRUE if the object is stored, %FALSE otherwise.
+ */
+gboolean
+cc_object_storage_has_object (const gchar *key)
+{
+ g_assert (CC_IS_OBJECT_STORAGE (_instance));
+ g_assert (key != NULL);
+
+ return g_hash_table_contains (_instance->id_to_object, key);
+}
+
+/**
+ * cc_object_storage_add_object:
+ * @key: the unique string identifier of the object
+ * @object: (type GObject): the object to be stored
+ *
+ * Adds @object to the object storage. It is a programming error to try to
+ * add an object that was already added.
+ *
+ * @object must be a GObject.
+ *
+ * Always check if the object is stored with cc_object_storage_has_object()
+ * before calling this function.
+ */
+void
+cc_object_storage_add_object (const gchar *key,
+ gpointer object)
+{
+ /* Trying to add an object that was already added is a hard error. Each
+ * object must be added once, and only once, over the entire lifetime
+ * of the application.
+ */
+ g_assert (CC_IS_OBJECT_STORAGE (_instance));
+ g_assert (key != NULL);
+ g_assert (G_IS_OBJECT (object));
+ g_assert (!g_hash_table_contains (_instance->id_to_object, key));
+
+ g_debug ("Adding object %s (%s → %p) to the storage",
+ g_type_name (G_OBJECT_TYPE (object)),
+ key,
+ object);
+
+ g_hash_table_insert (_instance->id_to_object, g_strdup (key), g_object_ref (object));
+}
+
+/**
+ * cc_object_storage_get_object:
+ * @key: the unique string identifier of the object
+ *
+ * Retrieves the object associated with @key. It is a programming error to
+ * try to retrieve an object before adding it.
+ *
+ * Always check if the object is stored with cc_object_storage_has_object()
+ * before calling this function.
+ *
+ * Returns: (transfer full): the GObject associated with @key.
+ */
+gpointer
+cc_object_storage_get_object (const gchar *key)
+{
+ /* Trying to peek an object that was not yet added is a hard error. Users
+ * of this API need to first check if the object is available with
+ * cc_object_storage_has_object().
+ */
+ g_assert (CC_IS_OBJECT_STORAGE (_instance));
+ g_assert (key != NULL);
+ g_assert (g_hash_table_contains (_instance->id_to_object, key));
+
+ return g_object_ref (g_hash_table_lookup (_instance->id_to_object, key));
+}
+
+/**
+ * cc_object_storage_create_dbus_proxy_sync:
+ * @name: the D-Bus name
+ * @flags: the D-Bus proxy flags
+ * @path: the D-Bus object path
+ * @interface: the D-Bus interface name
+ * @cancellable: (nullable): #GCancellable to cancel the operation
+ * @error: (nullable): return location for a #GError
+ *
+ * Synchronously create a #GDBusProxy with @name, @path and @interface,
+ * stores it in the cache, and returns the newly created proxy.
+ *
+ * If a proxy with that signature is already created, it will be used
+ * instead of creating a new one.
+ *
+ * Returns: (transfer full)(nullable): the new #GDBusProxy.
+ */
+gpointer
+cc_object_storage_create_dbus_proxy_sync (GBusType bus_type,
+ GDBusProxyFlags flags,
+ const gchar *name,
+ const gchar *path,
+ const gchar *interface,
+ GCancellable *cancellable,
+ GError **error)
+{
+ g_autoptr(GDBusProxy) proxy = NULL;
+ g_autoptr(GError) local_error = NULL;
+ g_autofree gchar *key = NULL;
+
+ g_assert (CC_IS_OBJECT_STORAGE (_instance));
+ g_assert (name && *name);
+ g_assert (path && *path);
+ g_assert (interface && *interface);
+ g_assert (!error || !*error);
+
+ key = g_strdup_printf ("CcObjectStorage::dbus-proxy(%s,%s,%s)", name, path, interface);
+
+ g_debug ("Creating D-Bus proxy for %s", key);
+
+ /* Check if a DBus proxy with that signature is already available; if it is,
+ * return that instead of a new one.
+ */
+ if (g_hash_table_contains (_instance->id_to_object, key))
+ return cc_object_storage_get_object (key);
+
+ proxy = g_dbus_proxy_new_for_bus_sync (bus_type,
+ flags,
+ NULL,
+ name,
+ path,
+ interface,
+ cancellable,
+ &local_error);
+
+ if (local_error)
+ {
+ g_propagate_error (error, g_steal_pointer (&local_error));
+ return NULL;
+ }
+
+ /* Store the newly created D-Bus proxy */
+ cc_object_storage_add_object (key, proxy);
+
+ return g_steal_pointer (&proxy);
+}
+
+
+/**
+ * cc_object_storage_create_dbus_proxy:
+ * @name: the D-Bus name
+ * @flags: the D-Bus proxy flags
+ * @path: the D-Bus object path
+ * @interface: the D-Bus interface name
+ * @cancellable: (nullable): #GCancellable to cancel the operation
+ * @callback: callback for when the async operation is finished
+ * @user_data: user data for @callback
+ *
+ * Asynchronously create a #GDBusProxy with @name, @path and @interface.
+ *
+ * If a proxy with that signature is already created, it will be used instead of
+ * creating a new one.
+ *
+ * It is a programming error to create the an identical proxy while asynchronously
+ * creating one. Not cancelling this operation will result in an assertion failure
+ * when calling cc_object_storage_create_dbus_proxy_finish().
+ */
+void
+cc_object_storage_create_dbus_proxy (GBusType bus_type,
+ GDBusProxyFlags flags,
+ const gchar *name,
+ const gchar *path,
+ const gchar *interface,
+ GCancellable *cancellable,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ g_autoptr(GTask) task = NULL;
+ g_autofree gchar *key = NULL;
+ TaskData *data = NULL;
+
+ g_assert (CC_IS_OBJECT_STORAGE (_instance));
+ g_assert (name && *name);
+ g_assert (path && *path);
+ g_assert (interface && *interface);
+ g_assert (!cancellable || G_IS_CANCELLABLE (cancellable));
+
+ data = task_data_new (bus_type, flags, name, path, interface);
+
+ task = g_task_new (_instance, cancellable, callback, user_data);
+ g_task_set_source_tag (task, cc_object_storage_create_dbus_proxy);
+ g_task_set_task_data (task, data, (GDestroyNotify) task_data_free);
+
+ /* Check if the D-Bus proxy is already created */
+ key = g_strdup_printf ("CcObjectStorage::dbus-proxy(%s,%s,%s)", name, path, interface);
+
+ g_debug ("Asynchronously creating D-Bus proxy for %s", key);
+
+ if (g_hash_table_contains (_instance->id_to_object, key))
+ {
+ /* Mark this GTask as already cached, so we can call the right assertions
+ * on the callback
+ * */
+ data->cached = TRUE;
+
+ g_debug ("Found in cache the D-Bus proxy %s", key);
+
+ g_task_return_pointer (task, cc_object_storage_get_object (key), g_object_unref);
+ return;
+ }
+
+ g_task_run_in_thread (task, create_dbus_proxy_in_thread_cb);
+}
+
+/**
+ * cc_object_storage_create_dbus_proxy_finish:
+ * @result:
+ * @error: (nullable): return location for a #GError
+ *
+ * Finishes a D-Bus proxy creation started by cc_object_storage_create_dbus_proxy().
+ *
+ * Synchronously create a #GDBusProxy with @name, @path and @interface,
+ * stores it in the cache, and returns the newly created proxy.
+ *
+ * If a proxy with that signature is already created, it will be used
+ * instead of creating a new one.
+ *
+ * Returns: (transfer full)(nullable): the new #GDBusProxy.
+ */
+gpointer
+cc_object_storage_create_dbus_proxy_finish (GAsyncResult *result,
+ GError **error)
+{
+ g_autoptr(GDBusProxy) proxy = NULL;
+ g_autoptr(GError) local_error = NULL;
+ g_autofree gchar *key = NULL;
+ TaskData *task_data;
+ GTask *task;
+
+ task = G_TASK (result);
+
+ g_assert (task && G_TASK (result));
+ g_assert (!error || !*error);
+
+ task_data = g_task_get_task_data (task);
+ g_assert (task_data != NULL);
+
+ key = g_strdup_printf ("CcObjectStorage::dbus-proxy(%s,%s,%s)",
+ task_data->name,
+ task_data->path,
+ task_data->interface);
+
+ g_debug ("Finished creating D-Bus proxy for %s", key);
+
+ /* Retrieve the newly created proxy */
+ proxy = g_task_propagate_pointer (task, &local_error);
+
+ /* If the proxy is not cached, do the normal caching routine */
+ if (local_error)
+ {
+ g_propagate_error (error, g_steal_pointer (&local_error));
+ return NULL;
+ }
+
+ /* Either we have the object stored right when trying to create it - in which case,
+ * task_data->cached == TRUE and cc_object_storage_has_object (key) == TRUE - or we
+ * didn't have a cached proxy before, and we shouldn't have it now.
+ *
+ * This is to force consumers of this code to *never* try to create the same D-Bus
+ * proxy asynchronously multiple times. Trying to do so is considered a programming
+ * error.
+ */
+ g_assert (task_data->cached == cc_object_storage_has_object (key));
+
+ /* If the proxy is already cached, destroy the newly created and used the cached proxy
+ * instead.
+ */
+ if (cc_object_storage_has_object (key))
+ return cc_object_storage_get_object (key);
+
+ /* Store the newly created D-Bus proxy */
+ cc_object_storage_add_object (key, proxy);
+
+ return g_steal_pointer (&proxy);
+}
+
+/**
+ * cc_object_storage_initialize:
+ *
+ * Initializes the single CcObjectStorage. This must be called only once,
+ * and before every other method of this object.
+ */
+void
+cc_object_storage_initialize (void)
+{
+ g_assert (_instance == NULL);
+
+ if (g_once_init_enter (&_instance))
+ {
+ CcObjectStorage *instance = g_object_new (CC_TYPE_OBJECT_STORAGE, NULL);
+
+ g_debug ("Initializing object storage");
+
+ g_once_init_leave (&_instance, instance);
+ }
+}
+
+/**
+ * cc_object_storage_destroy:
+ *
+ * Destroys the instance of #CcObjectStorage. This must be called only
+ * once during the application lifetime. It is a programming error to
+ * call this function multiple times
+ */
+void
+cc_object_storage_destroy (void)
+{
+ g_assert (_instance != NULL);
+
+ g_clear_object (&_instance);
+}
diff --git a/shell/cc-object-storage.h b/shell/cc-object-storage.h
new file mode 100644
index 0000000..efea0e2
--- /dev/null
+++ b/shell/cc-object-storage.h
@@ -0,0 +1,65 @@
+/* cc-object-storage.h
+ *
+ * Copyright 2018 Georges Basile Stavracas Neto <georges.stavracas@gmail.com>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 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, see <http://www.gnu.org/licenses/>.
+ */
+
+#pragma once
+
+#include <glib-object.h>
+#include <gio/gio.h>
+
+G_BEGIN_DECLS
+
+/* Default storage keys */
+#define CC_OBJECT_NMCLIENT "CcObjectStorage::nm-client"
+
+
+#define CC_TYPE_OBJECT_STORAGE (cc_object_storage_get_type())
+
+G_DECLARE_FINAL_TYPE (CcObjectStorage, cc_object_storage, CC, OBJECT_STORAGE, GObject)
+
+gboolean cc_object_storage_has_object (const gchar *key);
+
+void cc_object_storage_add_object (const gchar *key,
+ gpointer object);
+
+gpointer cc_object_storage_get_object (const gchar *key);
+
+gpointer cc_object_storage_create_dbus_proxy_sync (GBusType bus_type,
+ GDBusProxyFlags flags,
+ const gchar *name,
+ const gchar *path,
+ const gchar *interface,
+ GCancellable *cancellable,
+ GError **error);
+
+void cc_object_storage_create_dbus_proxy (GBusType bus_type,
+ GDBusProxyFlags flags,
+ const gchar *name,
+ const gchar *path,
+ const gchar *interface,
+ GCancellable *cancellable,
+ GAsyncReadyCallback callback,
+ gpointer user_data);
+
+gpointer cc_object_storage_create_dbus_proxy_finish (GAsyncResult *result,
+ GError **error);
+
+void cc_object_storage_initialize (void);
+
+void cc_object_storage_destroy (void);
+
+G_END_DECLS
diff --git a/shell/cc-panel-list.c b/shell/cc-panel-list.c
new file mode 100644
index 0000000..d425067
--- /dev/null
+++ b/shell/cc-panel-list.c
@@ -0,0 +1,1112 @@
+/* cc-panel-list.c
+ *
+ * Copyright (C) 2016 Endless, Inc
+ *
+ * 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, see <http://www.gnu.org/licenses/>.
+ *
+ * Author: Georges Basile Stavracas Neto <gbsneto@gnome.org>
+ */
+
+#define G_LOG_DOMAIN "cc-panel-list"
+
+#include <string.h>
+
+#include "cc-debug.h"
+#include "cc-panel-list.h"
+#include "cc-util.h"
+
+typedef struct
+{
+ GtkWidget *row;
+ GtkWidget *description_label;
+ CcPanelCategory category;
+ gchar *id;
+ gchar *name;
+ gchar *description;
+ gchar **keywords;
+ CcPanelVisibility visibility;
+} RowData;
+
+struct _CcPanelList
+{
+ GtkStack parent;
+
+ GtkWidget *privacy_listbox;
+ GtkWidget *main_listbox;
+ GtkWidget *search_listbox;
+
+ /* When clicking on Details or Devices row, show it
+ * automatically select the first panel of the list.
+ */
+ gboolean autoselect_panel : 1;
+
+ GtkListBoxRow *privacy_row;
+
+ gchar *current_panel_id;
+ gchar *search_query;
+
+ CcPanelListView previous_view;
+ CcPanelListView view;
+ GHashTable *id_to_data;
+ GHashTable *id_to_search_data;
+};
+
+G_DEFINE_TYPE (CcPanelList, cc_panel_list, GTK_TYPE_STACK)
+
+enum
+{
+ PROP_0,
+ PROP_SEARCH_MODE,
+ PROP_SEARCH_QUERY,
+ PROP_VIEW,
+ N_PROPS
+};
+
+enum
+{
+ SHOW_PANEL,
+ LAST_SIGNAL
+};
+
+static GParamSpec *properties [N_PROPS] = { NULL, };
+static gint signals [LAST_SIGNAL] = { 0, };
+
+/*
+ * Auxiliary methods
+ */
+static GtkWidget*
+get_widget_from_view (CcPanelList *self,
+ CcPanelListView view)
+{
+ switch (view)
+ {
+ case CC_PANEL_LIST_MAIN:
+ return self->main_listbox;
+
+ case CC_PANEL_LIST_PRIVACY:
+ return self->privacy_listbox;
+
+ case CC_PANEL_LIST_SEARCH:
+ return self->search_listbox;
+
+ case CC_PANEL_LIST_WIDGET:
+ return gtk_stack_get_child_by_name (GTK_STACK (self), "custom-widget");
+
+ default:
+ return NULL;
+ }
+}
+
+static GtkWidget *
+get_listbox_from_category (CcPanelList *self,
+ CcPanelCategory category)
+{
+
+ switch (category)
+ {
+ case CC_CATEGORY_PRIVACY:
+ return self->privacy_listbox;
+ break;
+
+ default:
+ return self->main_listbox;
+ break;
+ }
+
+ return NULL;
+}
+
+static void
+activate_row_below (CcPanelList *self,
+ RowData *data)
+{
+ GtkListBoxRow *next_row;
+ GtkListBox *listbox;
+ guint row_index;
+
+ row_index = gtk_list_box_row_get_index (GTK_LIST_BOX_ROW (data->row));
+ listbox = GTK_LIST_BOX (get_listbox_from_category (self, data->category));
+ next_row = gtk_list_box_get_row_at_index (listbox, row_index + 1);
+
+ /* Try the previous one if the current is invalid */
+ if (!next_row)
+ next_row = gtk_list_box_get_row_at_index (listbox, row_index - 1);
+
+ if (next_row)
+ g_signal_emit_by_name (next_row, "activate");
+}
+
+static CcPanelListView
+get_view_from_listbox (CcPanelList *self,
+ GtkWidget *listbox)
+{
+ if (listbox == self->main_listbox)
+ return CC_PANEL_LIST_MAIN;
+
+ if (listbox == self->privacy_listbox)
+ return CC_PANEL_LIST_PRIVACY;
+
+ return CC_PANEL_LIST_SEARCH;
+}
+
+static void
+switch_to_view (CcPanelList *self,
+ CcPanelListView view)
+{
+ GtkWidget *visible_child;
+ gboolean should_crossfade;
+
+ CC_ENTRY;
+
+ if (self->view == view)
+ CC_RETURN ();
+
+ CC_TRACE_MSG ("Switching to view: %d", view);
+
+ self->previous_view = self->view;
+ self->view = view;
+
+ /*
+ * When changing to or from the search view, the animation should
+ * be crossfade. Otherwise, it's the previous-forward movement.
+ */
+ should_crossfade = view == CC_PANEL_LIST_SEARCH ||
+ self->previous_view == CC_PANEL_LIST_SEARCH;
+
+ gtk_stack_set_transition_type (GTK_STACK (self),
+ should_crossfade ? GTK_STACK_TRANSITION_TYPE_CROSSFADE :
+ GTK_STACK_TRANSITION_TYPE_SLIDE_LEFT_RIGHT);
+
+ visible_child = get_widget_from_view (self, view);
+
+ gtk_stack_set_visible_child (GTK_STACK (self), visible_child);
+
+ /* For non-search views, make sure the displayed panel matches the
+ * newly selected row
+ */
+ if (self->autoselect_panel &&
+ view != CC_PANEL_LIST_SEARCH &&
+ self->previous_view != CC_PANEL_LIST_WIDGET)
+ {
+ cc_panel_list_activate (self);
+ }
+
+ g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_VIEW]);
+ g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_SEARCH_MODE]);
+
+ CC_EXIT;
+}
+
+static void
+update_search (CcPanelList *self)
+{
+ /*
+ * Only change to the search view is there's a
+ * search query available.
+ */
+ if (self->search_query &&
+ g_utf8_strlen (self->search_query, -1) > 0)
+ {
+ if (self->view == CC_PANEL_LIST_MAIN)
+ switch_to_view (self, CC_PANEL_LIST_SEARCH);
+ }
+ else
+ {
+ if (self->view == CC_PANEL_LIST_SEARCH)
+ switch_to_view (self, self->previous_view);
+ }
+
+ gtk_list_box_invalidate_filter (GTK_LIST_BOX (self->search_listbox));
+ gtk_list_box_unselect_all (GTK_LIST_BOX (self->search_listbox));
+}
+
+static const gchar*
+get_panel_id_from_row (CcPanelList *self,
+ GtkListBoxRow *row)
+{
+
+ RowData *row_data;
+
+ if (row == self->privacy_row)
+ return "privacy";
+
+ row_data = g_object_get_data (G_OBJECT (row), "data");
+
+ g_assert (row_data != NULL);
+ return row_data->id;
+}
+
+/*
+ * RowData functions
+ */
+static void
+row_data_free (RowData *data)
+{
+ g_strfreev (data->keywords);
+ g_free (data->description);
+ g_free (data->name);
+ g_free (data->id);
+ g_free (data);
+}
+
+static RowData*
+row_data_new (CcPanelCategory category,
+ const gchar *id,
+ const gchar *name,
+ const gchar *description,
+ const GStrv keywords,
+ const gchar *icon,
+ CcPanelVisibility visibility,
+ gboolean has_sidebar)
+{
+ GtkWidget *label, *grid, *image;
+ RowData *data;
+
+ data = g_new0 (RowData, 1);
+ data->category = category;
+ data->row = gtk_list_box_row_new ();
+ data->id = g_strdup (id);
+ data->name = g_strdup (name);
+ data->description = g_strdup (description);
+ data->keywords = g_strdupv (keywords);
+
+ /* Setup the row */
+ grid = g_object_new (GTK_TYPE_GRID,
+ "visible", TRUE,
+ "hexpand", TRUE,
+ "border-width", 12,
+ "column-spacing", 12,
+ NULL);
+
+ /* Icon */
+ image = gtk_image_new_from_icon_name (icon, GTK_ICON_SIZE_BUTTON);
+ gtk_style_context_add_class (gtk_widget_get_style_context (image), "sidebar-icon");
+
+ gtk_grid_attach (GTK_GRID (grid), image, 0, 0, 1, 1);
+
+ gtk_widget_show (image);
+
+ /* Name label */
+ label = g_object_new (GTK_TYPE_LABEL,
+ "label", name,
+ "visible", TRUE,
+ "xalign", 0.0,
+ "hexpand", TRUE,
+ NULL);
+ gtk_grid_attach (GTK_GRID (grid), label, 1, 0, 1, 1);
+
+ /* Description label */
+ label = g_object_new (GTK_TYPE_LABEL,
+ "label", description,
+ "visible", FALSE,
+ "xalign", 0.0,
+ "hexpand", TRUE,
+ NULL);
+ gtk_label_set_max_width_chars (GTK_LABEL (label), 25);
+ gtk_label_set_line_wrap (GTK_LABEL (label), TRUE);
+
+ if (has_sidebar)
+ {
+ image = gtk_image_new_from_icon_name ("go-next-symbolic", GTK_ICON_SIZE_BUTTON);
+ gtk_style_context_add_class (gtk_widget_get_style_context (image), "sidebar-icon");
+ gtk_grid_attach (GTK_GRID (grid), image, 2, 0, 1, 1);
+ gtk_widget_show (image);
+ }
+
+ gtk_style_context_add_class (gtk_widget_get_style_context (label), "dim-label");
+ gtk_grid_attach (GTK_GRID (grid), label, 1, 1, 1, 1);
+
+ data->description_label = label;
+
+ gtk_container_add (GTK_CONTAINER (data->row), grid);
+ gtk_widget_show (data->row);
+
+ g_object_set_data_full (G_OBJECT (data->row), "data", data, (GDestroyNotify) row_data_free);
+
+ data->visibility = visibility;
+
+ return data;
+}
+
+/*
+ * GtkListBox functions
+ */
+static gboolean
+filter_func (GtkListBoxRow *row,
+ gpointer user_data)
+{
+ CcPanelList *self;
+ RowData *data;
+ g_autofree gchar *search_text = NULL;
+ g_autofree gchar *panel_text = NULL;
+ g_autofree gchar *panel_description = NULL;
+ gboolean retval = FALSE;
+ gint i;
+
+ self = CC_PANEL_LIST (user_data);
+ data = g_object_get_data (G_OBJECT (row), "data");
+
+ if (!self->search_query)
+ return TRUE;
+
+ panel_text = cc_util_normalize_casefold_and_unaccent (data->name);
+ search_text = cc_util_normalize_casefold_and_unaccent (self->search_query);
+ panel_description = cc_util_normalize_casefold_and_unaccent (data->description);
+
+ g_strstrip (panel_text);
+ g_strstrip (search_text);
+ g_strstrip (panel_description);
+
+ /*
+ * The description label is only visible when the search is
+ * happening.
+ */
+ gtk_widget_set_visible (data->description_label, self->view == CC_PANEL_LIST_SEARCH);
+
+ for (i = 0; !retval && data->keywords[i] != NULL; i++)
+ retval = (strstr (data->keywords[i], search_text) == data->keywords[i]);
+
+ retval = retval || g_strstr_len (panel_text, -1, search_text) != NULL ||
+ g_strstr_len (panel_description, -1, search_text) != NULL;
+
+ return retval;
+}
+
+static const gchar * const panel_order[] = {
+ /* Main page */
+ "wifi",
+ "network",
+ "mobile-broadband",
+ "bluetooth",
+ "background",
+ "notifications",
+ "search",
+ "applications",
+ "privacy",
+ "online-accounts",
+ "sharing",
+
+ /* Privacy page */
+ "location",
+ "camera",
+ "microphone",
+ "thunderbolt",
+ "usage",
+ "lock",
+ "diagnostics",
+
+ /* Devices page */
+ "sound",
+ "power",
+ "display",
+ "mouse",
+ "keyboard",
+ "printers",
+ "removable-media",
+ "wacom",
+ "color",
+
+ /* Details page */
+ "region",
+ "universal-access",
+ "user-accounts",
+ "default-apps",
+ "reset-settings",
+ "datetime",
+ "info-overview",
+};
+
+static guint
+get_panel_id_index (const gchar *panel_id)
+{
+ guint i;
+
+ for (i = 0; i < G_N_ELEMENTS (panel_order); i++)
+ {
+ if (g_str_equal (panel_order[i], panel_id))
+ return i;
+ }
+
+ return 0;
+}
+
+static gint
+sort_function (GtkListBoxRow *a,
+ GtkListBoxRow *b,
+ gpointer user_data)
+{
+ CcPanelList *self = CC_PANEL_LIST (user_data);
+ const gchar *a_id, *b_id;
+
+ a_id = get_panel_id_from_row (self, a);
+ b_id = get_panel_id_from_row (self, b);
+
+ return get_panel_id_index (a_id) - get_panel_id_index (b_id);
+}
+
+static gint
+search_sort_function (GtkListBoxRow *a,
+ GtkListBoxRow *b,
+ gpointer user_data)
+{
+ CcPanelList *self;
+ RowData *a_data, *b_data;
+ g_autofree gchar *a_name = NULL;
+ g_autofree gchar *b_name = NULL;
+ g_autofree gchar *search = NULL;
+ gchar *a_strstr, *b_strstr;
+ gint a_distance, b_distance;
+
+ self = CC_PANEL_LIST (user_data);
+ search = NULL;
+ a_data = g_object_get_data (G_OBJECT (a), "data");
+ b_data = g_object_get_data (G_OBJECT (b), "data");
+
+ a_distance = b_distance = G_MAXINT;
+
+ a_name = cc_util_normalize_casefold_and_unaccent (a_data->name);
+ b_name = cc_util_normalize_casefold_and_unaccent (b_data->name);
+ g_strstrip (a_name);
+ g_strstrip (b_name);
+
+ if (self->search_query)
+ {
+ search = cc_util_normalize_casefold_and_unaccent (self->search_query);
+ g_strstrip (search);
+ }
+
+ /* Default result for empty search */
+ if (!search || g_utf8_strlen (search, -1) == 0)
+ return g_strcmp0 (a_name, b_name);
+
+ a_strstr = g_strstr_len (a_name, -1, search);
+ b_strstr = g_strstr_len (b_name, -1, search);
+
+ if (a_strstr)
+ a_distance = g_strstr_len (a_name, -1, search) - a_name;
+
+ if (b_strstr)
+ b_distance = g_strstr_len (b_name, -1, search) - b_name;
+
+ return a_distance - b_distance;
+}
+
+static void
+header_func (GtkListBoxRow *row,
+ GtkListBoxRow *before,
+ gpointer user_data)
+{
+ CcPanelList *self = CC_PANEL_LIST (user_data);
+ RowData *row_data, *before_data;
+
+ if (!before)
+ return;
+
+ if (row == self->privacy_row || before == self->privacy_row)
+ return;
+
+ /*
+ * We can only retrieve the data after assuring that none
+ * of the rows are the Privacy row.
+ */
+ row_data = g_object_get_data (G_OBJECT (row), "data");
+ before_data = g_object_get_data (G_OBJECT (before), "data");
+
+ if (row_data->category != before_data->category)
+ {
+ GtkWidget *separator;
+
+ separator = gtk_separator_new (GTK_ORIENTATION_HORIZONTAL);
+ gtk_widget_set_hexpand (separator, TRUE);
+ gtk_widget_show (separator);
+
+ gtk_list_box_row_set_header (row, separator);
+ }
+ else
+ {
+ gtk_list_box_row_set_header (row, NULL);
+ }
+}
+
+/*
+ * Callbacks
+ */
+static void
+row_activated_cb (GtkWidget *listbox,
+ GtkListBoxRow *row,
+ CcPanelList *self)
+{
+ RowData *data;
+
+ if (row == self->privacy_row)
+ {
+ switch_to_view (self, CC_PANEL_LIST_PRIVACY);
+ goto out;
+ }
+
+ /*
+ * When a panel is selected, the previous one should be
+ * unselected, except when it's search.
+ */
+ if (listbox != self->search_listbox)
+ {
+ if (listbox != self->main_listbox)
+ gtk_list_box_unselect_all (GTK_LIST_BOX (self->main_listbox));
+
+ if (listbox != self->privacy_listbox)
+ gtk_list_box_unselect_all (GTK_LIST_BOX (self->privacy_listbox));
+ }
+
+ /*
+ * Since we're not sure that the activated row is in the
+ * current view, set the view here.
+ */
+ switch_to_view (self, get_view_from_listbox (self, listbox));
+
+ data = g_object_get_data (G_OBJECT (row), "data");
+
+ /* If the activated row is relative to the current panel, and it has
+ * a custom widget, show the custom widget again.
+ */
+ if (g_strcmp0 (data->id, self->current_panel_id) == 0 &&
+ self->previous_view != CC_PANEL_LIST_SEARCH &&
+ gtk_stack_get_child_by_name (GTK_STACK (self), "custom-widget") != NULL)
+ {
+ CC_TRACE_MSG ("Switching to panel widget");
+
+ switch_to_view (self, CC_PANEL_LIST_WIDGET);
+ }
+
+ g_signal_emit (self, signals[SHOW_PANEL], 0, data->id);
+
+out:
+ /* After selecting the panel and eventually changing the view, reset the
+ * autoselect flag. If necessary, cc_panel_list_set_active_panel() will
+ * set it to FALSE again.
+ */
+ self->autoselect_panel = TRUE;
+}
+
+static void
+search_row_activated_cb (GtkWidget *listbox,
+ GtkListBoxRow *row,
+ CcPanelList *self)
+{
+ GtkWidget *real_listbox;
+ RowData *data;
+ GList *children, *l;
+
+ CC_ENTRY;
+
+ data = g_object_get_data (G_OBJECT (row), "data");
+
+ if (data->category == CC_CATEGORY_PRIVACY)
+ real_listbox = self->privacy_listbox;
+ else
+ real_listbox = self->main_listbox;
+
+ /* Select the correct row */
+ children = gtk_container_get_children (GTK_CONTAINER (real_listbox));
+
+ for (l = children; l != NULL; l = l->next)
+ {
+ RowData *real_row_data;
+
+ real_row_data = g_object_get_data (l->data, "data");
+
+ /*
+ * The main listbox has the Details & Devices rows, and neither
+ * of them contains "data", so we have to ensure we have valid
+ * data before going on.
+ */
+ if (!real_row_data)
+ continue;
+
+ if (g_strcmp0 (real_row_data->id, data->id) == 0)
+ {
+ GtkListBoxRow *real_row;
+
+ real_row = GTK_LIST_BOX_ROW (real_row_data->row);
+
+ gtk_list_box_select_row (GTK_LIST_BOX (real_listbox), real_row);
+ gtk_widget_grab_focus (GTK_WIDGET (real_row));
+
+ g_signal_emit_by_name (real_row, "activate");
+ break;
+ }
+ }
+
+ g_list_free (children);
+
+ CC_EXIT;
+}
+
+static void
+cc_panel_list_finalize (GObject *object)
+{
+ CcPanelList *self = (CcPanelList *)object;
+
+ g_clear_pointer (&self->search_query, g_free);
+ g_clear_pointer (&self->current_panel_id, g_free);
+ g_clear_pointer (&self->id_to_data, g_hash_table_destroy);
+ g_clear_pointer (&self->id_to_search_data, g_hash_table_destroy);
+
+ G_OBJECT_CLASS (cc_panel_list_parent_class)->finalize (object);
+}
+
+static void
+cc_panel_list_get_property (GObject *object,
+ guint prop_id,
+ GValue *value,
+ GParamSpec *pspec)
+{
+ CcPanelList *self = CC_PANEL_LIST (object);
+
+ switch (prop_id)
+ {
+ case PROP_SEARCH_MODE:
+ g_value_set_boolean (value, self->view == CC_PANEL_LIST_SEARCH);
+ break;
+
+ case PROP_SEARCH_QUERY:
+ g_value_set_string (value, self->search_query);
+ break;
+
+ case PROP_VIEW:
+ g_value_set_int (value, self->view);
+ break;
+
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+ }
+}
+
+static void
+cc_panel_list_set_property (GObject *object,
+ guint prop_id,
+ const GValue *value,
+ GParamSpec *pspec)
+{
+ CcPanelList *self = CC_PANEL_LIST (object);
+
+ switch (prop_id)
+ {
+ case PROP_SEARCH_MODE:
+ update_search (self);
+ break;
+
+ case PROP_SEARCH_QUERY:
+ cc_panel_list_set_search_query (self, g_value_get_string (value));
+ break;
+
+ case PROP_VIEW:
+ switch_to_view (self, g_value_get_int (value));
+ break;
+
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+ }
+}
+
+static void
+cc_panel_list_class_init (CcPanelListClass *klass)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (klass);
+ GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);
+
+ object_class->finalize = cc_panel_list_finalize;
+ object_class->get_property = cc_panel_list_get_property;
+ object_class->set_property = cc_panel_list_set_property;
+
+ /**
+ * CcPanelList:show-panel:
+ *
+ * Emitted when a panel is selected.
+ */
+ signals[SHOW_PANEL] = g_signal_new ("show-panel",
+ CC_TYPE_PANEL_LIST,
+ G_SIGNAL_RUN_LAST,
+ 0, NULL, NULL, NULL,
+ G_TYPE_NONE,
+ 1,
+ G_TYPE_STRING);
+
+ /**
+ * CcPanelList:search-mode:
+ *
+ * Whether the search is visible or not.
+ */
+ properties[PROP_SEARCH_MODE] = g_param_spec_boolean ("search-mode",
+ "Search mode",
+ "Whether it's in search mode or not",
+ FALSE,
+ G_PARAM_READWRITE);
+
+ /**
+ * CcPanelList:search-query:
+ *
+ * The search that is being applied to sidelist.
+ */
+ properties[PROP_SEARCH_QUERY] = g_param_spec_string ("search-query",
+ "Search query",
+ "The current search query",
+ NULL,
+ G_PARAM_READWRITE);
+
+ /**
+ * CcPanelList:view:
+ *
+ * The current view of the sidelist.
+ */
+ properties[PROP_VIEW] = g_param_spec_int ("view",
+ "View",
+ "The current view of the sidelist",
+ CC_PANEL_LIST_MAIN,
+ CC_PANEL_LIST_SEARCH,
+ CC_PANEL_LIST_MAIN,
+ G_PARAM_READWRITE);
+
+ g_object_class_install_properties (object_class, N_PROPS, properties);
+
+ gtk_widget_class_set_template_from_resource (widget_class, "/org/gnome/ControlCenter/gtk/cc-panel-list.ui");
+
+ gtk_widget_class_bind_template_child (widget_class, CcPanelList, privacy_listbox);
+ gtk_widget_class_bind_template_child (widget_class, CcPanelList, privacy_row);
+ gtk_widget_class_bind_template_child (widget_class, CcPanelList, main_listbox);
+ gtk_widget_class_bind_template_child (widget_class, CcPanelList, search_listbox);
+
+ gtk_widget_class_bind_template_callback (widget_class, row_activated_cb);
+ gtk_widget_class_bind_template_callback (widget_class, search_row_activated_cb);
+}
+
+static void
+cc_panel_list_init (CcPanelList *self)
+{
+ gtk_widget_init_template (GTK_WIDGET (self));
+
+ self->id_to_data = g_hash_table_new (g_str_hash, g_str_equal);
+ self->id_to_search_data = g_hash_table_new (g_str_hash, g_str_equal);
+ self->view = CC_PANEL_LIST_MAIN;
+
+ gtk_list_box_set_sort_func (GTK_LIST_BOX (self->main_listbox),
+ sort_function,
+ self,
+ NULL);
+
+ gtk_list_box_set_sort_func (GTK_LIST_BOX (self->privacy_listbox),
+ sort_function,
+ self,
+ NULL);
+
+ gtk_list_box_set_header_func (GTK_LIST_BOX (self->main_listbox),
+ header_func,
+ self,
+ NULL);
+
+ /* Search listbox */
+ gtk_list_box_set_sort_func (GTK_LIST_BOX (self->search_listbox),
+ search_sort_function,
+ self,
+ NULL);
+
+ gtk_list_box_set_filter_func (GTK_LIST_BOX (self->search_listbox),
+ filter_func,
+ self,
+ NULL);
+}
+
+GtkWidget*
+cc_panel_list_new (void)
+{
+ return g_object_new (CC_TYPE_PANEL_LIST, NULL);
+}
+
+gboolean
+cc_panel_list_activate (CcPanelList *self)
+{
+ GtkListBoxRow *row;
+ GtkWidget *listbox;
+ guint i = 0;
+
+ CC_ENTRY;
+
+ g_return_val_if_fail (CC_IS_PANEL_LIST (self), FALSE);
+
+ listbox = get_widget_from_view (self, self->view);
+ if (!GTK_IS_LIST_BOX (listbox))
+ CC_RETURN (FALSE);
+
+ /* Select the first visible row */
+ do
+ row = gtk_list_box_get_row_at_index (GTK_LIST_BOX (listbox), i++);
+ while (row && !gtk_widget_get_visible (GTK_WIDGET (row)));
+
+ /* If the row is valid, activate it */
+ if (row)
+ {
+ gtk_list_box_select_row (GTK_LIST_BOX (listbox), row);
+ gtk_widget_grab_focus (GTK_WIDGET (row));
+
+ g_signal_emit_by_name (row, "activate");
+ }
+
+ CC_RETURN (row != NULL);
+}
+
+const gchar*
+cc_panel_list_get_search_query (CcPanelList *self)
+{
+ g_return_val_if_fail (CC_IS_PANEL_LIST (self), NULL);
+
+ return self->search_query;
+}
+
+void
+cc_panel_list_set_search_query (CcPanelList *self,
+ const gchar *search)
+{
+ g_return_if_fail (CC_IS_PANEL_LIST (self));
+
+ if (g_strcmp0 (self->search_query, search) != 0)
+ {
+ g_clear_pointer (&self->search_query, g_free);
+ self->search_query = g_strdup (search);
+
+ update_search (self);
+
+ g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_SEARCH_QUERY]);
+
+ gtk_list_box_invalidate_filter (GTK_LIST_BOX (self->search_listbox));
+ gtk_list_box_invalidate_sort (GTK_LIST_BOX (self->search_listbox));
+ }
+}
+
+CcPanelListView
+cc_panel_list_get_view (CcPanelList *self)
+{
+ g_return_val_if_fail (CC_IS_PANEL_LIST (self), -1);
+
+ return self->view;
+}
+
+void
+cc_panel_list_go_previous (CcPanelList *self)
+{
+ CcPanelListView previous_view;
+
+ g_return_if_fail (CC_IS_PANEL_LIST (self));
+
+ previous_view = self->previous_view;
+
+ /* The back button is only visible and clickable outside the main view. If
+ * the previous view is the widget view itself, it means we went from:
+ *
+ * Main → Details or Devices → Widget
+ *
+ * to
+ *
+ * Main → Details or Devices
+ *
+ * A similar situation may happen with search.
+ *
+ * To avoid a loop (Details or Devices → Widget → Details or Devices → ...),
+ * make sure to go back to the main view when the current view is details or
+ * devices.
+ */
+ if (previous_view == CC_PANEL_LIST_WIDGET || previous_view == CC_PANEL_LIST_SEARCH)
+ previous_view = CC_PANEL_LIST_MAIN;
+
+ switch_to_view (self, previous_view);
+}
+
+void
+cc_panel_list_add_panel (CcPanelList *self,
+ CcPanelCategory category,
+ const gchar *id,
+ const gchar *title,
+ const gchar *description,
+ const GStrv keywords,
+ const gchar *icon,
+ CcPanelVisibility visibility,
+ gboolean has_sidebar)
+{
+ GtkWidget *listbox;
+ RowData *data, *search_data;
+
+ g_return_if_fail (CC_IS_PANEL_LIST (self));
+
+ /* Add the panel to the proper listbox */
+ data = row_data_new (category, id, title, description, keywords, icon, visibility, has_sidebar);
+ gtk_widget_set_visible (data->row, visibility == CC_PANEL_VISIBLE);
+
+ listbox = get_listbox_from_category (self, category);
+ gtk_container_add (GTK_CONTAINER (listbox), data->row);
+
+ /* And add to the search listbox too */
+ search_data = row_data_new (category, id, title, description, keywords, icon, visibility, has_sidebar);
+ gtk_widget_set_visible (search_data->row, visibility != CC_PANEL_HIDDEN);
+
+ gtk_container_add (GTK_CONTAINER (self->search_listbox), search_data->row);
+
+ g_hash_table_insert (self->id_to_data, data->id, data);
+ g_hash_table_insert (self->id_to_search_data, search_data->id, search_data);
+
+ /* Only show the Devices/Details rows when there's at least one panel */
+ if (category == CC_CATEGORY_PRIVACY)
+ gtk_widget_show (GTK_WIDGET (self->privacy_row));
+}
+
+/**
+ * cc_panel_list_set_active_panel:
+ * @self: a #CcPanelList
+ * @id: the id of the panel to be activated
+ *
+ * Sets the current active panel.
+ */
+void
+cc_panel_list_set_active_panel (CcPanelList *self,
+ const gchar *id)
+{
+ GtkWidget *listbox;
+ RowData *data;
+
+ g_return_if_fail (CC_IS_PANEL_LIST (self));
+
+ data = g_hash_table_lookup (self->id_to_data, id);
+
+ g_assert (data != NULL);
+
+ /* Stop if row is supposed to be always hidden */
+ if (data->visibility == CC_PANEL_HIDDEN)
+ {
+ g_debug ("Panel '%s' is always hidden, stopping.", id);
+ cc_panel_list_activate (self);
+ return;
+ }
+
+ /* If the currently selected panel is not always visible, for example when
+ * the panel is only visible on search and we're temporarily seeing it, make
+ * sure to hide it after the user moves out.
+ */
+ if (self->current_panel_id != NULL && g_strcmp0 (self->current_panel_id, id) != 0)
+ {
+ RowData *current_row_data;
+
+ current_row_data = g_hash_table_lookup (self->id_to_data, self->current_panel_id);
+
+ /* We cannot be showing a non-existent panel */
+ g_assert (current_row_data != NULL);
+
+ gtk_widget_set_visible (current_row_data->row, current_row_data->visibility == CC_PANEL_VISIBLE);
+ }
+
+ listbox = gtk_widget_get_parent (data->row);
+
+ /* The row might be hidden now, so make sure it's visible */
+ gtk_widget_show (data->row);
+
+ gtk_list_box_select_row (GTK_LIST_BOX (listbox), GTK_LIST_BOX_ROW (data->row));
+ gtk_widget_grab_focus (data->row);
+
+ /* When setting the active panel programatically, prevent from
+ * autoselecting the first panel of the new view.
+ */
+ self->autoselect_panel = FALSE;
+
+ if (self->view != CC_PANEL_LIST_WIDGET)
+ g_signal_emit_by_name (data->row, "activate");
+
+ /* Store the current panel id */
+ g_clear_pointer (&self->current_panel_id, g_free);
+ self->current_panel_id = g_strdup (id);
+}
+
+/**
+ * cc_panel_list_set_panel_visibility:
+ * @self: a #CcPanelList
+ * @id: the id of the panel
+ * @visibility: visibility of panel with @id
+ *
+ * Sets the visibility of panel with @id. @id must be a valid
+ * id with a corresponding panel.
+ */
+void
+cc_panel_list_set_panel_visibility (CcPanelList *self,
+ const gchar *id,
+ CcPanelVisibility visibility)
+{
+ RowData *data, *search_data;
+
+ g_return_if_fail (CC_IS_PANEL_LIST (self));
+
+ data = g_hash_table_lookup (self->id_to_data, id);
+ search_data = g_hash_table_lookup (self->id_to_search_data, id);
+
+ g_assert (data != NULL);
+ g_assert (search_data != NULL);
+
+ data->visibility = visibility;
+
+ /* If this is the currently selected row, and the panel can't be displayed
+ * (i.e. visibility != VISIBLE), then select the next possible row */
+ if (gtk_list_box_row_is_selected (GTK_LIST_BOX_ROW (data->row)) &&
+ visibility != CC_PANEL_VISIBLE)
+ {
+ activate_row_below (self, data);
+ }
+
+ gtk_widget_set_visible (data->row, visibility == CC_PANEL_VISIBLE);
+ gtk_widget_set_visible (search_data->row, visibility =! CC_PANEL_HIDDEN);
+}
+
+void
+cc_panel_list_add_sidebar_widget (CcPanelList *self,
+ GtkWidget *widget)
+{
+ g_return_if_fail (CC_IS_PANEL_LIST (self));
+
+ if (widget)
+ {
+ gtk_stack_add_named (GTK_STACK (self), widget, "custom-widget");
+ switch_to_view (self, CC_PANEL_LIST_WIDGET);
+ }
+ else
+ {
+ widget = get_widget_from_view (self, CC_PANEL_LIST_WIDGET);
+
+ if (widget)
+ gtk_container_remove (GTK_CONTAINER (self), widget);
+ }
+}
+
+void
+cc_panel_list_set_selection_mode (CcPanelList *self,
+ GtkSelectionMode selection_mode)
+{
+ g_return_if_fail (CC_IS_PANEL_LIST (self));
+
+ gtk_list_box_set_selection_mode (GTK_LIST_BOX (self->main_listbox), selection_mode);
+
+ /* When selection mode changed, selection will be lost. So reselect */
+ if (selection_mode == GTK_SELECTION_SINGLE && self->current_panel_id)
+ {
+ GtkWidget *listbox;
+ RowData *data;
+
+ data = g_hash_table_lookup (self->id_to_data, self->current_panel_id);
+ listbox = gtk_widget_get_parent (data->row);
+ gtk_list_box_select_row (GTK_LIST_BOX (listbox), GTK_LIST_BOX_ROW (data->row));
+ }
+}
+
diff --git a/shell/cc-panel-list.h b/shell/cc-panel-list.h
new file mode 100644
index 0000000..8d7324d
--- /dev/null
+++ b/shell/cc-panel-list.h
@@ -0,0 +1,78 @@
+/* cc-panel-list.c
+ *
+ * Copyright (C) 2016 Endless, Inc
+ *
+ * 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, see <http://www.gnu.org/licenses/>.
+ *
+ * Author: Georges Basile Stavracas Neto <gbsneto@gnome.org>
+ */
+
+#pragma once
+
+#include <glib-object.h>
+
+#include "cc-panel.h"
+#include "cc-shell-model.h"
+
+G_BEGIN_DECLS
+
+typedef enum
+{
+ CC_PANEL_LIST_MAIN,
+ CC_PANEL_LIST_PRIVACY,
+ CC_PANEL_LIST_WIDGET,
+ CC_PANEL_LIST_SEARCH
+} CcPanelListView;
+
+#define CC_TYPE_PANEL_LIST (cc_panel_list_get_type())
+
+G_DECLARE_FINAL_TYPE (CcPanelList, cc_panel_list, CC, PANEL_LIST, GtkStack)
+
+GtkWidget* cc_panel_list_new (void);
+
+gboolean cc_panel_list_activate (CcPanelList *self);
+
+const gchar* cc_panel_list_get_search_query (CcPanelList *self);
+
+void cc_panel_list_set_search_query (CcPanelList *self,
+ const gchar *search);
+
+CcPanelListView cc_panel_list_get_view (CcPanelList *self);
+
+void cc_panel_list_go_previous (CcPanelList *self);
+
+void cc_panel_list_add_panel (CcPanelList *self,
+ CcPanelCategory category,
+ const gchar *id,
+ const gchar *title,
+ const gchar *description,
+ const GStrv keywords,
+ const gchar *icon,
+ CcPanelVisibility visibility,
+ gboolean has_sidebar);
+
+void cc_panel_list_set_active_panel (CcPanelList *self,
+ const gchar *id);
+
+void cc_panel_list_set_panel_visibility (CcPanelList *self,
+ const gchar *id,
+ CcPanelVisibility visibility);
+
+void cc_panel_list_add_sidebar_widget (CcPanelList *self,
+ GtkWidget *widget);
+
+void cc_panel_list_set_selection_mode (CcPanelList *self,
+ GtkSelectionMode selection_mode);
+
+G_END_DECLS
diff --git a/shell/cc-panel-list.ui b/shell/cc-panel-list.ui
new file mode 100644
index 0000000..06ef5a4
--- /dev/null
+++ b/shell/cc-panel-list.ui
@@ -0,0 +1,147 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<interface>
+ <requires lib="gtk+" version="3.20"/>
+ <template class="CcPanelList" parent="GtkStack">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="vhomogeneous">False</property>
+ <property name="hhomogeneous">True</property>
+ <property name="transition_type">slide-left-right</property>
+ <child>
+ <object class="GtkListBox" id="main_listbox">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <signal name="row-activated" handler="row_activated_cb" object="CcPanelList" swapped="no" />
+ <child>
+ <object class="GtkListBoxRow" id="privacy_row">
+ <property name="visible">False</property>
+ <property name="can_focus">True</property>
+ <child>
+ <object class="GtkBox">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="border_width">12</property>
+ <property name="spacing">12</property>
+ <child>
+ <object class="GtkImage">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="icon_name">preferences-system-privacy-symbolic</property>
+ <style>
+ <class name="sidebar-icon" />
+ </style>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">True</property>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkLabel">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="hexpand">True</property>
+ <property name="label" translatable="yes">Privacy</property>
+ <property name="xalign">0</property>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">True</property>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkImage">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="icon_name">go-next-symbolic</property>
+ <style>
+ <class name="sidebar-icon" />
+ </style>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">True</property>
+ <property name="position">2</property>
+ </packing>
+ </child>
+ </object>
+ </child>
+ </object>
+ </child>
+ </object>
+ <packing>
+ <property name="name">main</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkListBox" id="privacy_listbox">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <signal name="row-activated" handler="row_activated_cb" object="CcPanelList" swapped="no" />
+ </object>
+ <packing>
+ <property name="name">privacy</property>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkListBox" id="search_listbox">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <signal name="row-activated" handler="search_row_activated_cb" object="CcPanelList" swapped="no" />
+
+ <!-- Placeholder -->
+ <child type="placeholder">
+ <object class="GtkBox" id="empty_search_placeholder">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="halign">center</property>
+ <property name="valign">center</property>
+ <property name="expand">True</property>
+ <property name="border_width">18</property>
+ <property name="orientation">vertical</property>
+ <property name="spacing">6</property>
+ <child>
+ <object class="GtkImage">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="pixel_size">64</property>
+ <property name="icon_name">edit-find-symbolic</property>
+ <style>
+ <class name="dim-label"/>
+ </style>
+ </object>
+ </child>
+ <child>
+ <object class="GtkLabel">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="label" translatable="yes">No results found</property>
+ <attributes>
+ <attribute name="weight" value="bold"/>
+ <attribute name="scale" value="1.44"/>
+ </attributes>
+ </object>
+ </child>
+ <child>
+ <object class="GtkLabel">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="label" translatable="yes">Try a different search</property>
+ <style>
+ <class name="dim-label"/>
+ </style>
+ </object>
+ </child>
+ </object>
+ </child>
+ </object>
+ <packing>
+ <property name="name">search</property>
+ <property name="position">3</property>
+ </packing>
+ </child>
+ </template>
+</interface>
diff --git a/shell/cc-panel-loader.c b/shell/cc-panel-loader.c
new file mode 100644
index 0000000..f203843
--- /dev/null
+++ b/shell/cc-panel-loader.c
@@ -0,0 +1,316 @@
+/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */
+/*
+ * Copyright (c) 2012 Giovanni Campagna <scampa.giovanni@gmail.com>
+ *
+ * The Control Center 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.
+ *
+ * The Control Center 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 the Control Center; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ *
+ * Author: Thomas Wood <thos@gnome.org>
+ */
+
+#include <config.h>
+
+#include <string.h>
+#include <gio/gdesktopappinfo.h>
+#include <glib/gi18n.h>
+
+#include "cc-panel.h"
+#include "cc-panel-loader.h"
+
+#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);
+#endif /* BUILD_BLUETOOTH */
+extern GType cc_color_panel_get_type (void);
+extern GType cc_date_time_panel_get_type (void);
+extern GType cc_default_apps_panel_get_type (void);
+extern GType cc_display_panel_get_type (void);
+extern GType cc_info_overview_panel_get_type (void);
+extern GType cc_keyboard_panel_get_type (void);
+extern GType cc_mouse_panel_get_type (void);
+#ifdef BUILD_NETWORK
+extern GType cc_network_panel_get_type (void);
+extern GType cc_wifi_panel_get_type (void);
+#endif /* BUILD_NETWORK */
+extern GType cc_notifications_panel_get_type (void);
+extern GType cc_goa_panel_get_type (void);
+extern GType cc_power_panel_get_type (void);
+extern GType cc_printers_panel_get_type (void);
+extern GType cc_region_panel_get_type (void);
+extern GType cc_removable_media_panel_get_type (void);
+extern GType cc_search_panel_get_type (void);
+extern GType cc_sharing_panel_get_type (void);
+extern GType cc_sound_panel_get_type (void);
+#ifdef BUILD_THUNDERBOLT
+extern GType cc_bolt_panel_get_type (void);
+#endif /* BUILD_THUNDERBOLT */
+extern GType cc_ua_panel_get_type (void);
+extern GType cc_user_panel_get_type (void);
+#ifdef BUILD_WACOM
+extern GType cc_wacom_panel_get_type (void);
+#endif /* BUILD_WACOM */
+extern GType cc_location_panel_get_type (void);
+extern GType cc_camera_panel_get_type (void);
+extern GType cc_microphone_panel_get_type (void);
+extern GType cc_usage_panel_get_type (void);
+extern GType cc_lock_panel_get_type (void);
+extern GType cc_diagnostics_panel_get_type (void);
+
+/* Static init functions */
+extern void cc_diagnostics_panel_static_init_func (void);
+#ifdef BUILD_NETWORK
+extern void cc_wifi_panel_static_init_func (void);
+#endif /* BUILD_NETWORK */
+#ifdef BUILD_WACOM
+extern void cc_wacom_panel_static_init_func (void);
+#endif /* BUILD_WACOM */
+
+#define PANEL_TYPE(name, get_type, init_func) { name, get_type, init_func }
+
+#else /* CC_PANEL_LOADER_NO_GTYPES */
+
+#define PANEL_TYPE(name, get_type, init_func) { name }
+
+#endif
+
+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),
+#endif
+ PANEL_TYPE("camera", cc_camera_panel_get_type, NULL),
+ PANEL_TYPE("color", cc_color_panel_get_type, NULL),
+ PANEL_TYPE("datetime", cc_date_time_panel_get_type, NULL),
+ PANEL_TYPE("default-apps", cc_default_apps_panel_get_type, NULL),
+ PANEL_TYPE("diagnostics", cc_diagnostics_panel_get_type, cc_diagnostics_panel_static_init_func),
+ PANEL_TYPE("display", cc_display_panel_get_type, NULL),
+ PANEL_TYPE("info-overview", cc_info_overview_panel_get_type, NULL),
+ PANEL_TYPE("keyboard", cc_keyboard_panel_get_type, NULL),
+ PANEL_TYPE("location", cc_location_panel_get_type, NULL),
+ PANEL_TYPE("lock", cc_lock_panel_get_type, NULL),
+ PANEL_TYPE("microphone", cc_microphone_panel_get_type, NULL),
+ PANEL_TYPE("mouse", cc_mouse_panel_get_type, NULL),
+#ifdef BUILD_NETWORK
+ PANEL_TYPE("network", cc_network_panel_get_type, NULL),
+ PANEL_TYPE("wifi", cc_wifi_panel_get_type, cc_wifi_panel_static_init_func),
+#endif
+ PANEL_TYPE("notifications", cc_notifications_panel_get_type, NULL),
+ PANEL_TYPE("online-accounts", cc_goa_panel_get_type, NULL),
+ PANEL_TYPE("power", cc_power_panel_get_type, NULL),
+ PANEL_TYPE("printers", cc_printers_panel_get_type, NULL),
+ PANEL_TYPE("region", cc_region_panel_get_type, NULL),
+ PANEL_TYPE("removable-media", cc_removable_media_panel_get_type, NULL),
+ PANEL_TYPE("search", cc_search_panel_get_type, NULL),
+ PANEL_TYPE("sharing", cc_sharing_panel_get_type, NULL),
+ PANEL_TYPE("sound", cc_sound_panel_get_type, NULL),
+#ifdef BUILD_THUNDERBOLT
+ PANEL_TYPE("thunderbolt", cc_bolt_panel_get_type, NULL),
+#endif
+ PANEL_TYPE("universal-access", cc_ua_panel_get_type, NULL),
+ PANEL_TYPE("usage", cc_usage_panel_get_type, NULL),
+ PANEL_TYPE("user-accounts", cc_user_panel_get_type, NULL),
+#ifdef BUILD_WACOM
+ PANEL_TYPE("wacom", cc_wacom_panel_get_type, cc_wacom_panel_static_init_func),
+#endif
+};
+
+/* Override for the panel vtable. When NULL, the default_panels will
+ * be used.
+ */
+static CcPanelLoaderVtable *panels_vtable = default_panels;
+static gsize panels_vtable_len = G_N_ELEMENTS (default_panels);
+
+
+static int
+parse_categories (GDesktopAppInfo *app)
+{
+ g_auto(GStrv) split = NULL;
+ const gchar *categories;
+ gint retval;
+
+ categories = g_desktop_app_info_get_categories (app);
+ split = g_strsplit (categories, ";", -1);
+
+ retval = -1;
+
+#define const_strv(s) ((const gchar* const*) s)
+
+ if (g_strv_contains (const_strv (split), "X-GNOME-ConnectivitySettings"))
+ retval = CC_CATEGORY_CONNECTIVITY;
+ else if (g_strv_contains (const_strv (split), "X-GNOME-PersonalizationSettings"))
+ retval = CC_CATEGORY_PERSONALIZATION;
+ else if (g_strv_contains (const_strv (split), "X-GNOME-AccountSettings"))
+ retval = CC_CATEGORY_ACCOUNT;
+ else if (g_strv_contains (const_strv (split), "X-GNOME-DevicesSettings"))
+ retval = CC_CATEGORY_DEVICES;
+ else if (g_strv_contains (const_strv (split), "X-GNOME-DetailsSettings"))
+ retval = CC_CATEGORY_DETAILS;
+ else if (g_strv_contains (const_strv (split), "X-GNOME-PrivacySettings"))
+ retval = CC_CATEGORY_PRIVACY;
+ else if (g_strv_contains (const_strv (split), "HardwareSettings"))
+ retval = CC_CATEGORY_HARDWARE;
+
+#undef const_strv
+
+ if (retval < 0)
+ {
+ g_warning ("Invalid categories %s for panel %s",
+ categories, g_app_info_get_id (G_APP_INFO (app)));
+ }
+
+ return retval;
+}
+
+#ifndef CC_PANEL_LOADER_NO_GTYPES
+
+static GHashTable *panel_types;
+
+static void
+ensure_panel_types (void)
+{
+ int i;
+
+ if (G_LIKELY (panel_types != NULL))
+ return;
+
+ panel_types = g_hash_table_new (g_str_hash, g_str_equal);
+ for (i = 0; i < panels_vtable_len; i++)
+ g_hash_table_insert (panel_types, (char*)panels_vtable[i].name, panels_vtable[i].get_type);
+}
+
+/**
+ * cc_panel_loader_load_by_name:
+ * @shell: a #CcShell implementation
+ * @name: name of the panel
+ * @parameters: parameters passed to the new panel
+ *
+ * Creates a new instance of a #CcPanel from @name, and sets the
+ * @shell and @parameters properties at construction time.
+ */
+CcPanel *
+cc_panel_loader_load_by_name (CcShell *shell,
+ const gchar *name,
+ GVariant *parameters)
+{
+ GType (*get_type) (void);
+
+ ensure_panel_types ();
+
+ get_type = g_hash_table_lookup (panel_types, name);
+ g_assert (get_type != NULL);
+
+ return g_object_new (get_type (),
+ "shell", shell,
+ "parameters", parameters,
+ NULL);
+}
+
+#endif /* CC_PANEL_LOADER_NO_GTYPES */
+
+/**
+ * cc_panel_loader_fill_model:
+ * @model: a #CcShellModel
+ *
+ * Fills @model with information from the available panels. It
+ * iterates over the panel vtable, gathering the panel names,
+ * build the desktop filename from it, and retrieves additional
+ * information from it.
+ */
+void
+cc_panel_loader_fill_model (CcShellModel *model)
+{
+ guint i;
+
+ for (i = 0; i < panels_vtable_len; i++)
+ {
+ g_autoptr(GDesktopAppInfo) app = NULL;
+ g_autofree gchar *desktop_name = NULL;
+ gint category;
+
+ desktop_name = g_strconcat ("gnome-", panels_vtable[i].name, "-panel.desktop", NULL);
+ app = g_desktop_app_info_new (desktop_name);
+
+ if (!app)
+ {
+ g_warning ("Ignoring broken panel %s (missing desktop file)", panels_vtable[i].name);
+ continue;
+ }
+
+ category = parse_categories (app);
+ if (G_UNLIKELY (category < 0))
+ continue;
+
+ cc_shell_model_add_item (model, category, G_APP_INFO (app), panels_vtable[i].name);
+ }
+
+ /* If there's an static init function, execute it after adding all panels to
+ * the model. This will allow the panels to show or hide themselves without
+ * having an instance running.
+ */
+#ifndef CC_PANEL_LOADER_NO_GTYPES
+ for (i = 0; i < panels_vtable_len; i++)
+ {
+ if (panels_vtable[i].static_init_func)
+ panels_vtable[i].static_init_func ();
+ }
+#endif
+}
+
+/**
+ * cc_panel_loader_list_panels:
+ *
+ * Prints the list of panels from the current panel vtable,
+ * usually as response to running GNOME Settings with the
+ * '--list' command line argument.
+ */
+void
+cc_panel_loader_list_panels (void)
+{
+ guint i;
+
+ g_print ("%s\n", _("Available panels:"));
+
+ for (i = 0; i < panels_vtable_len; i++)
+ g_print ("\t%s\n", panels_vtable[i].name);
+
+}
+
+/**
+ * cc_panel_loader_override_vtable:
+ * @override_vtable: the new panel vtable
+ * @n_elements: number of items of @override_vtable
+ *
+ * Override the default panel vtable so that GNOME Settings loads
+ * a custom set of panels. Intended to be used by tests to inject
+ * panels that exercise specific interactions with CcWindow (e.g.
+ * header widgets, permissions, etc).
+ */
+void
+cc_panel_loader_override_vtable (CcPanelLoaderVtable *override_vtable,
+ gsize n_elements)
+{
+ g_assert (override_vtable != NULL);
+ g_assert (n_elements > 0);
+
+ g_debug ("Overriding default panel vtable");
+
+ panels_vtable = override_vtable;
+ panels_vtable_len = n_elements;
+}
diff --git a/shell/cc-panel-loader.h b/shell/cc-panel-loader.h
new file mode 100644
index 0000000..1b91832
--- /dev/null
+++ b/shell/cc-panel-loader.h
@@ -0,0 +1,52 @@
+/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */
+/*
+ * Copyright (c) 2012 Giovanni Campagna <scampa.giovanni@gmail.com>
+ *
+ * The Control Center 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.
+ *
+ * The Control Center 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 the Control Center; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ *
+ */
+
+#pragma once
+
+#include "config.h"
+
+#include <glib.h>
+#include <glib-object.h>
+#include <shell/cc-panel.h>
+#include <shell/cc-shell-model.h>
+
+G_BEGIN_DECLS
+
+typedef struct
+{
+ const gchar *name;
+
+#ifndef CC_PANEL_LOADER_NO_GTYPES
+ GType (*get_type)(void);
+ CcPanelStaticInitFunc static_init_func;
+#endif
+} CcPanelLoaderVtable;
+
+void cc_panel_loader_fill_model (CcShellModel *model);
+void cc_panel_loader_list_panels (void);
+CcPanel *cc_panel_loader_load_by_name (CcShell *shell,
+ const char *name,
+ GVariant *parameters);
+
+void cc_panel_loader_override_vtable (CcPanelLoaderVtable *override_vtable,
+ gsize n_elements);
+
+G_END_DECLS
+
diff --git a/shell/cc-panel.c b/shell/cc-panel.c
new file mode 100644
index 0000000..df9a7f3
--- /dev/null
+++ b/shell/cc-panel.c
@@ -0,0 +1,257 @@
+/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*-
+ *
+ * Copyright (C) 2010 Red Hat, Inc.
+ * Copyright (C) 2010 Intel, Inc
+ *
+ * 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, see <http://www.gnu.org/licenses/>.
+ *
+ * Authors: William Jon McCann <jmccann@redhat.com>
+ * Thomas Wood <thomas.wood@intel.com>
+ *
+ */
+
+/**
+ * SECTION:cc-panel
+ * @short_description: An abstract class for Control Center panels
+ *
+ * CcPanel is an abstract class used to implement panels for the shell. A
+ * panel contains a collection of related settings that are displayed within
+ * the shell window.
+ */
+
+#include "config.h"
+
+#include "cc-panel.h"
+
+#include <stdlib.h>
+#include <stdio.h>
+
+#include <gtk/gtk.h>
+#include <gio/gio.h>
+
+typedef struct
+{
+ CcShell *shell;
+ GCancellable *cancellable;
+} CcPanelPrivate;
+
+G_DEFINE_ABSTRACT_TYPE_WITH_PRIVATE (CcPanel, cc_panel, GTK_TYPE_BIN)
+
+enum
+{
+ SIDEBAR_ACTIVATED,
+ LAST_SIGNAL
+};
+
+enum
+{
+ PROP_0,
+ PROP_SHELL,
+ PROP_PARAMETERS,
+ N_PROPS
+};
+
+static GParamSpec *properties [N_PROPS];
+
+static guint signals [LAST_SIGNAL] = { 0 };
+
+static void
+cc_panel_set_property (GObject *object,
+ guint prop_id,
+ const GValue *value,
+ GParamSpec *pspec)
+{
+ CcPanelPrivate *priv = cc_panel_get_instance_private (CC_PANEL (object));
+
+ switch (prop_id)
+ {
+ case PROP_SHELL:
+ /* construct only property */
+ priv->shell = g_value_get_object (value);
+ break;
+
+ case PROP_PARAMETERS:
+ {
+ g_autoptr(GVariant) v = NULL;
+ GVariant *parameters;
+ gsize n_parameters;
+
+ parameters = g_value_get_variant (value);
+
+ if (parameters == NULL)
+ return;
+
+ n_parameters = g_variant_n_children (parameters);
+ if (n_parameters == 0)
+ return;
+
+ g_variant_get_child (parameters, 0, "v", &v);
+
+ if (!g_variant_is_of_type (v, G_VARIANT_TYPE_DICTIONARY))
+ g_warning ("Wrong type for the first argument GVariant, expected 'a{sv}' but got '%s'",
+ (gchar *)g_variant_get_type (v));
+ else if (g_variant_n_children (v) > 0)
+ g_warning ("Ignoring additional flags");
+
+ if (n_parameters > 1)
+ g_warning ("Ignoring additional parameters");
+
+ break;
+ }
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+ break;
+ }
+}
+
+static void
+cc_panel_get_property (GObject *object,
+ guint prop_id,
+ GValue *value,
+ GParamSpec *pspec)
+{
+ CcPanelPrivate *priv = cc_panel_get_instance_private (CC_PANEL (object));
+
+ switch (prop_id)
+ {
+ case PROP_SHELL:
+ g_value_set_object (value, priv->shell);
+ break;
+
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+ break;
+ }
+}
+
+static void
+cc_panel_finalize (GObject *object)
+{
+ CcPanelPrivate *priv = cc_panel_get_instance_private (CC_PANEL (object));
+
+ g_cancellable_cancel (priv->cancellable);
+ g_clear_object (&priv->cancellable);
+
+ G_OBJECT_CLASS (cc_panel_parent_class)->finalize (object);
+}
+
+static void
+cc_panel_class_init (CcPanelClass *klass)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (klass);
+
+ object_class->get_property = cc_panel_get_property;
+ object_class->set_property = cc_panel_set_property;
+ object_class->finalize = cc_panel_finalize;
+
+ signals[SIDEBAR_ACTIVATED] = g_signal_new ("sidebar-activated",
+ G_TYPE_FROM_CLASS (object_class),
+ G_SIGNAL_RUN_LAST,
+ 0, NULL, NULL,
+ g_cclosure_marshal_VOID__VOID,
+ G_TYPE_NONE, 0);
+
+ properties[PROP_SHELL] = g_param_spec_object ("shell",
+ "Shell",
+ "Shell the Panel resides in",
+ CC_TYPE_SHELL,
+ G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS);
+
+ properties[PROP_PARAMETERS] = g_param_spec_variant ("parameters",
+ "Structured parameters",
+ "Additional parameters passed externally (ie. command line, D-Bus activation)",
+ G_VARIANT_TYPE ("av"),
+ NULL,
+ G_PARAM_WRITABLE | G_PARAM_STATIC_STRINGS);
+
+ g_object_class_install_properties (object_class, N_PROPS, properties);
+}
+
+static void
+cc_panel_init (CcPanel *panel)
+{
+}
+
+/**
+ * cc_panel_get_shell:
+ * @panel: A #CcPanel
+ *
+ * Get the shell that the panel resides in
+ *
+ * Returns: a #CcShell
+ */
+CcShell *
+cc_panel_get_shell (CcPanel *panel)
+{
+ CcPanelPrivate *priv;
+
+ g_return_val_if_fail (CC_IS_PANEL (panel), NULL);
+
+ priv = cc_panel_get_instance_private (panel);
+
+ return priv->shell;
+}
+
+const gchar*
+cc_panel_get_help_uri (CcPanel *panel)
+{
+ CcPanelClass *class = CC_PANEL_GET_CLASS (panel);
+
+ if (class->get_help_uri)
+ return class->get_help_uri (panel);
+
+ return NULL;
+}
+
+GtkWidget *
+cc_panel_get_title_widget (CcPanel *panel)
+{
+ CcPanelClass *class = CC_PANEL_GET_CLASS (panel);
+
+ if (class->get_title_widget)
+ return class->get_title_widget (panel);
+
+ return NULL;
+}
+
+GtkWidget*
+cc_panel_get_sidebar_widget (CcPanel *panel)
+{
+ CcPanelClass *class = CC_PANEL_GET_CLASS (panel);
+
+ if (class->get_sidebar_widget)
+ {
+ GtkWidget *sidebar_widget;
+
+ sidebar_widget = class->get_sidebar_widget (panel);
+ g_assert (sidebar_widget != NULL);
+
+ return sidebar_widget;
+ }
+
+ return NULL;
+}
+
+GCancellable *
+cc_panel_get_cancellable (CcPanel *panel)
+{
+ CcPanelPrivate *priv = cc_panel_get_instance_private (panel);
+
+ g_return_val_if_fail (CC_IS_PANEL (panel), NULL);
+
+ if (priv->cancellable == NULL)
+ priv->cancellable = g_cancellable_new ();
+
+ return priv->cancellable;
+}
diff --git a/shell/cc-panel.h b/shell/cc-panel.h
new file mode 100644
index 0000000..35cb9c4
--- /dev/null
+++ b/shell/cc-panel.h
@@ -0,0 +1,100 @@
+/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*-
+ *
+ * Copyright (C) 2010 Red Hat, Inc.
+ * Copyright (C) 2010 Intel, Inc
+ *
+ * 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, see <http://www.gnu.org/licenses/>.
+ *
+ * Authors: William Jon McCann <jmccann@redhat.com>
+ * Thomas Wood <thomas.wood@intel.com>
+ */
+
+#pragma once
+
+#include <glib-object.h>
+#include <gtk/gtk.h>
+
+/**
+ * Utility macro used to register panels
+ *
+ * use: CC_PANEL_REGISTER (PluginName, plugin_name)
+ */
+#define CC_PANEL_REGISTER(PluginName, plugin_name) G_DEFINE_TYPE (PluginName, plugin_name, CC_TYPE_PANEL)
+
+/**
+ * CcPanelStaticInitFunc:
+ *
+ * Function that statically allocates resources and initializes
+ * any data that the panel will make use of during runtime.
+ *
+ * If panels represent hardware that can potentially not exist,
+ * e.g. the Wi-Fi panel, these panels can use this function to
+ * show or hide themselves without needing to have an instance
+ * created and running.
+ */
+typedef void (*CcPanelStaticInitFunc) (void);
+
+
+#define CC_TYPE_PANEL (cc_panel_get_type())
+G_DECLARE_DERIVABLE_TYPE (CcPanel, cc_panel, CC, PANEL, GtkBin)
+
+/**
+ * CcPanelVisibility:
+ *
+ * @CC_PANEL_HIDDEN: Panel is hidden from search and sidebar, and not reachable.
+ * @CC_PANEL_VISIBLE_IN_SEARCH: Panel is hidden from main view, but can be accessed from search.
+ * @CC_PANEL_VISIBLE: Panel is visible everywhere.
+ */
+typedef enum
+{
+ CC_PANEL_HIDDEN,
+ CC_PANEL_VISIBLE_IN_SEARCH,
+ CC_PANEL_VISIBLE,
+} CcPanelVisibility;
+
+/* cc-shell.h requires CcPanel, so make sure it is defined first */
+#include "cc-shell.h"
+
+G_BEGIN_DECLS
+
+/**
+ * CcPanelClass:
+ *
+ * The contents of this struct are private and should not be accessed directly.
+ */
+struct _CcPanelClass
+{
+ /*< private >*/
+ GtkBinClass parent_class;
+
+ const gchar* (*get_help_uri) (CcPanel *panel);
+
+ GtkWidget* (*get_title_widget) (CcPanel *panel);
+ GtkWidget* (*get_sidebar_widget) (CcPanel *panel);
+};
+
+CcShell* cc_panel_get_shell (CcPanel *panel);
+
+GPermission* cc_panel_get_permission (CcPanel *panel);
+
+const gchar* cc_panel_get_help_uri (CcPanel *panel);
+
+GtkWidget* cc_panel_get_title_widget (CcPanel *panel);
+
+GtkWidget* cc_panel_get_sidebar_widget (CcPanel *panel);
+
+GCancellable *cc_panel_get_cancellable (CcPanel *panel);
+
+G_END_DECLS
+
diff --git a/shell/cc-shell-model.c b/shell/cc-shell-model.c
new file mode 100644
index 0000000..19fc9c6
--- /dev/null
+++ b/shell/cc-shell-model.c
@@ -0,0 +1,422 @@
+/*
+ * Copyright (c) 2009, 2010 Intel, Inc.
+ * Copyright (c) 2010 Red Hat, Inc.
+ *
+ * The Control Center 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.
+ *
+ * The Control Center 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 the Control Center; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ *
+ * Author: Thomas Wood <thos@gnome.org>
+ */
+
+#include "cc-shell-model.h"
+#include "cc-util.h"
+
+#include <string.h>
+
+#include <gio/gdesktopappinfo.h>
+
+#define GNOME_SETTINGS_PANEL_ID_KEY "X-GNOME-Settings-Panel"
+#define GNOME_SETTINGS_PANEL_CATEGORY GNOME_SETTINGS_PANEL_ID_KEY
+#define GNOME_SETTINGS_PANEL_ID_KEYWORDS "Keywords"
+
+struct _CcShellModel
+{
+ GtkListStore parent;
+
+ GStrv sort_terms;
+};
+
+G_DEFINE_TYPE (CcShellModel, cc_shell_model, GTK_TYPE_LIST_STORE)
+
+static gint
+sort_by_name (GtkTreeModel *model,
+ GtkTreeIter *a,
+ GtkTreeIter *b)
+{
+ g_autofree gchar *a_name = NULL;
+ g_autofree gchar *b_name = NULL;
+
+ gtk_tree_model_get (model, a, COL_CASEFOLDED_NAME, &a_name, -1);
+ gtk_tree_model_get (model, b, COL_CASEFOLDED_NAME, &b_name, -1);
+
+ return g_strcmp0 (a_name, b_name);
+}
+
+static gint
+sort_by_name_with_terms (GtkTreeModel *model,
+ GtkTreeIter *a,
+ GtkTreeIter *b,
+ gchar **terms)
+{
+ gboolean a_match, b_match;
+ g_autofree gchar *a_name = NULL;
+ g_autofree gchar *b_name = NULL;
+ gint i;
+
+ gtk_tree_model_get (model, a, COL_CASEFOLDED_NAME, &a_name, -1);
+ gtk_tree_model_get (model, b, COL_CASEFOLDED_NAME, &b_name, -1);
+
+ for (i = 0; terms[i]; ++i)
+ {
+ a_match = strstr (a_name, terms[i]) != NULL;
+ b_match = strstr (b_name, terms[i]) != NULL;
+
+ if (a_match && !b_match)
+ return -1;
+ else if (!a_match && b_match)
+ return 1;
+ }
+
+ return 0;
+}
+
+static gint
+count_matches (gchar **keywords,
+ gchar **terms)
+{
+ gint i, j, c;
+
+ if (!keywords || !terms)
+ return 0;
+
+ c = 0;
+
+ for (i = 0; terms[i]; ++i)
+ for (j = 0; keywords[j]; ++j)
+ if (strstr (keywords[j], terms[i]))
+ c += 1;
+
+ return c;
+}
+
+static gint
+sort_by_keywords_with_terms (GtkTreeModel *model,
+ GtkTreeIter *a,
+ GtkTreeIter *b,
+ gchar **terms)
+{
+ gint a_matches, b_matches;
+ g_auto(GStrv) a_keywords = NULL;
+ g_auto(GStrv) b_keywords = NULL;
+
+ gtk_tree_model_get (model, a, COL_KEYWORDS, &a_keywords, -1);
+ gtk_tree_model_get (model, b, COL_KEYWORDS, &b_keywords, -1);
+
+ a_matches = count_matches (a_keywords, terms);
+ b_matches = count_matches (b_keywords, terms);
+
+ if (a_matches > b_matches)
+ return -1;
+ else if (a_matches < b_matches)
+ return 1;
+
+ return 0;
+}
+
+static gint
+sort_by_description_with_terms (GtkTreeModel *model,
+ GtkTreeIter *a,
+ GtkTreeIter *b,
+ gchar **terms)
+{
+ gint a_matches, b_matches;
+ g_autofree gchar *a_description = NULL;
+ g_autofree gchar *b_description = NULL;
+ g_auto(GStrv) a_description_split = NULL;
+ g_auto(GStrv) b_description_split = NULL;
+
+ gtk_tree_model_get (model, a, COL_DESCRIPTION, &a_description, -1);
+ gtk_tree_model_get (model, b, COL_DESCRIPTION, &b_description, -1);
+
+ if (a_description && !b_description)
+ return -1;
+ else if (!a_description && b_description)
+ return 1;
+ else if (!a_description && !b_description)
+ return 0;
+
+ a_description_split = g_strsplit (a_description, " ", -1);
+ b_description_split = g_strsplit (b_description, " ", -1);
+
+ a_matches = count_matches (a_description_split, terms);
+ b_matches = count_matches (b_description_split, terms);
+
+ if (a_matches > b_matches)
+ return -1;
+ else if (a_matches < b_matches)
+ return 1;
+
+ return 0;
+}
+
+static gint
+sort_with_terms (GtkTreeModel *model,
+ GtkTreeIter *a,
+ GtkTreeIter *b,
+ gchar **terms)
+{
+ gint rval;
+
+ rval = sort_by_name_with_terms (model, a, b, terms);
+ if (rval)
+ return rval;
+
+ rval = sort_by_keywords_with_terms (model, a, b, terms);
+ if (rval)
+ return rval;
+
+ rval = sort_by_description_with_terms (model, a, b, terms);
+ if (rval)
+ return rval;
+
+ return sort_by_name (model, a, b);
+}
+
+static gint
+cc_shell_model_sort_func (GtkTreeModel *model,
+ GtkTreeIter *a,
+ GtkTreeIter *b,
+ gpointer data)
+{
+ CcShellModel *self = data;
+
+ if (!self->sort_terms || !self->sort_terms[0])
+ return sort_by_name (model, a, b);
+ else
+ return sort_with_terms (model, a, b, self->sort_terms);
+}
+
+static void
+cc_shell_model_finalize (GObject *object)
+{
+ CcShellModel *self = CC_SHELL_MODEL (object);
+
+ g_clear_pointer (&self->sort_terms, g_strfreev);
+
+ G_OBJECT_CLASS (cc_shell_model_parent_class)->finalize (object);
+}
+
+static void
+cc_shell_model_class_init (CcShellModelClass *klass)
+{
+ GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
+
+ gobject_class->finalize = cc_shell_model_finalize;
+}
+
+static void
+cc_shell_model_init (CcShellModel *self)
+{
+ GType types[] = {G_TYPE_STRING, G_TYPE_STRING, G_TYPE_APP_INFO, G_TYPE_STRING, G_TYPE_UINT,
+ G_TYPE_STRING, G_TYPE_STRING, G_TYPE_ICON, G_TYPE_STRV, G_TYPE_UINT, G_TYPE_BOOLEAN };
+
+ gtk_list_store_set_column_types (GTK_LIST_STORE (self),
+ N_COLS, types);
+
+ gtk_tree_sortable_set_default_sort_func (GTK_TREE_SORTABLE (self),
+ cc_shell_model_sort_func,
+ self, NULL);
+ gtk_tree_sortable_set_sort_column_id (GTK_TREE_SORTABLE (self),
+ GTK_TREE_SORTABLE_DEFAULT_SORT_COLUMN_ID,
+ GTK_SORT_ASCENDING);
+}
+
+CcShellModel *
+cc_shell_model_new (void)
+{
+ return g_object_new (CC_TYPE_SHELL_MODEL, NULL);
+}
+
+static char **
+get_casefolded_keywords (GAppInfo *appinfo)
+{
+ const char * const * keywords;
+ char **casefolded_keywords;
+ int i, n;
+
+ keywords = g_desktop_app_info_get_keywords (G_DESKTOP_APP_INFO (appinfo));
+ n = keywords ? g_strv_length ((char**) keywords) : 0;
+ casefolded_keywords = g_new (char*, n+1);
+
+ for (i = 0; i < n; i++)
+ casefolded_keywords[i] = cc_util_normalize_casefold_and_unaccent (keywords[i]);
+ casefolded_keywords[n] = NULL;
+
+ return casefolded_keywords;
+}
+
+static GIcon *
+symbolicize_g_icon (GIcon *gicon)
+{
+ const gchar * const *names;
+ g_autofree gchar *new_name = NULL;
+
+ if (!G_IS_THEMED_ICON (gicon))
+ return g_object_ref (gicon);
+
+ names = g_themed_icon_get_names (G_THEMED_ICON (gicon));
+
+ if (g_str_has_suffix (names[0], "-symbolic"))
+ return g_object_ref (gicon);
+
+ new_name = g_strdup_printf ("%s-symbolic", names[0]);
+ return g_themed_icon_new_with_default_fallbacks (new_name);
+}
+
+void
+cc_shell_model_add_item (CcShellModel *model,
+ CcPanelCategory category,
+ GAppInfo *appinfo,
+ const char *id)
+{
+ g_autoptr(GIcon) icon = NULL;
+ const gchar *name = g_app_info_get_name (appinfo);
+ const gchar *comment = g_app_info_get_description (appinfo);
+ g_auto(GStrv) keywords = NULL;
+ g_autofree gchar *casefolded_name = NULL;
+ g_autofree gchar *casefolded_description = NULL;
+ gboolean has_sidebar;
+
+ casefolded_name = cc_util_normalize_casefold_and_unaccent (name);
+ casefolded_description = cc_util_normalize_casefold_and_unaccent (comment);
+ keywords = get_casefolded_keywords (appinfo);
+ icon = symbolicize_g_icon (g_app_info_get_icon (appinfo));
+ has_sidebar = g_desktop_app_info_get_boolean (G_DESKTOP_APP_INFO (appinfo), "X-GNOME-ControlCenter-HasSidebar");
+
+ gtk_list_store_insert_with_values (GTK_LIST_STORE (model), NULL, 0,
+ COL_NAME, name,
+ COL_CASEFOLDED_NAME, casefolded_name,
+ COL_APP, appinfo,
+ COL_ID, id,
+ COL_CATEGORY, category,
+ COL_DESCRIPTION, comment,
+ COL_CASEFOLDED_DESCRIPTION, casefolded_description,
+ COL_GICON, icon,
+ COL_KEYWORDS, keywords,
+ COL_VISIBILITY, CC_PANEL_VISIBLE,
+ COL_HAS_SIDEBAR, has_sidebar,
+ -1);
+}
+
+gboolean
+cc_shell_model_has_panel (CcShellModel *model,
+ const char *id)
+{
+ GtkTreeIter iter;
+ gboolean valid;
+
+ g_assert (id);
+
+ valid = gtk_tree_model_get_iter_first (GTK_TREE_MODEL (model), &iter);
+ while (valid)
+ {
+ g_autofree gchar *panel_id = NULL;
+
+ gtk_tree_model_get (GTK_TREE_MODEL (model), &iter, COL_ID, &panel_id, -1);
+ if (g_str_equal (id, panel_id))
+ return TRUE;
+
+ valid = gtk_tree_model_iter_next (GTK_TREE_MODEL (model), &iter);
+ }
+
+ return FALSE;
+}
+
+gboolean
+cc_shell_model_iter_matches_search (CcShellModel *model,
+ GtkTreeIter *iter,
+ const char *term)
+{
+ g_autofree gchar *name = NULL;
+ g_autofree gchar *description = NULL;
+ gboolean result;
+ g_auto(GStrv) keywords = NULL;
+
+ gtk_tree_model_get (GTK_TREE_MODEL (model), iter,
+ COL_CASEFOLDED_NAME, &name,
+ COL_CASEFOLDED_DESCRIPTION, &description,
+ COL_KEYWORDS, &keywords,
+ -1);
+
+ result = (strstr (name, term) != NULL);
+
+ if (!result && description)
+ result = (strstr (description, term) != NULL);
+
+ if (!result && keywords)
+ {
+ gint i;
+
+ for (i = 0; !result && keywords[i]; i++)
+ result = (strstr (keywords[i], term) == keywords[i]);
+ }
+
+ return result;
+}
+
+void
+cc_shell_model_set_sort_terms (CcShellModel *self,
+ gchar **terms)
+{
+ g_return_if_fail (CC_IS_SHELL_MODEL (self));
+
+ g_clear_pointer (&self->sort_terms, g_strfreev);
+ self->sort_terms = g_strdupv (terms);
+
+ /* trigger a re-sort */
+ gtk_tree_sortable_set_default_sort_func (GTK_TREE_SORTABLE (self),
+ cc_shell_model_sort_func,
+ self,
+ NULL);
+}
+
+void
+cc_shell_model_set_panel_visibility (CcShellModel *self,
+ const gchar *id,
+ CcPanelVisibility visibility)
+{
+ GtkTreeModel *model;
+ GtkTreeIter iter;
+ gboolean valid;
+
+ g_return_if_fail (CC_IS_SHELL_MODEL (self));
+
+ model = GTK_TREE_MODEL (self);
+
+ /* Find the iter for the panel with the given id */
+ valid = gtk_tree_model_get_iter_first (model, &iter);
+
+ while (valid)
+ {
+ g_autofree gchar *item_id = NULL;
+
+ gtk_tree_model_get (model, &iter, COL_ID, &item_id, -1);
+
+ /* Found the iter */
+ if (g_str_equal (id, item_id))
+ break;
+
+ /* If not found, continue */
+ valid = gtk_tree_model_iter_next (model, &iter);
+ }
+
+ /* If we don't find any panel with the given id, we'll iterate until
+ * valid == FALSE, so we can use this variable to determine if the
+ * panel was found or not. It is a programming error to try to set
+ * the visibility of a non-existent panel.
+ */
+ g_assert (valid);
+
+ gtk_list_store_set (GTK_LIST_STORE (self), &iter, COL_VISIBILITY, visibility, -1);
+}
diff --git a/shell/cc-shell-model.h b/shell/cc-shell-model.h
new file mode 100644
index 0000000..763803e
--- /dev/null
+++ b/shell/cc-shell-model.h
@@ -0,0 +1,84 @@
+/*
+ * Copyright (c) 2010 Intel, Inc.
+ *
+ * The Control Center 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.
+ *
+ * The Control Center 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 the Control Center; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ *
+ * Author: Thomas Wood <thos@gnome.org>
+ */
+
+#pragma once
+
+#include "cc-panel.h"
+
+#include <gtk/gtk.h>
+
+G_BEGIN_DECLS
+
+#define CC_TYPE_SHELL_MODEL cc_shell_model_get_type()
+
+G_DECLARE_FINAL_TYPE (CcShellModel, cc_shell_model, CC, SHELL_MODEL, GtkListStore)
+
+typedef enum
+{
+ CC_CATEGORY_CONNECTIVITY,
+ CC_CATEGORY_PERSONALIZATION,
+ CC_CATEGORY_ACCOUNT,
+ CC_CATEGORY_HARDWARE,
+ CC_CATEGORY_PRIVACY,
+ CC_CATEGORY_DEVICES,
+ CC_CATEGORY_DETAILS,
+ CC_CATEGORY_LAST
+} CcPanelCategory;
+
+enum
+{
+ COL_NAME,
+ COL_CASEFOLDED_NAME,
+ COL_APP,
+ COL_ID,
+ COL_CATEGORY,
+ COL_DESCRIPTION,
+ COL_CASEFOLDED_DESCRIPTION,
+ COL_GICON,
+ COL_KEYWORDS,
+ COL_VISIBILITY,
+ COL_HAS_SIDEBAR,
+
+ N_COLS
+};
+
+
+CcShellModel* cc_shell_model_new (void);
+
+void cc_shell_model_add_item (CcShellModel *model,
+ CcPanelCategory category,
+ GAppInfo *appinfo,
+ const char *id);
+
+gboolean cc_shell_model_has_panel (CcShellModel *model,
+ const char *id);
+
+gboolean cc_shell_model_iter_matches_search (CcShellModel *model,
+ GtkTreeIter *iter,
+ const char *term);
+
+void cc_shell_model_set_sort_terms (CcShellModel *model,
+ GStrv terms);
+
+void cc_shell_model_set_panel_visibility (CcShellModel *self,
+ const gchar *id,
+ CcPanelVisibility visible);
+
+G_END_DECLS
diff --git a/shell/cc-shell.c b/shell/cc-shell.c
new file mode 100644
index 0000000..ffab0a6
--- /dev/null
+++ b/shell/cc-shell.c
@@ -0,0 +1,175 @@
+/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*-
+ *
+ * Copyright (c) 2010 Intel, Inc.
+ *
+ * The Control Center 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.
+ *
+ * The Control Center 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 the Control Center; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ *
+ * Author: Thomas Wood <thos@gnome.org>
+ */
+
+/**
+ * SECTION:cc-shell
+ * @short_description: Interface representing the Control Center shell
+ *
+ * CcShell is an interface that represents an instance of a control
+ * center shell. It provides access to some of the properties of the shell
+ * that panels will need to read or change. When a panel is created it has an
+ * instance of CcShell available that represents the current shell.
+ */
+
+
+#include "cc-shell.h"
+#include "cc-panel.h"
+
+G_DEFINE_INTERFACE (CcShell, cc_shell, GTK_TYPE_WIDGET)
+
+static void
+cc_shell_default_init (CcShellInterface *iface)
+{
+ g_object_interface_install_property (iface,
+ g_param_spec_object ("active-panel",
+ "active panel",
+ "The currently active Panel",
+ CC_TYPE_PANEL,
+ G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
+}
+
+/**
+ * cc_shell_get_active_panel:
+ * @shell: A #CcShell
+ *
+ * Get the current active panel
+ *
+ * Returns: a #CcPanel or NULL if no panel is active
+ */
+CcPanel *
+cc_shell_get_active_panel (CcShell *shell)
+{
+ CcPanel *panel = NULL;
+
+ g_return_val_if_fail (CC_IS_SHELL (shell), NULL);
+
+ g_object_get (shell, "active-panel", &panel, NULL);
+
+ return panel;
+}
+
+/**
+ * cc_shell_set_active_panel:
+ * @shell: A #CcShell
+ * @panel: A #CcPanel
+ *
+ * Set the current active panel. If @panel is NULL, then the shell is returned
+ * to a state where no panel is being displayed (for example, the list of panels
+ * may be shown instead).
+ *
+ */
+void
+cc_shell_set_active_panel (CcShell *shell,
+ CcPanel *panel)
+{
+ g_return_if_fail (CC_IS_SHELL (shell));
+ g_return_if_fail (panel == NULL || CC_IS_PANEL (panel));
+
+ g_object_set (shell, "active-panel", panel, NULL);
+}
+
+/**
+ * cc_shell_set_active_panel_from_id:
+ * @shell: A #CcShell
+ * @id: The ID of the panel to set as active
+ * @parameters: A #GVariant with additional parameters
+ * @error: A #GError
+ *
+ * Find a panel corresponding to the specified id and set it as active.
+ *
+ * Returns: #TRUE if the panel was found and set as the active panel
+ */
+gboolean
+cc_shell_set_active_panel_from_id (CcShell *shell,
+ const gchar *id,
+ GVariant *parameters,
+ GError **error)
+{
+ CcShellInterface *iface;
+
+ g_return_val_if_fail (CC_IS_SHELL (shell), FALSE);
+
+ iface = CC_SHELL_GET_IFACE (shell);
+
+ if (!iface->set_active_panel_from_id)
+ {
+ g_warning ("Object of type \"%s\" does not implement required interface"
+ " method \"set_active_panel_from_id\",",
+ G_OBJECT_TYPE_NAME (shell));
+ return FALSE;
+ }
+ else
+ {
+ return iface->set_active_panel_from_id (shell, id, parameters, error);
+ }
+}
+
+/**
+ * cc_shell_get_toplevel:
+ * @shell: A #CcShell
+ *
+ * Gets the toplevel window of the shell.
+ *
+ * Returns: The #GtkWidget of the shell window, or #NULL on error.
+ */
+GtkWidget *
+cc_shell_get_toplevel (CcShell *shell)
+{
+ CcShellInterface *iface;
+
+ g_return_val_if_fail (CC_IS_SHELL (shell), NULL);
+
+ iface = CC_SHELL_GET_IFACE (shell);
+
+ if (iface->get_toplevel)
+ {
+ return iface->get_toplevel (shell);
+ }
+
+ g_warning ("Object of type \"%s\" does not implement required interface"
+ " method \"get_toplevel\",",
+ G_OBJECT_TYPE_NAME (shell));
+
+ return NULL;
+}
+
+void
+cc_shell_embed_widget_in_header (CcShell *shell,
+ GtkWidget *widget,
+ GtkPositionType position)
+{
+ CcShellInterface *iface;
+
+ g_return_if_fail (CC_IS_SHELL (shell));
+
+ iface = CC_SHELL_GET_IFACE (shell);
+
+ if (!iface->embed_widget_in_header)
+ {
+ g_warning ("Object of type \"%s\" does not implement required interface"
+ " method \"embed_widget_in_header\",",
+ G_OBJECT_TYPE_NAME (shell));
+ }
+ else
+ {
+ iface->embed_widget_in_header (shell, widget, position);
+ }
+}
diff --git a/shell/cc-shell.h b/shell/cc-shell.h
new file mode 100644
index 0000000..f5d35db
--- /dev/null
+++ b/shell/cc-shell.h
@@ -0,0 +1,77 @@
+/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*-
+ *
+ * Copyright (c) 2010 Intel, Inc.
+ *
+ * The Control Center 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.
+ *
+ * The Control Center 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 the Control Center; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ *
+ * Author: Thomas Wood <thos@gnome.org>
+ */
+
+#pragma once
+
+#include <gtk/gtk.h>
+
+G_BEGIN_DECLS
+
+#define CC_TYPE_SHELL (cc_shell_get_type())
+#define CC_SHELL(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), CC_TYPE_SHELL, CcShell))
+#define CC_IS_SHELL(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), CC_TYPE_SHELL))
+#define CC_SHELL_GET_IFACE(obj) (G_TYPE_INSTANCE_GET_INTERFACE ((obj), CC_TYPE_SHELL, CcShellInterface))
+
+#define CC_SHELL_PANEL_EXTENSION_POINT "control-center-1"
+
+typedef struct _CcShell CcShell;
+typedef struct _CcShellInterface CcShellInterface;
+
+/* cc-panel.h requires CcShell, so make sure they are defined first */
+#include "cc-panel.h"
+
+/**
+ * CcShellInterface:
+ * @set_active_panel_from_id: virtual function to set the active panel from an
+ * id string
+ *
+ */
+struct _CcShellInterface
+{
+ GTypeInterface g_iface;
+
+ /* methods */
+ gboolean (*set_active_panel_from_id) (CcShell *shell,
+ const gchar *id,
+ GVariant *parameters,
+ GError **error);
+ GtkWidget * (*get_toplevel) (CcShell *shell);
+ void (*embed_widget_in_header) (CcShell *shell,
+ GtkWidget *widget,
+ GtkPositionType position);
+};
+
+GType cc_shell_get_type (void) G_GNUC_CONST;
+
+CcPanel * cc_shell_get_active_panel (CcShell *shell);
+void cc_shell_set_active_panel (CcShell *shell,
+ CcPanel *panel);
+gboolean cc_shell_set_active_panel_from_id (CcShell *shell,
+ const gchar *id,
+ GVariant *parameters,
+ GError **error);
+GtkWidget * cc_shell_get_toplevel (CcShell *shell);
+
+void cc_shell_embed_widget_in_header (CcShell *shell,
+ GtkWidget *widget,
+ GtkPositionType position);
+
+G_END_DECLS
diff --git a/shell/cc-window.c b/shell/cc-window.c
new file mode 100644
index 0000000..400c391
--- /dev/null
+++ b/shell/cc-window.c
@@ -0,0 +1,948 @@
+/*
+ * Copyright (c) 2009, 2010 Intel, Inc.
+ * Copyright (c) 2010 Red Hat, Inc.
+ * Copyright (c) 2016 Endless, Inc.
+ *
+ * The Control Center 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.
+ *
+ * The Control Center 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 the Control Center; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ *
+ * Author: Thomas Wood <thos@gnome.org>
+ */
+
+#define G_LOG_DOMAIN "cc-window"
+
+#include <config.h>
+
+#include "cc-debug.h"
+#include "cc-window.h"
+
+#include <glib/gi18n.h>
+#include <gio/gio.h>
+#include <gio/gdesktopappinfo.h>
+#include <gtk/gtk.h>
+#include <gdk/gdkkeysyms.h>
+#include <gdk/gdkx.h>
+#include <handy.h>
+#include <string.h>
+#include <time.h>
+
+#include "cc-application.h"
+#include "cc-panel.h"
+#include "cc-shell.h"
+#include "cc-shell-model.h"
+#include "cc-panel-list.h"
+#include "cc-panel-loader.h"
+#include "cc-util.h"
+
+#define MOUSE_BACK_BUTTON 8
+
+#define DEFAULT_WINDOW_ICON_NAME "gnome-control-center"
+
+struct _CcWindow
+{
+ GtkApplicationWindow parent;
+
+ GtkRevealer *back_revealer;
+ GtkMessageDialog *development_warning_dialog;
+ GtkHeaderBar *header;
+ HdyLeaflet *header_box;
+ GtkSizeGroup *header_sizegroup;
+ HdyLeaflet *main_leaflet;
+ GtkHeaderBar *panel_headerbar;
+ CcPanelList *panel_list;
+ GtkButton *previous_button;
+ GtkSearchBar *search_bar;
+ GtkToggleButton *search_button;
+ GtkSearchEntry *search_entry;
+ GtkBox *sidebar_box;
+ GtkStack *stack;
+ GtkBox *top_left_box;
+ GtkBox *top_right_box;
+
+ GtkWidget *current_panel;
+ char *current_panel_id;
+ GQueue *previous_panels;
+
+ GPtrArray *custom_widgets;
+
+ CcShellModel *store;
+
+ CcPanel *active_panel;
+ GSettings *settings;
+
+ CcPanelListView previous_list_view;
+};
+
+static void cc_shell_iface_init (CcShellInterface *iface);
+
+G_DEFINE_TYPE_WITH_CODE (CcWindow, cc_window, GTK_TYPE_APPLICATION_WINDOW,
+ G_IMPLEMENT_INTERFACE (CC_TYPE_SHELL, cc_shell_iface_init))
+
+enum
+{
+ PROP_0,
+ PROP_ACTIVE_PANEL,
+ PROP_MODEL
+};
+
+/* Auxiliary methods */
+static gboolean
+in_flatpak_sandbox (void)
+{
+ return g_strcmp0 (PROFILE, "development") == 0;
+}
+
+static void
+remove_all_custom_widgets (CcWindow *self)
+{
+ GtkWidget *parent;
+ GtkWidget *widget;
+ guint i;
+
+ CC_ENTRY;
+
+ /* remove from the header */
+ for (i = 0; i < self->custom_widgets->len; i++)
+ {
+ widget = g_ptr_array_index (self->custom_widgets, i);
+ parent = gtk_widget_get_parent (widget);
+
+ g_assert (parent == GTK_WIDGET (self->top_right_box) || parent == GTK_WIDGET (self->top_left_box));
+ gtk_container_remove (GTK_CONTAINER (parent), widget);
+ }
+ g_ptr_array_set_size (self->custom_widgets, 0);
+
+ CC_EXIT;
+}
+
+static void
+show_panel (CcWindow *self)
+{
+ hdy_leaflet_set_visible_child (self->main_leaflet, GTK_WIDGET (self->stack));
+ hdy_leaflet_set_visible_child (self->header_box, GTK_WIDGET (self->panel_headerbar));
+}
+
+static void
+show_sidebar (CcWindow *self)
+{
+ hdy_leaflet_set_visible_child (self->main_leaflet, GTK_WIDGET (self->sidebar_box));
+ hdy_leaflet_set_visible_child (self->header_box, GTK_WIDGET (self->header));
+}
+
+static void
+on_sidebar_activated_cb (CcWindow *self)
+{
+ show_panel (self);
+}
+
+static gboolean
+activate_panel (CcWindow *self,
+ const gchar *id,
+ GVariant *parameters,
+ const gchar *name,
+ GIcon *gicon,
+ CcPanelVisibility visibility)
+{
+ g_autoptr(GTimer) timer = NULL;
+ GtkWidget *sidebar_widget;
+ GtkWidget *title_widget;
+ gdouble ellapsed_time;
+
+ CC_ENTRY;
+
+ if (!id)
+ CC_RETURN (FALSE);
+
+ if (visibility == CC_PANEL_HIDDEN)
+ CC_RETURN (FALSE);
+
+ /* clear any custom widgets */
+ remove_all_custom_widgets (self);
+
+ timer = g_timer_new ();
+
+ g_settings_set_string (self->settings, "last-panel", id);
+
+ /* Begin the profile */
+ g_timer_start (timer);
+
+ if (self->current_panel)
+ g_signal_handlers_disconnect_by_data (self->current_panel, self);
+ self->current_panel = GTK_WIDGET (cc_panel_loader_load_by_name (CC_SHELL (self), id, parameters));
+ cc_shell_set_active_panel (CC_SHELL (self), CC_PANEL (self->current_panel));
+ gtk_widget_show (self->current_panel);
+
+ gtk_stack_add_named (self->stack, self->current_panel, id);
+
+ /* switch to the new panel */
+ gtk_widget_show (self->current_panel);
+ gtk_stack_set_visible_child_name (self->stack, id);
+
+ /* set the title of the window */
+ gtk_window_set_role (GTK_WINDOW (self), id);
+ gtk_header_bar_set_title (self->panel_headerbar, name);
+
+ title_widget = cc_panel_get_title_widget (CC_PANEL (self->current_panel));
+ gtk_header_bar_set_custom_title (self->panel_headerbar, title_widget);
+
+ sidebar_widget = cc_panel_get_sidebar_widget (CC_PANEL (self->current_panel));
+ cc_panel_list_add_sidebar_widget (self->panel_list, sidebar_widget);
+ /* Ensure we show the panel when the leaflet is folded and a sidebar widget's
+ * row is activated.
+ */
+ g_signal_connect_object (self->current_panel, "sidebar-activated", G_CALLBACK (on_sidebar_activated_cb), self, G_CONNECT_SWAPPED);
+
+ /* Finish profiling */
+ g_timer_stop (timer);
+
+ ellapsed_time = g_timer_elapsed (timer, NULL);
+
+ g_debug ("Time to open panel '%s': %lfs", name, ellapsed_time);
+
+ CC_RETURN (TRUE);
+}
+
+static void
+add_current_panel_to_history (CcWindow *self,
+ const char *start_id)
+{
+ g_return_if_fail (start_id != NULL);
+
+ if (!self->current_panel_id || g_strcmp0 (self->current_panel_id, start_id) == 0)
+ return;
+
+ g_queue_push_head (self->previous_panels, g_strdup (self->current_panel_id));
+ g_debug ("Added '%s' to the previous panels", self->current_panel_id);
+}
+
+static gboolean
+find_iter_for_panel_id (CcWindow *self,
+ const gchar *panel_id,
+ GtkTreeIter *out_iter)
+{
+ GtkTreeIter iter;
+ gboolean valid;
+
+ valid = gtk_tree_model_get_iter_first (GTK_TREE_MODEL (self->store), &iter);
+
+ while (valid)
+ {
+ g_autofree gchar *id = NULL;
+
+ gtk_tree_model_get (GTK_TREE_MODEL (self->store),
+ &iter,
+ COL_ID, &id,
+ -1);
+
+ if (g_strcmp0 (id, panel_id) == 0)
+ break;
+
+ valid = gtk_tree_model_iter_next (GTK_TREE_MODEL (self->store), &iter);
+ }
+
+ g_assert (out_iter != NULL);
+ *out_iter = iter;
+
+ return valid;
+}
+
+static void
+update_list_title (CcWindow *self)
+{
+ CcPanelListView view;
+ GtkTreeIter iter;
+ g_autofree gchar *title = NULL;
+
+ CC_ENTRY;
+
+ view = cc_panel_list_get_view (self->panel_list);
+ title = NULL;
+
+ switch (view)
+ {
+ case CC_PANEL_LIST_PRIVACY:
+ title = g_strdup (_("Privacy"));
+ break;
+
+ case CC_PANEL_LIST_MAIN:
+ title = g_strdup (_("Settings"));
+ break;
+
+ case CC_PANEL_LIST_WIDGET:
+ find_iter_for_panel_id (self, self->current_panel_id, &iter);
+ gtk_tree_model_get (GTK_TREE_MODEL (self->store),
+ &iter,
+ COL_NAME, &title,
+ -1);
+ break;
+
+ case CC_PANEL_LIST_SEARCH:
+ title = NULL;
+ break;
+ }
+
+ if (title)
+ gtk_header_bar_set_title (self->header, title);
+
+ CC_EXIT;
+}
+
+static void
+on_row_changed_cb (CcWindow *self,
+ GtkTreePath *path,
+ GtkTreeIter *iter,
+ GtkTreeModel *model)
+{
+ g_autofree gchar *id = NULL;
+ CcPanelVisibility visibility;
+
+ gtk_tree_model_get (model, iter,
+ COL_ID, &id,
+ COL_VISIBILITY, &visibility,
+ -1);
+
+ cc_panel_list_set_panel_visibility (self->panel_list, id, visibility);
+}
+
+static void
+setup_model (CcWindow *self)
+{
+ GtkTreeModel *model;
+ GtkTreeIter iter;
+ gboolean valid;
+
+ /* CcApplication must have a valid model at this point */
+ g_assert (self->store != NULL);
+
+ model = GTK_TREE_MODEL (self->store);
+
+ cc_panel_loader_fill_model (self->store);
+
+ /* Create a row for each panel */
+ valid = gtk_tree_model_get_iter_first (model, &iter);
+
+ while (valid)
+ {
+ CcPanelCategory category;
+ g_autoptr(GIcon) icon = NULL;
+ g_autofree gchar *name = NULL;
+ g_autofree gchar *description = NULL;
+ g_autofree gchar *id = NULL;
+ g_auto(GStrv) keywords = NULL;
+ CcPanelVisibility visibility;
+ gboolean has_sidebar;
+ const gchar *icon_name = NULL;
+
+ gtk_tree_model_get (model, &iter,
+ COL_CATEGORY, &category,
+ COL_DESCRIPTION, &description,
+ COL_GICON, &icon,
+ COL_ID, &id,
+ COL_NAME, &name,
+ COL_KEYWORDS, &keywords,
+ COL_VISIBILITY, &visibility,
+ COL_HAS_SIDEBAR, &has_sidebar,
+ -1);
+
+ if (G_IS_THEMED_ICON (icon))
+ icon_name = g_themed_icon_get_names (G_THEMED_ICON (icon))[0];
+
+ cc_panel_list_add_panel (self->panel_list,
+ category,
+ id,
+ name,
+ description,
+ keywords,
+ icon_name,
+ visibility,
+ has_sidebar);
+
+ valid = gtk_tree_model_iter_next (model, &iter);
+ }
+
+ /* React to visibility changes */
+ g_signal_connect_object (model, "row-changed", G_CALLBACK (on_row_changed_cb), self, G_CONNECT_SWAPPED);
+}
+
+static void
+update_headerbar_buttons (CcWindow *self)
+{
+ gboolean is_main_view;
+
+ CC_ENTRY;
+
+ is_main_view = cc_panel_list_get_view (self->panel_list) == CC_PANEL_LIST_MAIN;
+
+ gtk_widget_set_visible (GTK_WIDGET (self->previous_button), !is_main_view);
+ gtk_widget_set_visible (GTK_WIDGET (self->search_button), is_main_view);
+
+ update_list_title (self);
+
+ CC_EXIT;
+}
+
+static gboolean
+set_active_panel_from_id (CcWindow *self,
+ const gchar *start_id,
+ GVariant *parameters,
+ gboolean add_to_history,
+ gboolean force_moving_to_the_panel,
+ GError **error)
+{
+ g_autoptr(GIcon) gicon = NULL;
+ g_autofree gchar *name = NULL;
+ CcPanelVisibility visibility;
+ GtkTreeIter iter;
+ GtkWidget *old_panel;
+ CcPanelListView view;
+ gboolean activated;
+ gboolean found;
+
+ CC_ENTRY;
+
+ view = cc_panel_list_get_view (self->panel_list);
+
+ /* When loading the same panel again, just set its parameters */
+ if (g_strcmp0 (self->current_panel_id, start_id) == 0)
+ {
+ g_object_set (G_OBJECT (self->current_panel), "parameters", parameters, NULL);
+ if (force_moving_to_the_panel || self->previous_list_view == view)
+ show_panel (self);
+ self->previous_list_view = view;
+ CC_RETURN (TRUE);
+ }
+
+ found = find_iter_for_panel_id (self, start_id, &iter);
+ if (!found)
+ {
+ g_warning ("Could not find settings panel \"%s\"", start_id);
+ CC_RETURN (TRUE);
+ }
+
+ old_panel = self->current_panel;
+
+ gtk_tree_model_get (GTK_TREE_MODEL (self->store),
+ &iter,
+ COL_NAME, &name,
+ COL_GICON, &gicon,
+ COL_VISIBILITY, &visibility,
+ -1);
+
+ /* Activate the panel */
+ activated = activate_panel (self, start_id, parameters, name, gicon, visibility);
+
+ /* Failed to activate the panel for some reason, let's keep the old
+ * panel around instead */
+ if (!activated)
+ {
+ g_debug ("Failed to activate panel");
+ CC_RETURN (TRUE);
+ }
+
+ if (add_to_history)
+ add_current_panel_to_history (self, start_id);
+
+ if (force_moving_to_the_panel)
+ show_panel (self);
+
+ g_free (self->current_panel_id);
+ self->current_panel_id = g_strdup (start_id);
+
+ CC_TRACE_MSG ("Current panel id: %s", start_id);
+
+ if (old_panel)
+ gtk_container_remove (GTK_CONTAINER (self->stack), old_panel);
+
+ cc_panel_list_set_active_panel (self->panel_list, start_id);
+
+ update_headerbar_buttons (self);
+
+ CC_RETURN (TRUE);
+}
+
+static void
+set_active_panel (CcWindow *self,
+ CcPanel *panel)
+{
+ g_return_if_fail (CC_IS_SHELL (self));
+ g_return_if_fail (panel == NULL || CC_IS_PANEL (panel));
+
+ if (panel != self->active_panel)
+ {
+ /* remove the old panel */
+ g_clear_object (&self->active_panel);
+
+ /* set the new panel */
+ if (panel)
+ self->active_panel = g_object_ref (panel);
+
+ g_object_notify (G_OBJECT (self), "active-panel");
+ }
+}
+
+static void
+switch_to_previous_panel (CcWindow *self)
+{
+ g_autofree gchar *previous_panel_id = NULL;
+
+ CC_ENTRY;
+
+ if (g_queue_get_length (self->previous_panels) == 0)
+ CC_RETURN ();
+
+ previous_panel_id = g_queue_pop_head (self->previous_panels);
+
+ g_debug ("Going to previous panel (%s)", previous_panel_id);
+
+ set_active_panel_from_id (self, previous_panel_id, NULL, FALSE, FALSE, NULL);
+
+ CC_EXIT;
+}
+
+/* Callbacks */
+static void
+on_main_leaflet_folded_changed_cb (CcWindow *self)
+{
+ GtkSelectionMode selection_mode;
+
+ g_assert (CC_IS_WINDOW (self));
+
+ selection_mode = GTK_SELECTION_SINGLE;
+
+ if (hdy_leaflet_get_folded (self->main_leaflet))
+ selection_mode = GTK_SELECTION_NONE;
+
+ cc_panel_list_set_selection_mode (self->panel_list, selection_mode);
+}
+
+static void
+show_panel_cb (CcWindow *self,
+ const gchar *panel_id)
+{
+ if (!panel_id)
+ return;
+
+ set_active_panel_from_id (self, panel_id, NULL, TRUE, FALSE, NULL);
+}
+
+static void
+search_entry_activate_cb (CcWindow *self)
+{
+ gboolean changed;
+
+ changed = cc_panel_list_activate (self->panel_list);
+
+ gtk_search_bar_set_search_mode (self->search_bar, !changed);
+}
+
+static void
+back_button_clicked_cb (CcWindow *self)
+{
+ show_sidebar (self);
+}
+
+static void
+previous_button_clicked_cb (CcWindow *self)
+{
+ g_debug ("Num previous panels? %d", g_queue_get_length (self->previous_panels));
+
+ /* When in search, simply unsed the search mode */
+ if (gtk_search_bar_get_search_mode (self->search_bar))
+ gtk_search_bar_set_search_mode (self->search_bar, FALSE);
+ else
+ cc_panel_list_go_previous (self->panel_list);
+
+ update_headerbar_buttons (self);
+}
+
+static void
+gdk_window_set_cb (CcWindow *self)
+{
+ GdkWindow *window;
+ g_autofree gchar *str = NULL;
+
+ if (!GDK_IS_X11_DISPLAY (gdk_display_get_default ()))
+ return;
+
+ window = gtk_widget_get_window (GTK_WIDGET (self));
+
+ if (!window)
+ return;
+
+ str = g_strdup_printf ("%u", (guint) GDK_WINDOW_XID (window));
+ g_setenv ("GNOME_CONTROL_CENTER_XID", str, TRUE);
+}
+
+static gboolean
+window_map_event_cb (CcWindow *self)
+{
+ /* If focus ends up in a category icon view one of the items is
+ * immediately selected which looks odd when we are starting up, so
+ * we explicitly unset the focus here. */
+ gtk_window_set_focus (GTK_WINDOW (self), NULL);
+ return GDK_EVENT_PROPAGATE;
+}
+
+static gboolean
+window_key_press_event_cb (CcWindow *self,
+ GdkEventKey *event)
+{
+ GdkModifierType state;
+ CcPanelListView view;
+ GdkKeymap *keymap;
+ gboolean retval;
+ gboolean is_rtl;
+
+ retval = GDK_EVENT_PROPAGATE;
+ state = event->state;
+ keymap = gdk_keymap_get_for_display (gtk_widget_get_display (GTK_WIDGET (self)));
+ gdk_keymap_add_virtual_modifiers (keymap, &state);
+
+ state = state & gtk_accelerator_get_default_mod_mask ();
+ is_rtl = gtk_widget_get_direction (GTK_WIDGET (self)) == GTK_TEXT_DIR_RTL;
+ view = cc_panel_list_get_view (self->panel_list);
+
+ /* The search only happens when we're in the MAIN view */
+ if (view == CC_PANEL_LIST_MAIN &&
+ gtk_search_bar_handle_event (self->search_bar, (GdkEvent*) event) == GDK_EVENT_STOP)
+ {
+ return GDK_EVENT_STOP;
+ }
+
+ if (state == GDK_CONTROL_MASK)
+ {
+ switch (event->keyval)
+ {
+ case GDK_KEY_s:
+ case GDK_KEY_S:
+ case GDK_KEY_f:
+ case GDK_KEY_F:
+ /* The search only happens when we're in the MAIN view */
+ if (view != CC_PANEL_LIST_MAIN &&
+ view != CC_PANEL_LIST_SEARCH)
+ {
+ break;
+ }
+
+ retval = !gtk_search_bar_get_search_mode (self->search_bar);
+ gtk_search_bar_set_search_mode (self->search_bar, retval);
+ if (retval)
+ gtk_widget_grab_focus (GTK_WIDGET (self->search_entry));
+ retval = GDK_EVENT_STOP;
+ break;
+ case GDK_KEY_Q:
+ case GDK_KEY_q:
+ case GDK_KEY_W:
+ case GDK_KEY_w:
+ gtk_widget_destroy (GTK_WIDGET (self));
+ retval = GDK_EVENT_STOP;
+ break;
+ }
+ }
+ else if ((!is_rtl && state == GDK_MOD1_MASK && event->keyval == GDK_KEY_Left) ||
+ (is_rtl && state == GDK_MOD1_MASK && event->keyval == GDK_KEY_Right) ||
+ event->keyval == GDK_KEY_Back)
+ {
+ g_debug ("Going to previous panel");
+ switch_to_previous_panel (self);
+ retval = GDK_EVENT_STOP;
+ }
+
+ return retval;
+}
+
+static void
+on_development_warning_dialog_responded_cb (CcWindow *self)
+{
+ g_debug ("Disabling development build warning dialog");
+ g_settings_set_boolean (self->settings, "show-development-warning", FALSE);
+
+ gtk_widget_hide (GTK_WIDGET (self->development_warning_dialog));
+}
+
+/* CcShell implementation */
+static gboolean
+cc_window_set_active_panel_from_id (CcShell *shell,
+ const gchar *start_id,
+ GVariant *parameters,
+ GError **error)
+{
+ return set_active_panel_from_id (CC_WINDOW (shell), start_id, parameters, TRUE, TRUE, error);
+}
+
+static void
+cc_window_embed_widget_in_header (CcShell *shell,
+ GtkWidget *widget,
+ GtkPositionType position)
+{
+ CcWindow *self = CC_WINDOW (shell);
+
+ CC_ENTRY;
+
+ /* add to header */
+ switch (position)
+ {
+ case GTK_POS_RIGHT:
+ gtk_container_add (GTK_CONTAINER (self->top_right_box), widget);
+ break;
+
+ case GTK_POS_LEFT:
+ gtk_container_add (GTK_CONTAINER (self->top_left_box), widget);
+ break;
+
+ case GTK_POS_TOP:
+ case GTK_POS_BOTTOM:
+ default:
+ g_warning ("Invalid position passed");
+ return;
+ }
+
+ g_ptr_array_add (self->custom_widgets, g_object_ref (widget));
+
+ gtk_size_group_add_widget (self->header_sizegroup, widget);
+
+ CC_EXIT;
+}
+
+static GtkWidget *
+cc_window_get_toplevel (CcShell *self)
+{
+ return GTK_WIDGET (self);
+}
+
+static void
+cc_shell_iface_init (CcShellInterface *iface)
+{
+ iface->set_active_panel_from_id = cc_window_set_active_panel_from_id;
+ iface->embed_widget_in_header = cc_window_embed_widget_in_header;
+ iface->get_toplevel = cc_window_get_toplevel;
+}
+
+/* GtkWidget overrides */
+static void
+cc_window_map (GtkWidget *widget)
+{
+ CcWindow *self = (CcWindow *) widget;
+
+ GTK_WIDGET_CLASS (cc_window_parent_class)->map (widget);
+
+ /* Show a warning for Flatpak builds */
+ if (in_flatpak_sandbox () && g_settings_get_boolean (self->settings, "show-development-warning"))
+ gtk_window_present (GTK_WINDOW (self->development_warning_dialog));
+}
+
+/* GObject Implementation */
+static void
+cc_window_get_property (GObject *object,
+ guint property_id,
+ GValue *value,
+ GParamSpec *pspec)
+{
+ CcWindow *self = CC_WINDOW (object);
+
+ switch (property_id)
+ {
+ case PROP_ACTIVE_PANEL:
+ g_value_set_object (value, self->active_panel);
+ break;
+
+ case PROP_MODEL:
+ g_value_set_object (value, self->store);
+ break;
+
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+ }
+}
+
+static void
+cc_window_set_property (GObject *object,
+ guint property_id,
+ const GValue *value,
+ GParamSpec *pspec)
+{
+ CcWindow *self = CC_WINDOW (object);
+
+ switch (property_id)
+ {
+ case PROP_ACTIVE_PANEL:
+ set_active_panel (self, g_value_get_object (value));
+ break;
+
+ case PROP_MODEL:
+ g_assert (self->store == NULL);
+ self->store = g_value_dup_object (value);
+ break;
+
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+ }
+}
+
+static void
+cc_window_constructed (GObject *object)
+{
+ CcWindow *self = CC_WINDOW (object);
+ g_autofree char *id = NULL;
+
+ /* Add the panels */
+ setup_model (self);
+
+ /* After everything is loaded, select the last used panel, if any,
+ * or the first visible panel */
+ id = g_settings_get_string (self->settings, "last-panel");
+ if (id != NULL && cc_shell_model_has_panel (self->store, id))
+ cc_panel_list_set_active_panel (self->panel_list, id);
+ else
+ cc_panel_list_activate (self->panel_list);
+
+ g_signal_connect_swapped (self->panel_list,
+ "notify::view",
+ G_CALLBACK (update_headerbar_buttons),
+ self);
+
+ update_headerbar_buttons (self);
+ show_sidebar (self);
+
+ G_OBJECT_CLASS (cc_window_parent_class)->constructed (object);
+}
+
+static void
+cc_window_dispose (GObject *object)
+{
+ CcWindow *self = CC_WINDOW (object);
+
+ g_clear_pointer (&self->current_panel_id, g_free);
+ g_clear_pointer (&self->custom_widgets, g_ptr_array_unref);
+ g_clear_object (&self->store);
+ g_clear_object (&self->active_panel);
+
+ G_OBJECT_CLASS (cc_window_parent_class)->dispose (object);
+}
+
+static void
+cc_window_finalize (GObject *object)
+{
+ CcWindow *self = CC_WINDOW (object);
+
+ if (self->previous_panels)
+ {
+ g_queue_free_full (self->previous_panels, g_free);
+ self->previous_panels = NULL;
+ }
+
+ g_clear_object (&self->settings);
+
+ G_OBJECT_CLASS (cc_window_parent_class)->finalize (object);
+}
+
+static void
+cc_window_class_init (CcWindowClass *klass)
+{
+ GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);
+ GObjectClass *object_class = G_OBJECT_CLASS (klass);
+
+ object_class->get_property = cc_window_get_property;
+ object_class->set_property = cc_window_set_property;
+ object_class->constructed = cc_window_constructed;
+ object_class->dispose = cc_window_dispose;
+ object_class->finalize = cc_window_finalize;
+
+ widget_class->map = cc_window_map;
+
+ g_object_class_override_property (object_class, PROP_ACTIVE_PANEL, "active-panel");
+
+ g_object_class_install_property (object_class,
+ PROP_MODEL,
+ g_param_spec_object ("model",
+ "Model",
+ "The CcShellModel of this application",
+ CC_TYPE_SHELL_MODEL,
+ G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS));
+
+ gtk_widget_class_set_template_from_resource (widget_class, "/org/gnome/ControlCenter/gtk/cc-window.ui");
+
+ gtk_widget_class_bind_template_child (widget_class, CcWindow, back_revealer);
+ gtk_widget_class_bind_template_child (widget_class, CcWindow, development_warning_dialog);
+ gtk_widget_class_bind_template_child (widget_class, CcWindow, header);
+ gtk_widget_class_bind_template_child (widget_class, CcWindow, header_box);
+ gtk_widget_class_bind_template_child (widget_class, CcWindow, header_sizegroup);
+ gtk_widget_class_bind_template_child (widget_class, CcWindow, main_leaflet);
+ gtk_widget_class_bind_template_child (widget_class, CcWindow, panel_headerbar);
+ gtk_widget_class_bind_template_child (widget_class, CcWindow, panel_list);
+ gtk_widget_class_bind_template_child (widget_class, CcWindow, previous_button);
+ gtk_widget_class_bind_template_child (widget_class, CcWindow, search_bar);
+ gtk_widget_class_bind_template_child (widget_class, CcWindow, search_button);
+ gtk_widget_class_bind_template_child (widget_class, CcWindow, search_entry);
+ gtk_widget_class_bind_template_child (widget_class, CcWindow, sidebar_box);
+ gtk_widget_class_bind_template_child (widget_class, CcWindow, stack);
+ gtk_widget_class_bind_template_child (widget_class, CcWindow, top_left_box);
+ gtk_widget_class_bind_template_child (widget_class, CcWindow, top_right_box);
+
+ gtk_widget_class_bind_template_callback (widget_class, back_button_clicked_cb);
+ gtk_widget_class_bind_template_callback (widget_class, gdk_window_set_cb);
+ gtk_widget_class_bind_template_callback (widget_class, on_main_leaflet_folded_changed_cb);
+ gtk_widget_class_bind_template_callback (widget_class, on_development_warning_dialog_responded_cb);
+ gtk_widget_class_bind_template_callback (widget_class, previous_button_clicked_cb);
+ gtk_widget_class_bind_template_callback (widget_class, search_entry_activate_cb);
+ gtk_widget_class_bind_template_callback (widget_class, show_panel_cb);
+ gtk_widget_class_bind_template_callback (widget_class, update_list_title);
+ gtk_widget_class_bind_template_callback (widget_class, window_key_press_event_cb);
+ gtk_widget_class_bind_template_callback (widget_class, window_map_event_cb);
+
+ g_type_ensure (CC_TYPE_PANEL_LIST);
+}
+
+static void
+cc_window_init (CcWindow *self)
+{
+ gtk_widget_init_template (GTK_WIDGET (self));
+
+ gtk_widget_add_events (GTK_WIDGET (self), GDK_BUTTON_RELEASE_MASK);
+
+ self->settings = g_settings_new ("org.gnome.ControlCenter");
+ self->custom_widgets = g_ptr_array_new_with_free_func ((GDestroyNotify) g_object_unref);
+ self->previous_panels = g_queue_new ();
+ self->previous_list_view = cc_panel_list_get_view (self->panel_list);
+
+ /* Add a custom CSS class on development builds */
+ if (in_flatpak_sandbox ())
+ gtk_style_context_add_class (gtk_widget_get_style_context (GTK_WIDGET (self)), "devel");
+}
+
+CcWindow *
+cc_window_new (GtkApplication *application,
+ CcShellModel *model)
+{
+ g_return_val_if_fail (GTK_IS_APPLICATION (application), NULL);
+
+ return g_object_new (CC_TYPE_WINDOW,
+ "application", application,
+ "resizable", TRUE,
+ "title", _("Settings"),
+ "icon-name", DEFAULT_WINDOW_ICON_NAME,
+ "window-position", GTK_WIN_POS_CENTER,
+ "show-menubar", FALSE,
+ "model", model,
+ NULL);
+}
+
+void
+cc_window_set_search_item (CcWindow *center,
+ const char *search)
+{
+ gtk_search_bar_set_search_mode (center->search_bar, TRUE);
+ gtk_entry_set_text (GTK_ENTRY (center->search_entry), search);
+ gtk_editable_set_position (GTK_EDITABLE (center->search_entry), -1);
+}
diff --git a/shell/cc-window.h b/shell/cc-window.h
new file mode 100644
index 0000000..5f74c36
--- /dev/null
+++ b/shell/cc-window.h
@@ -0,0 +1,39 @@
+/*
+ * Copyright (c) 2010 Intel, Inc.
+ *
+ * The Control Center 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.
+ *
+ * The Control Center 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 the Control Center; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ *
+ * Author: Thomas Wood <thos@gnome.org>
+ */
+
+#pragma once
+
+#include <glib-object.h>
+#include "cc-shell.h"
+#include "cc-shell-model.h"
+
+G_BEGIN_DECLS
+
+#define CC_TYPE_WINDOW (cc_window_get_type ())
+
+G_DECLARE_FINAL_TYPE (CcWindow, cc_window, CC, WINDOW, GtkApplicationWindow)
+
+CcWindow *cc_window_new (GtkApplication *application,
+ CcShellModel *model);
+
+void cc_window_set_search_item (CcWindow *center,
+ const char *search);
+
+G_END_DECLS
diff --git a/shell/cc-window.ui b/shell/cc-window.ui
new file mode 100644
index 0000000..24f37de
--- /dev/null
+++ b/shell/cc-window.ui
@@ -0,0 +1,336 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<interface>
+ <requires lib="gtk+" version="3.20"/>
+ <template class="CcWindow" parent="GtkApplicationWindow">
+ <property name="can_focus">False</property>
+ <property name="window_position">center</property>
+ <property name="default-width">980</property>
+ <property name="default-height">640</property>
+ <signal name="notify::window" handler="gdk_window_set_cb" object="CcWindow" swapped="yes" />
+ <signal name="map-event" handler="window_map_event_cb" object="CcWindow" swapped="yes" />
+ <signal name="key-press-event" handler="window_key_press_event_cb" object="CcWindow" swapped="yes" after="yes" />
+ <child>
+ <object class="HdyLeaflet" id="main_leaflet">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="transition-type">slide</property>
+ <signal name="notify::folded" handler="on_main_leaflet_folded_changed_cb" object="CcWindow" swapped="yes" />
+ <child>
+ <object class="GtkBox" id="sidebar_box">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="hexpand">False</property>
+ <property name="orientation">vertical</property>
+ <child>
+ <object class="GtkSearchBar" id="search_bar">
+ <property name="visible">True</property>
+ <property name="search_mode_enabled" bind-source="search_button" bind-property="active" bind-flags="sync-create|bidirectional" />
+ <property name="app_paintable">True</property>
+ <property name="can_focus">False</property>
+ <child>
+ <object class="GtkSearchEntry" id="search_entry">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="width_chars">25</property>
+ <property name="input_hints">no-emoji</property>
+ <property name="max_width_chars">25</property>
+ <property name="primary_icon_name">edit-find-symbolic</property>
+ <property name="primary_icon_activatable">False</property>
+ <property name="primary_icon_sensitive">False</property>
+ <signal name="activate" handler="search_entry_activate_cb" object="CcWindow" swapped="yes" />
+ <signal name="notify::text" handler="update_list_title" object="CcWindow" swapped="yes" />
+ </object>
+ </child>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">True</property>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkScrolledWindow">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="vexpand">True</property>
+ <property name="width-request">200</property>
+ <property name="hscrollbar_policy">never</property>
+ <style>
+ <class name="view"/>
+ </style>
+ <child>
+ <object class="CcPanelList" id="panel_list">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="search-mode" bind-source="search_bar" bind-property="search-mode-enabled" bind-flags="bidirectional" />
+ <property name="search-query" bind-source="search_entry" bind-property="text" bind-flags="default" />
+ <signal name="show-panel" handler="show_panel_cb" object="CcWindow" swapped="yes" />
+ </object>
+ </child>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">True</property>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ </object>
+ </child>
+ <child>
+ <object class="GtkSeparator" id="panel_separator">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="orientation">vertical</property>
+ <style>
+ <class name="sidebar"/>
+ </style>
+ </object>
+ </child>
+ <child>
+ <object class="GtkStack" id="stack">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="hexpand">True</property>
+ <property name="vexpand">True</property>
+ <property name="transition_type">crossfade</property>
+ <property name="width_request">360</property>
+ <style>
+ <class name="background"/>
+ </style>
+ </object>
+ </child>
+ </object>
+ </child>
+ <child type="titlebar">
+ <object class="HdyTitleBar">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <child>
+ <object class="HdyLeaflet" id="header_box">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="orientation">horizontal</property>
+ <property name="mode-transition-duration" bind-source="main_leaflet" bind-property="mode-transition-duration" bind-flags="bidirectional|sync-create"/>
+ <property name="child-transition-duration" bind-source="main_leaflet" bind-property="child-transition-duration" bind-flags="bidirectional|sync-create"/>
+ <property name="transition-type" bind-source="main_leaflet" bind-property="transition-type" bind-flags="bidirectional|sync-create"/>
+ <child>
+ <object class="GtkHeaderBar" id="header">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="show-close-button">True</property>
+ <property name="title" translatable="yes">Settings</property>
+ <child>
+ <object class="GtkButton" id="previous_button">
+ <property name="can_focus">True</property>
+ <property name="receives_default">True</property>
+ <signal name="clicked" handler="previous_button_clicked_cb" object="CcWindow" swapped="yes" />
+ <child>
+ <object class="GtkImage">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="icon_name">go-previous-symbolic</property>
+ </object>
+ </child>
+ <child internal-child="accessible">
+ <object class="AtkObject" id="a11y-button1">
+ <property name="accessible-name" translatable="yes">All Settings</property>
+ </object>
+ </child>
+ <style>
+ <class name="image-button"/>
+ </style>
+ </object>
+ </child>
+ <child>
+ <object class="GtkToggleButton" id="search_button">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="receives_default">True</property>
+ <child internal-child="accessible">
+ <object class="AtkObject">
+ <property name="accessible-name" translatable="yes">Search</property>
+ </object>
+ </child>
+ <child>
+ <object class="GtkImage">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="icon_name">edit-find-symbolic</property>
+ </object>
+ </child>
+ <style>
+ <class name="image-button"/>
+ </style>
+ </object>
+ </child>
+ <child>
+ <object class="GtkMenuButton">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="receives_default">True</property>
+ <property name="menu_model">primary_menu</property>
+ <child internal-child="accessible">
+ <object class="AtkObject">
+ <property name="accessible-name" translatable="yes">Primary Menu</property>
+ </object>
+ </child>
+ <child>
+ <object class="GtkImage">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="icon_name">open-menu-symbolic</property>
+ </object>
+ </child>
+ <style>
+ <class name="image-button"/>
+ </style>
+ </object>
+ <packing>
+ <property name="pack_type">end</property>
+ </packing>
+ </child>
+ </object>
+ </child>
+ <child>
+ <object class="GtkSeparator" id="header_separator">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="orientation">vertical</property>
+ <style>
+ <class name="sidebar"/>
+ </style>
+ </object>
+ </child>
+ <child>
+ <object class="GtkHeaderBar" id="panel_headerbar">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="hexpand">True</property>
+ <property name="show_close_button">True</property>
+ <child>
+ <object class="GtkRevealer" id="back_revealer">
+ <property name="can_focus">False</property>
+ <property name="transition-type">crossfade</property>
+ <property name="transition-duration" bind-source="main_leaflet" bind-property="mode-transition-duration" bind-flags="bidirectional|sync-create"/>
+ <property name="visible" bind-source="header_box" bind-property="folded" bind-flags="sync-create"/>
+ <property name="reveal-child" bind-source="header_box" bind-property="folded" bind-flags="sync-create"/>
+ <child>
+ <object class="GtkButton" id="back">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="receives_default">False</property>
+ <property name="valign">center</property>
+ <property name="use-underline">True</property>
+ <signal name="clicked" handler="back_button_clicked_cb" object="CcWindow" swapped="yes" />
+ <style>
+ <class name="image-button"/>
+ </style>
+ <child internal-child="accessible">
+ <object class="AtkObject" id="a11y-back">
+ <property name="accessible-name" translatable="yes">Back</property>
+ </object>
+ </child>
+ <child>
+ <object class="GtkImage" id="back_image">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="icon_name">go-previous-symbolic</property>
+ <property name="icon_size">1</property>
+ </object>
+ </child>
+ </object>
+ </child>
+ </object>
+ </child>
+ <child>
+ <object class="GtkBox" id="top_left_box">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ </object>
+ </child>
+ <child>
+ <object class="GtkBox" id="top_right_box">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ </object>
+ <packing>
+ <property name="pack_type">end</property>
+ </packing>
+ </child>
+ </object>
+ </child>
+ </object>
+ </child>
+ </object>
+ </child>
+ </template>
+
+ <object class="HdyHeaderGroup">
+ <property name="decorate-all" bind-source="main_leaflet" bind-property="folded" bind-flags="sync-create"/>
+ <headerbars>
+ <headerbar name="header"/>
+ <headerbar name="panel_headerbar"/>
+ </headerbars>
+ </object>
+
+ <object class="GtkSizeGroup" id="header_sizegroup">
+ <property name="mode">vertical</property>
+ <widgets>
+ <widget name="search_button"/>
+ <widget name="previous_button"/>
+ </widgets>
+ </object>
+
+ <!-- Synchronize left header and sidebar -->
+ <object class="GtkSizeGroup">
+ <property name="mode">horizontal</property>
+ <widgets>
+ <widget name="header"/>
+ <widget name="sidebar_box"/>
+ </widgets>
+ </object>
+
+ <!-- Synchronize separators -->
+ <object class="GtkSizeGroup">
+ <property name="mode">horizontal</property>
+ <widgets>
+ <widget name="header_separator"/>
+ <widget name="panel_separator"/>
+ </widgets>
+ </object>
+
+ <!-- Synchronize right header and panel -->
+ <object class="GtkSizeGroup">
+ <property name="mode">horizontal</property>
+ <widgets>
+ <widget name="panel_headerbar"/>
+ <widget name="stack"/>
+ </widgets>
+ </object>
+
+ <!-- Warning dialog for development builds -->
+ <object class="GtkMessageDialog" id="development_warning_dialog">
+ <property name="message-type">warning</property>
+ <property name="transient-for">CcWindow</property>
+ <property name="resizable">false</property>
+ <property name="modal">true</property>
+ <property name="buttons">ok</property>
+ <property name="text" translatable="yes">Warning: Development Version</property>
+ <property name="secondary-text" translatable="yes">This version of Settings should only be used for development purposes. You may experience incorrect system behavior, data loss, and other unexpected issues. </property>
+ <signal name="response" handler="on_development_warning_dialog_responded_cb" object="CcWindow" swapped="yes" />
+ </object>
+
+ <menu id="primary_menu">
+ <section>
+ <item>
+ <attribute name="label" translatable="yes">Keyboard Shortcuts</attribute>
+ <attribute name="action">win.show-help-overlay</attribute>
+ </item>
+ <item>
+ <attribute name="label" translatable="yes">Help</attribute>
+ <attribute name="action">app.help</attribute>
+ </item>
+ </section>
+ </menu>
+
+</interface>
diff --git a/shell/completions/gnome-control-center.in b/shell/completions/gnome-control-center.in
new file mode 100644
index 0000000..51ce4fd
--- /dev/null
+++ b/shell/completions/gnome-control-center.in
@@ -0,0 +1,51 @@
+# gnome-control-center tab completion for bash.
+
+_gnome_control_center()
+{
+ local cur prev command_list i v
+
+ cur=${COMP_WORDS[COMP_CWORD]}
+ prev=${COMP_WORDS[COMP_CWORD-1]}
+
+ case "$prev" in
+ *)
+ if [ $prev = "gnome-control-center" ] ; then
+ command_list="--verbose --version"
+ command_list="$command_list @PANELS@"
+ elif [ $prev = "--verbose" ]; then
+ command_list="@PANELS@"
+ fi
+
+ for i in --version @PANELS@; do
+ if [ $i = $prev ]; then
+ case $i in
+ keyboard)
+ command_list="shortcuts typing"
+ ;;
+ network)
+ # FIXME
+ # The first 3 commands need an object path like
+ # /org/freedesktop/NetworkManager/Devices/1
+ command_list="connect-3g connect-8021x-wifi show-device connect-hidden-wifi create-wifi"
+ ;;
+ sound)
+ command_list="applications effects hardware input outputs"
+ ;;
+ *)
+ command_list=""
+ ;;
+ esac
+ fi
+ done
+ ;;
+ esac
+
+ for i in $command_list; do
+ if [ -z "${i/$cur*}" ]; then
+ COMPREPLY=( ${COMPREPLY[@]} $i )
+ fi
+ done
+}
+
+# load the completion
+complete -F _gnome_control_center gnome-control-center
diff --git a/shell/completions/meson.build b/shell/completions/meson.build
new file mode 100644
index 0000000..951d060
--- /dev/null
+++ b/shell/completions/meson.build
@@ -0,0 +1,12 @@
+completion_conf = configuration_data()
+completion_conf.set('PANELS', ' '.join(panels_list))
+
+desktop = 'gnome-control-center'
+
+desktop_in = configure_file(
+ input: desktop + '.in',
+ output: desktop,
+ configuration: completion_conf,
+ install: true,
+ install_dir: join_paths(control_center_datadir, 'bash-completion', 'completions')
+)
diff --git a/shell/gnome-control-center.desktop.in.in b/shell/gnome-control-center.desktop.in.in
new file mode 100644
index 0000000..e876299
--- /dev/null
+++ b/shell/gnome-control-center.desktop.in.in
@@ -0,0 +1,16 @@
+[Desktop Entry]
+Name=Settings
+# Translators: Do NOT translate or transliterate this text (this is an icon file name)!
+Icon=org.gnome.Settings
+Exec=gnome-control-center
+Terminal=false
+Type=Application
+StartupNotify=true
+Categories=GNOME;GTK;Settings;
+OnlyShowIn=GNOME;Unity;
+X-GNOME-Bugzilla-Bugzilla=GNOME
+X-GNOME-Bugzilla-Product=gnome-control-center
+X-GNOME-Bugzilla-Component=shell
+X-GNOME-Bugzilla-Version=@VERSION@
+# Translators: Search terms to find this application. Do NOT translate or localize the semicolons! The list MUST also end with a semicolon!
+Keywords=Preferences;Settings;
diff --git a/shell/gnome-control-center.gresource.xml b/shell/gnome-control-center.gresource.xml
new file mode 100644
index 0000000..9be077e
--- /dev/null
+++ b/shell/gnome-control-center.gresource.xml
@@ -0,0 +1,9 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<gresources>
+ <gresource prefix="/org/gnome/ControlCenter/gtk">
+ <file preprocess="xml-stripblanks">cc-panel-list.ui</file>
+ <file preprocess="xml-stripblanks">cc-window.ui</file>
+ <file preprocess="xml-stripblanks">help-overlay.ui</file>
+ <file>style.css</file>
+ </gresource>
+</gresources>
diff --git a/shell/help-overlay.ui b/shell/help-overlay.ui
new file mode 100644
index 0000000..02cb679
--- /dev/null
+++ b/shell/help-overlay.ui
@@ -0,0 +1,61 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<interface>
+ <!-- interface-requires gtk+ 3.17 -->
+
+ <object class="GtkShortcutsWindow" id="help_overlay">
+ <property name="modal">1</property>
+ <child>
+ <object class="GtkShortcutsSection">
+ <property name="visible">1</property>
+ <property name="section-name">shortcuts</property>
+ <property name="max-height">10</property>
+ <child>
+ <object class="GtkShortcutsGroup">
+ <property name="visible">1</property>
+ <property name="title" translatable="yes" context="shortcut window">General</property>
+ <child>
+ <object class="GtkShortcutsShortcut">
+ <property name="visible">1</property>
+ <property name="accelerator">&lt;ctrl&gt;Q</property>
+ <property name="title" translatable="yes" context="shortcut window">Quit</property>
+ </object>
+ </child>
+ <child>
+ <object class="GtkShortcutsShortcut">
+ <property name="visible">1</property>
+ <property name="accelerator">&lt;ctrl&gt;S</property>
+ <property name="title" translatable="yes" context="shortcut window">Search</property>
+ </object>
+ </child>
+ </object>
+ </child>
+ <child>
+ <object class="GtkShortcutsGroup">
+ <property name="visible">1</property>
+ <property name="title" translatable="yes" context="shortcut window">Panels</property>
+ <child>
+ <object class="GtkShortcutsShortcut">
+ <property name="visible">1</property>
+ <property name="accelerator">&lt;Alt&gt;Left</property>
+ <property name="title" translatable="yes" context="shortcut window">Go back to previous panel</property>
+ </object>
+ </child>
+ </object>
+ </child>
+ <child>
+ <object class="GtkShortcutsGroup">
+ <property name="visible">1</property>
+ <property name="title" translatable="yes" context="shortcut window">Search</property>
+ <child>
+ <object class="GtkShortcutsShortcut">
+ <property name="visible">1</property>
+ <property name="accelerator">Escape</property>
+ <property name="title" translatable="yes" context="shortcut window">Cancel search</property>
+ </object>
+ </child>
+ </object>
+ </child>
+ </object>
+ </child>
+ </object>
+</interface>
diff --git a/shell/list-panel.sh b/shell/list-panel.sh
new file mode 100755
index 0000000..cf31eae
--- /dev/null
+++ b/shell/list-panel.sh
@@ -0,0 +1,8 @@
+#!/bin/sh
+
+LIST=""
+for i in $1/panels/*/gnome-*panel.desktop.in.in $1/panels/*/data/gnome-*panel.desktop.in.in; do
+ basename=`basename $i`
+ LIST="$LIST `echo $basename | sed 's/gnome-//' | sed 's/-panel.desktop.in.in/ /'`"
+done
+echo -n $LIST | tr " " "\n" | sort | tr "\n" " "
diff --git a/shell/main.c b/shell/main.c
new file mode 100644
index 0000000..14c81c5
--- /dev/null
+++ b/shell/main.c
@@ -0,0 +1,66 @@
+/*
+ * Copyright (c) 2009, 2010 Intel, Inc.
+ * Copyright (c) 2010 Red Hat, Inc.
+ *
+ * The Control Center 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.
+ *
+ * The Control Center 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 the Control Center; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ *
+ * Author: Thomas Wood <thos@gnome.org>
+ */
+
+#include "config.h"
+
+#include <stdlib.h>
+#include <glib/gi18n.h>
+#include <gtk/gtk.h>
+
+#ifdef GDK_WINDOWING_X11
+#include <X11/Xlib.h>
+#endif
+
+#ifdef HAVE_CHEESE
+#include <cheese-gtk.h>
+#endif /* HAVE_CHEESE */
+
+#include "cc-application.h"
+
+static void
+initialize_dependencies (gint *argc,
+ gchar ***argv)
+{
+ #ifdef GDK_WINDOWING_X11
+ XInitThreads ();
+ #endif
+
+ #ifdef HAVE_CHEESE
+ cheese_gtk_init (argc, argv);
+ #endif /* HAVE_CHEESE */
+}
+
+int
+main (gint argc,
+ gchar **argv)
+{
+ g_autoptr(GtkApplication) application = NULL;
+
+ bindtextdomain (GETTEXT_PACKAGE, GNOMELOCALEDIR);
+ bind_textdomain_codeset (GETTEXT_PACKAGE, "UTF-8");
+ textdomain (GETTEXT_PACKAGE);
+
+ initialize_dependencies (&argc, &argv);
+
+ application = cc_application_new ();
+
+ return g_application_run (G_APPLICATION (application), argc, argv);
+}
diff --git a/shell/meson.build b/shell/meson.build
new file mode 100644
index 0000000..89b9659
--- /dev/null
+++ b/shell/meson.build
@@ -0,0 +1,176 @@
+subdir('appdata')
+subdir('completions')
+
+service_conf = configuration_data()
+service_conf.set('bindir', control_center_bindir)
+
+service = 'org.gnome.ControlCenter.service'
+
+configure_file(
+ input : service + '.in',
+ output : service,
+ install : true,
+ install_dir : join_paths(control_center_datadir, 'dbus-1', 'services'),
+ configuration : service_conf
+)
+
+desktop = 'gnome-control-center.desktop'
+
+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
+)
+
+cflags = ['-DGNOMELOCALEDIR="@0@"'.format(control_center_localedir)]
+
+
+# Common sources between gnome-control-center and
+# libtestshell.
+common_sources = files(
+ 'cc-application.c',
+ 'cc-log.c',
+ 'cc-object-storage.c',
+ 'cc-panel-loader.c',
+ 'cc-panel.c',
+ 'cc-shell.c',
+ 'cc-panel-list.c',
+ 'cc-window.c',
+)
+
+
+###################
+# Generated files #
+###################
+
+generated_sources = files()
+
+# Debug
+debug_conf = configuration_data()
+debug_conf.set('BUGREPORT_URL', 'https://gitlab.gnome.org/GNOME/' + meson.project_name() + '/issues/new')
+debug_conf.set10('ENABLE_TRACING', enable_tracing)
+
+generated_sources += configure_file(
+ input : 'cc-debug.h.in',
+ output : 'cc-debug.h',
+ configuration : debug_conf
+)
+
+#Resources
+resource_data = files(
+ 'cc-panel-list.ui',
+ 'cc-window.ui',
+ 'help-overlay.ui',
+)
+
+generated_sources += gnome.compile_resources(
+ 'resources',
+ meson.project_name() + '.gresource.xml',
+ dependencies : resource_data,
+ export : true
+)
+
+common_sources += generated_sources
+
+############
+# libshell #
+############
+
+libshell = static_library(
+ 'shell',
+ sources : 'cc-shell-model.c',
+ include_directories : [top_inc, common_inc],
+ dependencies : common_deps,
+ c_args : cflags
+)
+
+libshell_dep = declare_dependency(
+ sources : generated_sources,
+ include_directories : top_inc,
+ link_with : libshell
+)
+
+
+########################
+# gnome-control-center #
+########################
+
+shell_sources = common_sources + files('main.c')
+
+shell_deps = common_deps + [
+ libdevice_dep,
+ liblanguage_dep,
+ libwidgets_dep,
+ x11_dep,
+ libshell_dep,
+]
+
+if enable_cheese
+ shell_deps += cheese_deps
+endif
+
+if host_is_linux_not_s390
+ shell_deps += wacom_deps
+endif
+
+executable(
+ meson.project_name(),
+ shell_sources,
+ include_directories : top_inc,
+ dependencies : shell_deps,
+ c_args : cflags,
+ link_with : panels_libs,
+ install : true
+)
+
+
+##################
+# lipanel_loader #
+##################
+
+# Because it is confusing and somewhat problematic to directly add and compile
+# cc-panel-loader.o by another directory (i.e. the shell search provider), we
+# have to create a library and link it there, just like libshell.la.
+libpanel_loader = static_library(
+ 'panel_loader',
+ sources : 'cc-panel-loader.c',
+ include_directories : top_inc,
+ dependencies : common_deps,
+ c_args : cflags + ['-DCC_PANEL_LOADER_NO_GTYPES']
+)
+
+
+################
+# libtestshell #
+################
+
+libtestshell = static_library(
+ 'testshell',
+ common_sources,
+ include_directories : top_inc,
+ dependencies : shell_deps,
+ c_args : cflags,
+ link_with : panels_libs,
+)
+libtestshell_dep = declare_dependency(
+ sources : generated_sources,
+ include_directories : top_inc,
+ link_with : libtestshell
+)
+libtestshell_deps = common_deps + [ libwidgets_dep, libtestshell_dep ]
+
+
+install_data (
+ 'org.gnome.ControlCenter.gschema.xml',
+ install_dir : control_center_schemadir
+)
diff --git a/shell/org.gnome.ControlCenter.gschema.xml b/shell/org.gnome.ControlCenter.gschema.xml
new file mode 100644
index 0000000..40350bc
--- /dev/null
+++ b/shell/org.gnome.ControlCenter.gschema.xml
@@ -0,0 +1,19 @@
+<schemalist>
+ <schema gettext-domain="gnome-control-center-2.0" id="org.gnome.ControlCenter" path="/org/gnome/control-center/">
+ <key name="last-panel" type="s">
+ <default>''</default>
+ <summary>The identifier for the last Settings panel to be opened</summary>
+ <description>
+ The identifier for the last Settings panel to be opened. Unrecognised values
+ will be ignored and the first panel in the list selected.
+ </description>
+ </key>
+ <key name="show-development-warning" type="b">
+ <default>true</default>
+ <summary>Show warning when running a development build of Settings</summary>
+ <description>
+ Whether Settings should show a warning when running a development build.
+ </description>
+ </key>
+ </schema>
+</schemalist>
diff --git a/shell/org.gnome.ControlCenter.service.in b/shell/org.gnome.ControlCenter.service.in
new file mode 100644
index 0000000..a515129
--- /dev/null
+++ b/shell/org.gnome.ControlCenter.service.in
@@ -0,0 +1,3 @@
+[D-BUS Service]
+Name=org.gnome.ControlCenter
+Exec=@bindir@/gnome-control-center
diff --git a/shell/style.css b/shell/style.css
new file mode 100644
index 0000000..7ffc6ab
--- /dev/null
+++ b/shell/style.css
@@ -0,0 +1,7 @@
+.drag-handle {
+ color: alpha(@theme_fg_color, 0.4);
+}
+
+.drag-handle:backdrop {
+ color: alpha(@theme_unfocused_fg_color, 0.4);
+}