From f5411c92b2578aaeb44e951a81ba39fb48517d4f Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Guido=20G=C3=BCnther?= <agx@sigxcpu.org>
Date: Tue, 13 Feb 2018 12:14:53 +0100
Subject: [PATCH] Add basic settings menu

This adds a common base class PhoshMenu for all top panel menus so
phosh.c itself doesn't need to care about the details and we don't
duplicate the code.
---
 README.md                       |   6 +
 protocol/phosh-mobile-shell.xml |  21 ++-
 run.in                          |   3 +
 src/favorites.c                 |  33 ++--
 src/favorites.h                 |  29 +---
 src/menu.c                      | 295 ++++++++++++++++++++++++++++++++
 src/menu.h                      |  38 ++++
 src/meson.build                 |   5 +
 src/panel.c                     |  51 ++++--
 src/phosh.c                     |  72 ++++----
 src/phosh.gresources.xml        |   1 +
 src/settings.c                  | 102 +++++++++++
 src/settings.h                  |  18 ++
 src/settings/brightness.c       | 124 ++++++++++++++
 src/settings/brightness.h       |   9 +
 src/style.css                   |   2 +-
 src/ui/settings-menu.ui         |  97 +++++++++++
 src/ui/top-panel.ui             |  21 ++-
 18 files changed, 824 insertions(+), 103 deletions(-)
 create mode 100644 src/menu.c
 create mode 100644 src/menu.h
 create mode 100644 src/settings.c
 create mode 100644 src/settings.h
 create mode 100644 src/settings/brightness.c
 create mode 100644 src/settings/brightness.h
 create mode 100644 src/ui/settings-menu.ui

diff --git a/README.md b/README.md
index 5b9b5ef..18946b3 100644
--- a/README.md
+++ b/README.md
@@ -26,3 +26,9 @@ using:
 
     _build/run
 
+If you want to test interaction with gnome-settings-daemon e.g. for brightness
+start a session like:
+
+    gnome-session --session=gnome-dummy --disable-acceleration-check &
+
+before running phosh.
diff --git a/protocol/phosh-mobile-shell.xml b/protocol/phosh-mobile-shell.xml
index 259d2f7..887f6cb 100644
--- a/protocol/phosh-mobile-shell.xml
+++ b/protocol/phosh-mobile-shell.xml
@@ -129,15 +129,14 @@
     </request>
   </interface>
   
-  <!--
-    <enum name="menu_position">
-      <!- only supported for top panels ->
-      <entry name="left" value="0"/>
-      <entry name="right" value="1"/>
-    </enum>
-
-    <request name="set_menu_position">
-      <arg name="position" type="uint"/>
-    </request>
-  -->
+  <enum name="menu_position">
+    <!-- only supported for top panels -->
+    <entry name="left" value="0"/>
+    <entry name="right" value="1"/>
+  </enum>
+
+  <request name="set_menu_position">
+    <arg name="surface" type="object" interface="wl_surface"/>
+    <arg name="position" type="uint"/>
+  </request>
 </protocol>
diff --git a/run.in b/run.in
index 6f7f2f0..7348a9e 100755
--- a/run.in
+++ b/run.in
@@ -2,6 +2,9 @@
 
 ABS_BUILDDIR='@ABS_BUILDDIR@'
 
+# Start up gsd, etc.
+gnome-session --session=gnome-dummy --disable-acceleration-check &
+
 export GSETTINGS_SCHEMA_DIR="${ABS_BUILDDIR}/data"
 
 exec "${ABS_BUILDDIR}/src/phosh" $@
diff --git a/src/favorites.c b/src/favorites.c
index 30c881b..60bf2f0 100644
--- a/src/favorites.c
+++ b/src/favorites.c
@@ -21,12 +21,19 @@ enum {
 };
 static guint signals[N_SIGNALS] = { 0 };
 
-struct PhoshFavoritesPrivate {
+typedef struct
+{
   GtkWidget *grid;
   GSettings *settings;
+} PhoshFavoritesPrivate;
+
+
+struct _PhoshFavorites
+{
+  PhoshMenuClass parent;
 };
 
-G_DEFINE_TYPE_WITH_PRIVATE(PhoshFavorites, phosh_favorites, GTK_TYPE_WINDOW)
+G_DEFINE_TYPE_WITH_PRIVATE(PhoshFavorites, phosh_favorites, PHOSH_TYPE_MENU) 
 
 
 static void
@@ -109,7 +116,7 @@ favorites_changed (GSettings *settings,
   for (gint i = 0; i < g_strv_length (favorites); i++) {
     gchar *fav = favorites[i];
     btn = add_favorite (self, fav);
-    gtk_grid_attach (GTK_GRID (self->priv->grid), btn, 1, top++, 1, 1);
+    gtk_grid_attach (GTK_GRID (priv->grid), btn, 1, top++, 1, 1);
   }
   g_strfreev (favorites);
 }
@@ -138,10 +145,10 @@ phosh_favorites_constructed (GObject *object)
 			       GTK_ALIGN_CENTER, NULL);
   gtk_container_add (GTK_CONTAINER (self), priv->grid);
 
-  self->priv->settings = g_settings_new ("sm.puri.phosh");
-  g_signal_connect (self->priv->settings, "changed::favorites",
+  priv->settings = g_settings_new ("sm.puri.phosh");
+  g_signal_connect (priv->settings, "changed::favorites",
 		    G_CALLBACK (favorites_changed), self);
-  favorites_changed (self->priv->settings, "favorites", self);
+  favorites_changed (priv->settings, "favorites", self);
 }
 
 
@@ -149,8 +156,9 @@ static void
 phosh_favorites_dispose (GObject *object)
 {
   PhoshFavorites *self = PHOSH_FAVORITES (object);
+  PhoshFavoritesPrivate *priv = phosh_favorites_get_instance_private (self);
 
-  g_clear_object (&self->priv->settings);
+  g_clear_object (&priv->settings);
 
   G_OBJECT_CLASS (phosh_favorites_parent_class)->dispose (object);
 }
@@ -173,14 +181,15 @@ phosh_favorites_class_init (PhoshFavoritesClass *klass)
 static void
 phosh_favorites_init (PhoshFavorites *self)
 {
-  self->priv = G_TYPE_INSTANCE_GET_PRIVATE (self,
-      PHOSH_FAVORITES_TYPE,
-      PhoshFavoritesPrivate);
 }
 
 
 GtkWidget *
-phosh_favorites_new (void)
+phosh_favorites_new (int position, const gpointer *shell)
 {
-  return g_object_new (PHOSH_FAVORITES_TYPE, NULL);
+  return g_object_new (PHOSH_TYPE_FAVORITES,
+		       "name", "favorites",
+		       "shell", shell,
+		       "position", position,
+		       NULL);
 }
diff --git a/src/favorites.h b/src/favorites.h
index 8584aeb..4b94cdb 100644
--- a/src/favorites.h
+++ b/src/favorites.h
@@ -7,33 +7,12 @@
 #ifndef __PHOSH_FAVORITES_H__
 #define __PHOSH_FAVORITES_H__
 
-#include <gtk/gtk.h>
+#include "menu.h"
 
-#define PHOSH_FAVORITES_TYPE                 (phosh_favorites_get_type ())
-#define PHOSH_FAVORITES(obj)                 (G_TYPE_CHECK_INSTANCE_CAST ((obj), PHOSH_FAVORITES_TYPE, PhoshFavorites))
-#define PHOSH_FAVORITES_CLASS(klass)         (G_TYPE_CHECK_CLASS_CAST ((klass), PHOSH_FAVORITES_TYPE, PhoshFavoritesClass))
-#define PHOSH_IS_FAVORITES(obj)              (G_TYPE_CHECK_INSTANCE_TYPE ((obj), PHOSH_FAVORITES_TYPE))
-#define PHOSH_IS_FAVORITES_CLASS(klass)      (G_TYPE_CHECK_CLASS_TYPE ((klass), PHOSH_FAVORITES_TYPE))
-#define PHOSH_FAVORITES_GET_CLASS(obj)       (G_TYPE_INSTANCE_GET_CLASS ((obj), PHOSH_FAVORITES_TYPE, PhoshFavoritesClass))
+#define PHOSH_TYPE_FAVORITES (phosh_favorites_get_type())
 
-typedef struct PhoshFavorites PhoshFavorites;
-typedef struct PhoshFavoritesClass PhoshFavoritesClass;
-typedef struct PhoshFavoritesPrivate PhoshFavoritesPrivate;
+G_DECLARE_FINAL_TYPE (PhoshFavorites, phosh_favorites, PHOSH, FAVORITES, PhoshMenu)
 
-struct PhoshFavorites
-{
-  GtkWindow parent;
-
-  PhoshFavoritesPrivate *priv;
-};
-
-struct PhoshFavoritesClass
-{
-  GtkWindowClass parent_class;
-};
-
-GType phosh_favorites_get_type (void) G_GNUC_CONST;
-
-GtkWidget * phosh_favorites_new (void);
+GtkWidget * phosh_favorites_new (int position, const gpointer *shell);
 
 #endif /* __PHOSH_FAVORITES_H__ */
diff --git a/src/menu.c b/src/menu.c
new file mode 100644
index 0000000..1cf024c
--- /dev/null
+++ b/src/menu.c
@@ -0,0 +1,295 @@
+/*
+ * Copyright (C) 2018 Purism SPC
+ *
+ * SPDX-License-Identifier: GPL-3+
+ * Author: Guido Günther <agx@sigxcpu.org>
+ */
+
+#include <glib/gi18n.h>
+
+#include "menu.h"
+#include "phosh-mobile-shell-client-protocol.h"
+
+/**
+ * SECTION:phosh-menu
+ * @short_description: A menu of the phosh wayland shell
+ * @Title: PhoshMenu
+ *
+ * The #PhoshMenu widget is a shell menu attached to a panel
+ * Don't let the wayland details leak to child classes.
+ */
+
+
+typedef struct
+{
+  struct wl_surface *surface;
+  struct phosh_mobile_shell *mshell;
+  gboolean shown;
+  gchar *name;
+  int position;
+} PhoshMenuPrivate;
+
+G_DEFINE_TYPE_WITH_PRIVATE (PhoshMenu, phosh_menu, GTK_TYPE_WINDOW)
+
+
+enum {
+  PHOSH_MENU_PROP_0 = 0,
+  PHOSH_MENU_PROP_SHOWN,
+  PHOSH_MENU_PROP_NAME,
+  PHOSH_MENU_PROP_SHELL,
+  PHOSH_MENU_PROP_POSITION,
+  PHOSH_MENU_PROP_LAST_PROP,
+};
+static GParamSpec *props[PHOSH_MENU_PROP_LAST_PROP] = { NULL, };
+
+enum {
+  TOGGLED = 1,
+  LAST_SIGNAL,
+};
+static guint signals [LAST_SIGNAL];
+
+
+static void
+phosh_menu_set_property (GObject *object,
+			 guint property_id,
+			 const GValue *value,
+			 GParamSpec *pspec)
+{
+  PhoshMenu *self = PHOSH_MENU (object);
+  PhoshMenuPrivate *priv = phosh_menu_get_instance_private(self);
+
+  switch (property_id) {
+  case PHOSH_MENU_PROP_SHOWN:
+    priv->shown = g_value_get_boolean (value);
+    break;
+
+  case PHOSH_MENU_PROP_NAME:
+    g_free (priv->name);
+    priv->name = g_value_dup_string (value);
+    break;
+
+  case PHOSH_MENU_PROP_SHELL:
+    priv->mshell = g_value_get_pointer (value);
+    break;
+
+  case PHOSH_MENU_PROP_POSITION:
+    priv->position = g_value_get_int (value);
+    break;
+
+  default:
+    G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+    break;
+  }
+}
+
+
+static void
+phosh_menu_get_property (GObject *object,
+			 guint property_id,
+			 GValue *value,
+			 GParamSpec *pspec)
+{
+  PhoshMenu *self = PHOSH_MENU (object);
+  PhoshMenuPrivate *priv = phosh_menu_get_instance_private(self);
+
+  switch (property_id) {
+  case PHOSH_MENU_PROP_SHOWN:
+    g_value_set_boolean (value, priv->shown);
+    break;
+
+  case PHOSH_MENU_PROP_NAME:
+    g_value_set_string (value, priv->name);
+    break;
+
+  case PHOSH_MENU_PROP_SHELL:
+    g_value_set_pointer (value, priv->mshell);
+
+  case PHOSH_MENU_PROP_POSITION:
+    g_value_set_int (value, priv->position);
+
+  default:
+    G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+    break;
+  }
+}
+
+
+static void
+phosh_menu_constructed (GObject *object)
+{
+  PhoshMenu *self = PHOSH_MENU (object);
+  PhoshMenuPrivate *priv = phosh_menu_get_instance_private (self);
+  GdkWindow *gdk_window;
+  g_autofree gchar *name;;
+
+  G_OBJECT_CLASS (phosh_menu_parent_class)->constructed (object);
+
+  name = g_strdup_printf("phosh-menu-%s", priv->name);
+
+  /* window properties */
+  gtk_window_set_title (GTK_WINDOW (self), name);
+  gtk_window_set_decorated (GTK_WINDOW (self), FALSE);
+  gtk_widget_realize(GTK_WIDGET (self));
+
+  gtk_style_context_add_class (
+      gtk_widget_get_style_context (GTK_WIDGET (self)),
+      "phosh-menu");
+
+  gtk_style_context_add_class (
+      gtk_widget_get_style_context (GTK_WIDGET (self)),
+      name);
+
+  gdk_window = gtk_widget_get_window (GTK_WIDGET (self));
+  gdk_wayland_window_set_use_custom_surface (gdk_window);
+  priv->surface = gdk_wayland_window_get_wl_surface (gdk_window);
+
+  g_return_if_fail (priv->surface != NULL);
+
+  phosh_mobile_shell_set_panel_menu (priv->mshell, priv->surface);
+  phosh_mobile_shell_set_menu_position(priv->mshell,
+				       priv->surface,
+				       priv->position);
+}
+
+
+static void
+phosh_menu_finalize (GObject *object)
+{
+  PhoshMenuPrivate *priv = phosh_menu_get_instance_private (PHOSH_MENU(object));
+  GObjectClass *parent_class = G_OBJECT_CLASS (phosh_menu_parent_class);
+
+  g_free (priv->name);
+
+  if (parent_class->finalize != NULL)
+    parent_class->finalize (object);
+}
+
+
+static void
+phosh_menu_class_init (PhoshMenuClass *klass)
+{
+  GObjectClass *object_class = G_OBJECT_CLASS (klass);
+
+  object_class->constructed = phosh_menu_constructed;
+  object_class->finalize = phosh_menu_finalize;
+
+  object_class->set_property = phosh_menu_set_property;
+  object_class->get_property = phosh_menu_get_property;
+
+  /**
+   * PhoshMenu::toggled:
+   * @self: The #PhoshMenu instance.
+   *
+   * This signal is emitted when the menu state changes.
+   * That is if it's shown or hidden on the screen.
+   */
+  signals [TOGGLED] = g_signal_new ("toggled",
+        G_TYPE_FROM_CLASS (klass), G_SIGNAL_RUN_LAST, 0, NULL, NULL,
+	NULL, G_TYPE_NONE, 0);
+
+  props[PHOSH_MENU_PROP_SHOWN] = g_param_spec_boolean ("shown",
+						       "menu shown",
+						       "Whether the menu is shown on screen",
+						       FALSE,
+						       G_PARAM_READABLE);
+  props[PHOSH_MENU_PROP_NAME] = g_param_spec_string ("name",
+						     "menu name",
+						     "the menus name",
+						     "unnamed",
+						     G_PARAM_CONSTRUCT_ONLY |
+						     G_PARAM_READWRITE);
+  props[PHOSH_MENU_PROP_SHELL] = g_param_spec_pointer ("shell",
+						       "mobile shell",
+						       "the mobile shell",
+						       G_PARAM_CONSTRUCT_ONLY |
+						       G_PARAM_READWRITE);
+  props[PHOSH_MENU_PROP_POSITION] = g_param_spec_int ("position",
+						      "menu position",
+						      "menu position on top bar",
+						      0,
+						      INT_MAX,
+						      PHOSH_MOBILE_SHELL_MENU_POSITION_LEFT,
+						      G_PARAM_CONSTRUCT_ONLY |
+						      G_PARAM_READWRITE);
+
+  g_object_class_install_properties (object_class, PHOSH_MENU_PROP_LAST_PROP, props);
+}
+
+
+/**
+ * phosh_menu_new:
+ *
+ * Create a new #PhoshMenu widget.
+ *
+ * Returns: the newly created #PhoshMenu widget
+ *
+ */
+GtkWidget *
+phosh_menu_new (const char* name, int position, const gpointer *shell)
+{
+  return g_object_new (PHOSH_TYPE_MENU,
+		       "name", name,
+		       "shell", shell,
+		       "position", position,
+		       NULL);
+}
+
+
+static void
+phosh_menu_init (PhoshMenu *self)
+{
+  PhoshMenuPrivate *priv = phosh_menu_get_instance_private(self);
+
+  priv->shown = FALSE;
+  priv->name = NULL;
+  priv->mshell = NULL;
+}
+
+
+gboolean
+phosh_menu_is_shown (PhoshMenu *self)
+{
+  PhoshMenuPrivate *priv = phosh_menu_get_instance_private(self);
+
+  return priv->shown;
+}
+
+
+void
+phosh_menu_hide (PhoshMenu *self)
+{
+  PhoshMenuPrivate *priv = phosh_menu_get_instance_private(self);
+
+  g_return_if_fail (priv->mshell);
+  g_return_if_fail (priv->surface);
+
+  if (priv->shown)
+    phosh_mobile_shell_hide_panel_menu(priv->mshell,
+				       priv->surface);
+  priv->shown = FALSE;
+}
+
+
+void
+phosh_menu_show (PhoshMenu *self)
+{
+  PhoshMenuPrivate *priv = phosh_menu_get_instance_private(self);
+
+  g_return_if_fail (priv->mshell);
+  g_return_if_fail (priv->surface);
+
+  if (!priv->shown)
+    phosh_mobile_shell_show_panel_menu(priv->mshell,
+				       priv->surface);
+  priv->shown = TRUE;
+}
+
+
+gboolean
+phosh_menu_toggle (PhoshMenu *self)
+{
+  PhoshMenuPrivate *priv = phosh_menu_get_instance_private(self);
+
+  priv->shown ? phosh_menu_hide (self) : phosh_menu_show (self);
+  return priv->shown;
+}
diff --git a/src/menu.h b/src/menu.h
new file mode 100644
index 0000000..30da854
--- /dev/null
+++ b/src/menu.h
@@ -0,0 +1,38 @@
+/*
+ * Copyright (C) 2018 Purism SPC
+ *
+ * SPDX-License-Identifier: GPL-3+
+ */
+
+#ifndef PHOSH_MENU_H
+#define PHOSH_MENU_H
+
+#include <gtk/gtk.h>
+#include <gdk/gdkwayland.h>
+
+G_BEGIN_DECLS
+
+#define PHOSH_TYPE_MENU (phosh_menu_get_type())
+
+G_DECLARE_DERIVABLE_TYPE (PhoshMenu, phosh_menu, PHOSH, MENU, GtkWindow)
+
+/**
+ * PhoshMenuClass
+ * @parent_class: The parent class
+ */
+struct _PhoshMenuClass
+{
+  GtkWindowClass parent_class;
+};
+
+GtkWidget *          phosh_menu_new            (const char* name,
+						int position,
+						const gpointer *shell);
+gboolean             phosh_menu_is_shown       (PhoshMenu *self);
+void                 phosh_menu_show           (PhoshMenu *self);
+void                 phosh_menu_hide           (PhoshMenu *self);
+gboolean             phosh_menu_toggle         (PhoshMenu *self);
+
+G_END_DECLS
+
+#endif /* PHOSH_MENU_H */
diff --git a/src/meson.build b/src/meson.build
index 9d7a49d..861b758 100644
--- a/src/meson.build
+++ b/src/meson.build
@@ -22,9 +22,14 @@ phosh_sources = [
   'favorites.h',
   'lockscreen.c',
   'lockscreen.h',
+  'menu.c',
+  'menu.h',
   'panel.c',
   'panel.h',
   'phosh.c',
+  'settings.c',
+  'settings.h',
+  'settings/brightness.c',
   shell_proto_header,
   shell_proto_code,
   phosh_resources,
diff --git a/src/panel.c b/src/panel.c
index 9bc4a9d..f66c14b 100644
--- a/src/panel.c
+++ b/src/panel.c
@@ -15,17 +15,18 @@
 #define GNOME_DESKTOP_USE_UNSTABLE_API
 #include <libgnome-desktop/gnome-wall-clock.h>
 
-#define TOPLEFT_LABEL_TEXT "Librem5 dev board"
+#define FAVORITES_LABEL_TEXT "Librem5 dev board"
 
 enum {
   FAVORITES_ACTIVATED,
+  SETTINGS_ACTIVATED,
   N_SIGNALS
 };
 static guint signals[N_SIGNALS] = { 0 };
 
 struct PhoshPanelPrivate {
-  GtkWidget *btn_topleft;
-  GtkWidget *label_clock;
+  GtkWidget *btn_favorites;
+  GtkWidget *btn_settings;
 
   GnomeWallClock *wall_clock;
 };
@@ -33,9 +34,8 @@ struct PhoshPanelPrivate {
 G_DEFINE_TYPE_WITH_PRIVATE (PhoshPanel, phosh_panel, GTK_TYPE_WINDOW)
 
 
-/* FIXME: Temporarily add a term button until we have the menu system */
 static void
-topleft_clicked_cb (PhoshPanel *self, GtkButton *btn)
+favorites_clicked_cb (PhoshPanel *self, GtkButton *btn)
 {
   g_return_if_fail (PHOSH_IS_PANEL (self));
   g_return_if_fail (GTK_IS_BUTTON (btn));
@@ -43,6 +43,15 @@ topleft_clicked_cb (PhoshPanel *self, GtkButton *btn)
 }
 
 
+static void
+settings_clicked_cb (PhoshPanel *self, GtkButton *btn)
+{
+  g_return_if_fail (PHOSH_IS_PANEL (self));
+  g_return_if_fail (GTK_IS_BUTTON (btn));
+  g_signal_emit(self, signals[SETTINGS_ACTIVATED], 0);
+}
+
+
 static void
 wall_clock_notify_cb (GnomeWallClock *wall_clock,
     GParamSpec *pspec,
@@ -55,7 +64,7 @@ wall_clock_notify_cb (GnomeWallClock *wall_clock,
   datetime = g_date_time_new_now_local ();
 
   str = g_date_time_format (datetime, "%H:%M");
-  gtk_label_set_markup (GTK_LABEL (priv->label_clock), str);
+  gtk_button_set_label (GTK_BUTTON (priv->btn_settings), str);
 
   g_date_time_unref (datetime);
 }
@@ -69,16 +78,22 @@ phosh_panel_constructed (GObject *object)
 
   G_OBJECT_CLASS (phosh_panel_parent_class)->constructed (object);
 
-  gtk_button_set_label (GTK_BUTTON (priv->btn_topleft), TOPLEFT_LABEL_TEXT);
+  gtk_button_set_label (GTK_BUTTON (priv->btn_favorites), FAVORITES_LABEL_TEXT);
   priv->wall_clock = g_object_new (GNOME_TYPE_WALL_CLOCK, NULL);
   g_signal_connect (priv->wall_clock,
 		    "notify::clock",
 		    G_CALLBACK (wall_clock_notify_cb),
 		    self);
 
-  g_signal_connect_object (priv->btn_topleft,
+  g_signal_connect_object (priv->btn_favorites,
+                           "clicked",
+                           G_CALLBACK (favorites_clicked_cb),
+                           self,
+                           G_CONNECT_SWAPPED);
+
+  g_signal_connect_object (priv->btn_settings,
                            "clicked",
-                           G_CALLBACK (topleft_clicked_cb),
+                           G_CALLBACK (settings_clicked_cb),
                            self,
                            G_CONNECT_SWAPPED);
 
@@ -92,12 +107,14 @@ phosh_panel_constructed (GObject *object)
       "phosh-panel");
 
   /* Button properites */
-  gtk_style_context_remove_class (gtk_widget_get_style_context (priv->btn_topleft),
+  gtk_style_context_remove_class (gtk_widget_get_style_context (priv->btn_favorites),
+				  "button");
+  gtk_style_context_remove_class (gtk_widget_get_style_context (priv->btn_favorites),
+				  "image-button");
+  gtk_style_context_remove_class (gtk_widget_get_style_context (priv->btn_settings),
 				  "button");
-  gtk_style_context_remove_class (gtk_widget_get_style_context (priv->btn_topleft),
+  gtk_style_context_remove_class (gtk_widget_get_style_context (priv->btn_settings),
 				  "image-button");
-  gtk_style_context_add_class (gtk_widget_get_style_context (priv->btn_topleft),
-			       "phosh-panel-btn-top-left");
 
   wall_clock_notify_cb (priv->wall_clock, NULL, self);
 }
@@ -128,10 +145,14 @@ phosh_panel_class_init (PhoshPanelClass *klass)
       G_TYPE_FROM_CLASS (klass), G_SIGNAL_RUN_LAST, 0, NULL, NULL,
       NULL, G_TYPE_NONE, 0);
 
+  signals[SETTINGS_ACTIVATED] = g_signal_new ("settings-activated",
+      G_TYPE_FROM_CLASS (klass), G_SIGNAL_RUN_LAST, 0, NULL, NULL,
+      NULL, G_TYPE_NONE, 0);
+
   gtk_widget_class_set_template_from_resource (widget_class,
 					       "/sm/puri/phosh/ui/top-panel.ui");
-  gtk_widget_class_bind_template_child_private (widget_class, PhoshPanel, btn_topleft);
-  gtk_widget_class_bind_template_child_private (widget_class, PhoshPanel, label_clock);
+  gtk_widget_class_bind_template_child_private (widget_class, PhoshPanel, btn_favorites);
+  gtk_widget_class_bind_template_child_private (widget_class, PhoshPanel, btn_settings);
 }
 
 
diff --git a/src/phosh.c b/src/phosh.c
index 26c71d1..126da23 100644
--- a/src/phosh.c
+++ b/src/phosh.c
@@ -16,9 +16,11 @@
 
 #include "phosh-mobile-shell-client-protocol.h"
 
-#include "favorites.h"
+
 #include "lockscreen.h"
 #include "panel.h"
+#include "favorites.h"
+#include "settings.h"
 
 struct elem {
   GtkWidget *window;
@@ -48,8 +50,10 @@ struct desktop {
   gulong unlock_handler_id;
 
   /* Favorites menu */
-  struct elem *favorites;
-  gboolean favorites_shown;
+  GtkWidget *favorites;
+
+  /* Settings menu */
+  GtkWidget *settings;
 };
 
 
@@ -67,26 +71,23 @@ lockscreen_unlock_cb (struct desktop *desktop, PhoshLockscreen *window)
 
 
 static void
-favorites_activated_cb (struct desktop *desktop, PhoshLockscreen *window)
+favorites_activated_cb (struct desktop *desktop, PhoshPanel *window)
 {
-  if (desktop->favorites_shown) {
-    phosh_mobile_shell_hide_panel_menu(desktop->mshell,
-					 desktop->favorites->surface);
-
-  } else {
-    phosh_mobile_shell_show_panel_menu(desktop->mshell,
-					 desktop->favorites->surface);
-  }
-  desktop->favorites_shown = !desktop->favorites_shown;
+  phosh_menu_toggle (PHOSH_MENU (desktop->favorites));
 }
 
 
 static void
 app_launched_cb (struct desktop *desktop, PhoshFavorites *favorites)
 {
-  phosh_mobile_shell_hide_panel_menu(desktop->mshell,
-				       desktop->favorites->surface);
-  desktop->favorites_shown = FALSE;
+  phosh_menu_hide (PHOSH_MENU (desktop->favorites));
+}
+
+
+static void
+settings_activated_cb (struct desktop *desktop, PhoshPanel *window)
+{
+  phosh_menu_toggle (PHOSH_MENU (desktop->settings));
 }
 
 
@@ -120,29 +121,27 @@ lockscreen_create (struct desktop *desktop)
 static void
 favorites_create(struct desktop *desktop)
 {
-  struct elem *favorites;
-  GdkWindow *gdk_window;
-
-  favorites = calloc (1, sizeof *favorites);
-  favorites->window = phosh_favorites_new ();
+  desktop->favorites = phosh_favorites_new (PHOSH_MOBILE_SHELL_MENU_POSITION_LEFT,
+					    (gpointer) desktop->mshell);
 
-  gdk_window = gtk_widget_get_window (favorites->window);
-  gdk_wayland_window_set_use_custom_surface (gdk_window);
-  favorites->surface = gdk_wayland_window_get_wl_surface (gdk_window);
+  gtk_widget_show_all (desktop->favorites);
 
-  phosh_mobile_shell_set_panel_menu (desktop->mshell,
-				       favorites->surface);
-  gtk_widget_show_all (favorites->window);
-  desktop->favorites = favorites;
-  desktop->favorites_shown = FALSE;
-
-  g_signal_connect_swapped (favorites->window,
+  g_signal_connect_swapped (desktop->favorites,
 			    "app-launched",
 			    G_CALLBACK(app_launched_cb),
 			    desktop);
 }
 
 
+static void
+settings_create(struct desktop *desktop)
+{
+  desktop->settings = phosh_settings_new (PHOSH_MOBILE_SHELL_MENU_POSITION_RIGHT,
+					  (gpointer) desktop->mshell);
+  gtk_widget_show_all (desktop->settings);
+}
+
+
 static void
 panel_create (struct desktop *desktop)
 {
@@ -171,6 +170,12 @@ panel_create (struct desktop *desktop)
     "favorites-activated",
     G_CALLBACK(favorites_activated_cb),
     desktop);
+
+  g_signal_connect_swapped (
+    panel->window,
+    "settings-activated",
+    G_CALLBACK(settings_activated_cb),
+    desktop);
 }
 
 
@@ -300,6 +305,10 @@ shell_configure (struct desktop *desktop,
       width, PHOSH_PANEL_HEIGHT);
 
   phosh_mobile_shell_desktop_ready (desktop->mshell);
+
+  /* Create menus once we now the panel's position */
+  favorites_create (desktop);
+  settings_create (desktop);
 }
 
 
@@ -414,7 +423,6 @@ int main(int argc, char *argv[])
   css_setup (desktop);
   background_create (desktop);
   panel_create (desktop);
-  favorites_create (desktop);
 
   gtk_main ();
 
diff --git a/src/phosh.gresources.xml b/src/phosh.gresources.xml
index 25d8c25..d633fa2 100644
--- a/src/phosh.gresources.xml
+++ b/src/phosh.gresources.xml
@@ -2,6 +2,7 @@
 <gresources>
   <gresource prefix="/sm/puri/phosh">
     <file preprocess="xml-stripblanks">ui/top-panel.ui</file>
+    <file preprocess="xml-stripblanks">ui/settings-menu.ui</file>
     <file compressed="true">style.css</file>
   </gresource>
 </gresources>
diff --git a/src/settings.c b/src/settings.c
new file mode 100644
index 0000000..0da163e
--- /dev/null
+++ b/src/settings.c
@@ -0,0 +1,102 @@
+/*
+ * Copyright (C) 2018 Purism SPC
+ *
+ * SPDX-License-Identifier: GPL-3+
+ * Author: Guido Günther <agx@sigxcpu.org>
+ */
+
+#include <glib/gi18n.h>
+
+#include "settings.h"
+#include "settings/brightness.h"
+
+
+typedef struct
+{
+  GtkWidget *scale_brightness;
+  GtkWidget *scale_volume;
+
+  GtkAdjustment *adj_brightness;
+  GtkAdjustment *adj_volume;
+
+} PhoshSettingsPrivate;
+
+
+typedef struct _PhoshSettings
+{
+  PhoshMenu parent;
+} PhoshSettings;
+
+
+G_DEFINE_TYPE_WITH_PRIVATE (PhoshSettings, phosh_settings, PHOSH_TYPE_MENU)
+
+
+GtkWidget *phosh_settings (const char* name)
+{
+  return g_object_new (PHOSH_TYPE_SETTINGS, "name", name, NULL);
+}
+
+
+static void
+brightness_changed_cb (GtkAdjustment *adj_brightness, gpointer *unused)
+{
+  int brightness;
+
+  brightness = (int)gtk_adjustment_get_value (adj_brightness);
+  brightness_set (brightness);
+}
+
+
+static void
+phosh_settings_constructed (GObject *object)
+{
+  PhoshSettings *self = PHOSH_SETTINGS (object);
+  PhoshSettingsPrivate *priv = phosh_settings_get_instance_private (self);
+
+  priv->adj_brightness = gtk_adjustment_new (0, 0, 100, 1, 10, 10);
+  gtk_range_set_adjustment (GTK_RANGE (priv->scale_brightness), priv->adj_brightness);
+  gtk_range_set_round_digits (GTK_RANGE (priv->scale_brightness), 0);
+  
+  brightness_init (priv->adj_brightness);
+  g_signal_connect (priv->adj_brightness,
+		    "value-changed",
+		    G_CALLBACK(brightness_changed_cb),
+		    NULL);
+
+  priv->adj_volume = gtk_adjustment_new (0, 0, 100, 1, 10, 10);
+  gtk_range_set_adjustment (GTK_RANGE (priv->scale_volume), priv->adj_volume);
+
+  G_OBJECT_CLASS (phosh_settings_parent_class)->constructed (object);
+}
+
+
+static void
+phosh_settings_class_init (PhoshSettingsClass *klass)
+{
+  GObjectClass *object_class = G_OBJECT_CLASS (klass);
+  GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);
+
+  gtk_widget_class_set_template_from_resource (widget_class,
+					       "/sm/puri/phosh/ui/settings-menu.ui");
+
+  object_class->constructed = phosh_settings_constructed;
+  gtk_widget_class_bind_template_child_private (widget_class, PhoshSettings, scale_volume);
+  gtk_widget_class_bind_template_child_private (widget_class, PhoshSettings, scale_brightness);
+}
+
+
+static void
+phosh_settings_init (PhoshSettings *self)
+{
+  gtk_widget_init_template (GTK_WIDGET (self));
+}
+
+GtkWidget *
+phosh_settings_new (int position, const gpointer *shell)
+{
+  return g_object_new (PHOSH_TYPE_SETTINGS,
+		       "name", "settings",
+		       "shell", shell,
+		       "position", position,
+		       NULL);
+}
diff --git a/src/settings.h b/src/settings.h
new file mode 100644
index 0000000..b65bf9e
--- /dev/null
+++ b/src/settings.h
@@ -0,0 +1,18 @@
+/*
+ * Copyright (C) 2018 Purism SPC
+ *
+ * SPDX-License-Identifier: GPL-3.0+
+ */
+
+#ifndef __PHOSH_SETTINGS_H__
+#define __PHOSH_SETTINGS_H__
+
+#include "menu.h"
+
+#define PHOSH_TYPE_SETTINGS (phosh_settings_get_type())
+
+G_DECLARE_FINAL_TYPE (PhoshSettings, phosh_settings, PHOSH, SETTINGS, PhoshMenu)
+
+GtkWidget * phosh_settings_new (int position, const gpointer* shell);
+
+#endif /* __PHOSH_SETTINGS_H__ */
diff --git a/src/settings/brightness.c b/src/settings/brightness.c
new file mode 100644
index 0000000..331578b
--- /dev/null
+++ b/src/settings/brightness.c
@@ -0,0 +1,124 @@
+/*
+ * Copyright (C) 2018 Purism SPC
+ *
+ * SPDX-License-Identifier: GPL-3+
+ * Author: Guido Günther <agx@sigxcpu.org>
+ */
+
+#include <gio/gio.h>
+#include <gtk/gtk.h>
+
+GDBusProxy *brightness_proxy;
+
+
+void
+brightness_changed_cb (GDBusProxy *proxy,
+		       GVariant *changed_props,
+		       GVariant *invalidated_props,
+		       GtkAdjustment *adj)
+{
+  gboolean ret;
+  gint value, cur;
+
+  ret = g_variant_lookup (changed_props,
+                          "Brightness",
+                          "i", &value);
+
+  /* The value returned from DBUs is one lower
+   * than the value we set */
+  cur = gtk_adjustment_get_value (adj);
+  if (ret && cur != value + 1) {
+    gtk_adjustment_set_value (adj, value + 1);
+  }
+}
+
+
+void
+brightness_init (GtkAdjustment *adj)
+{
+  GError *err = NULL;
+  GDBusConnection *session_con;
+  GDBusProxy *proxy;
+  GVariant *var;
+  gint value;
+
+  session_con = g_bus_get_sync(G_BUS_TYPE_SESSION, NULL, &err);
+  if (err != NULL) {
+        g_error ("Can not connect to session bus: %s", err->message);
+        g_error_free (err);
+        return;
+  }
+
+  proxy = g_dbus_proxy_new_sync(session_con,
+				G_DBUS_PROXY_FLAGS_NONE,
+				NULL,
+				"org.gnome.SettingsDaemon.Power",
+				"/org/gnome/SettingsDaemon/Power",
+				"org.gnome.SettingsDaemon.Power.Screen",
+				NULL,
+				&err);
+  if (!proxy || err) {
+    g_warning("Could not connect to brightness service %s", err->message);
+    g_error_free(err);
+    return;
+  }
+
+  /* Set adjustment to current brightness */
+  var = g_dbus_proxy_get_cached_property (proxy, "Brightness");
+  if (var) {
+    g_variant_get (var, "i", &value);
+    gtk_adjustment_set_value (adj, value);
+    g_variant_unref (var);
+  }
+
+  g_signal_connect (proxy,
+		    "g-properties-changed",
+		    G_CALLBACK(brightness_changed_cb),
+		    adj);
+
+  brightness_proxy = proxy;
+}
+
+
+
+void
+brightness_set_cb (GDBusProxy *proxy, GAsyncResult *res, gpointer unused)
+{
+  GError *err = NULL;
+  GVariant *var;
+
+  var = g_dbus_proxy_call_finish (proxy, res, &err);
+
+  if (err) {
+    g_warning("Could not set brightness %s", err->message);
+    g_error_free(err);
+    return;
+  }
+
+  if (var)
+    g_variant_unref (var);
+}
+
+
+void
+brightness_set (int brightness)
+{
+  g_return_if_fail (brightness_proxy);
+
+  /* Don't let the display go completely dark for now */
+  if (brightness < 10)
+    brightness = 10;
+
+  g_dbus_proxy_call (brightness_proxy,
+                     "org.freedesktop.DBus.Properties.Set",
+                     g_variant_new (
+                         "(ssv)",
+			 "org.gnome.SettingsDaemon.Power.Screen",
+			 "Brightness",
+			 g_variant_new ("i", brightness)),
+                     G_DBUS_CALL_FLAGS_NONE,
+                     2000,
+		     NULL,
+                     (GAsyncReadyCallback)brightness_set_cb,
+		     NULL);
+}
diff --git a/src/settings/brightness.h b/src/settings/brightness.h
new file mode 100644
index 0000000..c3f265f
--- /dev/null
+++ b/src/settings/brightness.h
@@ -0,0 +1,9 @@
+/*
+ * Copyright (C) 2018 Purism SPC
+ *
+ * SPDX-License-Identifier: GPL-3+
+ * Author: Guido Günther <agx@sigxcpu.org>
+ */
+
+void brightness_init ();
+void brightness_set (int brightness);
diff --git a/src/style.css b/src/style.css
index 4ebe35e..19ead20 100644
--- a/src/style.css
+++ b/src/style.css
@@ -2,7 +2,7 @@
     font: 15px lato-regular;
 }
 
-.phosh-panel-btn-top-left {
+.phosh-panel-btn {
     background-image: none;
     border-width: 0;
 }
diff --git a/src/ui/settings-menu.ui b/src/ui/settings-menu.ui
new file mode 100644
index 0000000..8bb2d20
--- /dev/null
+++ b/src/ui/settings-menu.ui
@@ -0,0 +1,97 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- Generated with glade 3.20.2 -->
+<interface>
+  <requires lib="gtk+" version="3.20"/>
+  <template class="PhoshSettings" parent="PhoshMenu">
+    <property name="width_request">200</property>
+    <property name="can_focus">False</property>
+    <child>
+      <object class="GtkBox">
+        <property name="visible">True</property>
+        <property name="can_focus">False</property>
+        <property name="margin_left">10</property>
+        <property name="margin_right">10</property>
+        <property name="margin_top">10</property>
+        <property name="margin_bottom">10</property>
+        <property name="orientation">vertical</property>
+        <child>
+          <object class="GtkBox">
+            <property name="visible">True</property>
+            <property name="can_focus">False</property>
+            <child>
+              <object class="GtkImage">
+                <property name="visible">True</property>
+                <property name="can_focus">False</property>
+                <property name="margin_right">10</property>
+                <property name="icon_name">audio-speakers-symbolic</property>
+              </object>
+              <packing>
+                <property name="expand">False</property>
+                <property name="fill">True</property>
+                <property name="position">0</property>
+              </packing>
+            </child>
+            <child>
+              <object class="GtkScale" id="scale_volume">
+                <property name="visible">True</property>
+                <property name="can_focus">True</property>
+                <property name="round_digits">1</property>
+                <property name="draw_value">False</property>
+              </object>
+              <packing>
+                <property name="expand">True</property>
+                <property name="fill">True</property>
+                <property name="position">1</property>
+              </packing>
+            </child>
+          </object>
+          <packing>
+            <property name="expand">False</property>
+            <property name="fill">True</property>
+            <property name="position">0</property>
+          </packing>
+        </child>
+        <child>
+          <object class="GtkBox">
+            <property name="visible">True</property>
+            <property name="can_focus">False</property>
+            <child>
+              <object class="GtkImage">
+                <property name="visible">True</property>
+                <property name="can_focus">False</property>
+                <property name="margin_right">10</property>
+                <property name="icon_name">display-brightness-symbolic</property>
+              </object>
+              <packing>
+                <property name="expand">False</property>
+                <property name="fill">True</property>
+                <property name="position">0</property>
+              </packing>
+            </child>
+            <child>
+              <object class="GtkScale" id="scale_brightness">
+                <property name="visible">True</property>
+                <property name="can_focus">True</property>
+                <property name="round_digits">1</property>
+                <property name="draw_value">False</property>
+              </object>
+              <packing>
+                <property name="expand">True</property>
+                <property name="fill">True</property>
+                <property name="position">1</property>
+              </packing>
+            </child>
+          </object>
+          <packing>
+            <property name="expand">False</property>
+            <property name="fill">True</property>
+            <property name="position">1</property>
+          </packing>
+        </child>
+      </object>
+    </child>
+    <child type="titlebar">
+      <placeholder/>
+    </child>
+  </template>
+</interface>
diff --git a/src/ui/top-panel.ui b/src/ui/top-panel.ui
index f4c470e..c470706 100644
--- a/src/ui/top-panel.ui
+++ b/src/ui/top-panel.ui
@@ -10,14 +10,16 @@
         <property name="can_focus">False</property>
         <property name="homogeneous">True</property>
         <child>
-          <object class="GtkButton" id="btn_topleft">
-            <property name="label"></property>
+          <object class="GtkButton" id="btn_favorites">
             <property name="visible">True</property>
             <property name="can_focus">True</property>
             <property name="receives_default">True</property>
-            <property name="xalign">0.1</property>
+            <property name="xalign">0.10000000149011612</property>
             <property name="yalign">0.5</property>
             <property name="always_show_image">True</property>
+            <style>
+              <class name="phosh-panel-btn"/>
+            </style>
           </object>
           <packing>
             <property name="expand">False</property>
@@ -39,11 +41,16 @@
               <placeholder/>
             </child>
             <child>
-              <object class="GtkLabel" id="label_clock">
-                <property name="visible">True</property>
-                <property name="can_focus">False</property>
+              <object class="GtkButton" id="btn_settings">
                 <property name="label" translatable="yes">00:00</property>
-                <property name="xalign">0.9</property>
+                <property name="visible">True</property>
+                <property name="can_focus">True</property>
+                <property name="receives_default">True</property>
+                <property name="xalign">0.89999997615814209</property>
+                <property name="yalign">0.5</property>
+                <style>
+                  <class name="phosh-panel-btn"/>
+                </style>
               </object>
               <packing>
                 <property name="expand">True</property>
-- 
GitLab