[gnome-flashback] system-indicators: add input source indicator
- From: Alberts Muktupāvels <muktupavels src gnome org>
- To: commits-list gnome org
- Cc:
- Subject: [gnome-flashback] system-indicators: add input source indicator
- Date: Sun, 22 Dec 2019 22:06:27 +0000 (UTC)
commit ca1d4e44ade545c43f28512e24075a8ab3fc8a58
Author: Alberts Muktupāvels <alberts muktupavels gmail com>
Date: Sun Dec 22 23:58:00 2019 +0200
system-indicators: add input source indicator
data/schemas/Makefile.am | 12 +
...back.system-indicators.input-source.gschema.xml | 31 +
po/POTFILES.in | 2 +
system-indicators/Makefile.am | 4 +
system-indicators/si-applet.c | 27 +-
system-indicators/si-input-source.c | 1112 ++++++++++++++++++++
system-indicators/si-input-source.h | 34 +
7 files changed, 1220 insertions(+), 2 deletions(-)
---
diff --git a/data/schemas/Makefile.am b/data/schemas/Makefile.am
index d1d2fed..633b9db 100644
--- a/data/schemas/Makefile.am
+++ b/data/schemas/Makefile.am
@@ -16,6 +16,12 @@ gsettings_SCHEMAS = \
org.gnome.gnome-flashback.input-sources.gschema.xml \
$(NULL)
+if WITH_SYSTEM_INDICATORS
+gsettings_SCHEMAS += \
+ org.gnome.gnome-flashback.system-indicators.input-source.gschema.xml \
+ $(NULL)
+endif
+
@GSETTINGS_RULES@
EXTRA_DIST = \
@@ -23,6 +29,12 @@ EXTRA_DIST = \
$(gsettings_SCHEMAS) \
$(NULL)
+if !WITH_SYSTEM_INDICATORS
+EXTRA_DIST += \
+ org.gnome.gnome-flashback.system-indicators.input-source.gschema.xml \
+ $(NULL)
+endif
+
CLEANFILES = \
*.gschema.valid \
$(NULL)
diff --git a/data/schemas/org.gnome.gnome-flashback.system-indicators.input-source.gschema.xml
b/data/schemas/org.gnome.gnome-flashback.system-indicators.input-source.gschema.xml
new file mode 100644
index 0000000..3783930
--- /dev/null
+++ b/data/schemas/org.gnome.gnome-flashback.system-indicators.input-source.gschema.xml
@@ -0,0 +1,31 @@
+<schemalist gettext-domain="gnome-flashback">
+ <schema id="org.gnome.gnome-flashback.system-indicators.input-source">
+
+ <key name="generate-symbolic-icon" type="b">
+ <default>true</default>
+ <summary>Generate symbolic icon</summary>
+ </key>
+
+ <key name="icon-bg-color" type="s">
+ <default>'#FFFFFF'</default>
+ <summary>Icon background color</summary>
+ </key>
+
+ <key name="icon-fg-color" type="s">
+ <default>'#000000'</default>
+ <summary>Icon foreground color</summary>
+ </key>
+
+ <key name="icon-font-family" type="s">
+ <default>'Cantarell'</default>
+ <summary>Icon font family</summary>
+ </key>
+
+ <key name="icon-font-weight" type="i">
+ <range min="100" max="1000"/>
+ <default>600</default>
+ <summary>Icon font weight</summary>
+ </key>
+
+ </schema>
+</schemalist>
diff --git a/po/POTFILES.in b/po/POTFILES.in
index 514ee40..48d78b5 100644
--- a/po/POTFILES.in
+++ b/po/POTFILES.in
@@ -11,6 +11,7 @@ data/schemas/org.gnome.gnome-flashback.desktop.gschema.xml
data/schemas/org.gnome.gnome-flashback.desktop.background.gschema.xml
data/schemas/org.gnome.gnome-flashback.desktop.icons.gschema.xml
data/schemas/org.gnome.gnome-flashback.input-sources.gschema.xml
+data/schemas/org.gnome.gnome-flashback.system-indicators.input-source.gschema.xml
data/ui/gf-confirm-display-change-dialog.ui
data/xsessions/gnome-flashback-compiz.desktop.in.in
data/xsessions/gnome-flashback-metacity.desktop.in.in
@@ -44,4 +45,5 @@ gnome-flashback/libsound-applet/gf-sound-applet.c
gnome-flashback/libsound-applet/gvc-channel-bar.c
gnome-flashback/libsound-applet/gvc/gvc-mixer-control.c
gnome-flashback/libsound-applet/gvc-stream-status-icon.c
+system-indicators/si-input-source.c
system-indicators/si-module.c
diff --git a/system-indicators/Makefile.am b/system-indicators/Makefile.am
index 6e4f599..54d7440 100644
--- a/system-indicators/Makefile.am
+++ b/system-indicators/Makefile.am
@@ -8,6 +8,7 @@ system_indicators_la_CPPFLAGS = \
-DG_LOG_DOMAIN=\"system-indicators\" \
-DG_LOG_USE_STRUCTURED=1 \
-DLOCALE_DIR=\"$(localedir)\" \
+ -I$(top_srcdir) \
$(AM_CPPFLAGS) \
$(NULL)
@@ -22,12 +23,15 @@ system_indicators_la_SOURCES = \
si-applet.h \
si-indicator.c \
si-indicator.h \
+ si-input-source.c \
+ si-input-source.h \
si-menu-bar.c \
si-menu-bar.h \
si-module.c \
$(NULL)
system_indicators_la_LIBADD = \
+ $(top_builddir)/dbus/libdbus.la \
$(SYSTEM_INDICATORS_LIBS) \
$(NULL)
diff --git a/system-indicators/si-applet.c b/system-indicators/si-applet.c
index 1e38cad..0025484 100644
--- a/system-indicators/si-applet.c
+++ b/system-indicators/si-applet.c
@@ -18,13 +18,16 @@
#include "config.h"
#include "si-applet.h"
+#include "si-input-source.h"
#include "si-menu-bar.h"
struct _SiApplet
{
- GpApplet parent;
+ GpApplet parent;
- GtkWidget *menu_bar;
+ GtkWidget *menu_bar;
+
+ SiIndicator *input_source;
};
G_DEFINE_TYPE (SiApplet, si_applet, GP_TYPE_APPLET)
@@ -32,6 +35,8 @@ G_DEFINE_TYPE (SiApplet, si_applet, GP_TYPE_APPLET)
static void
setup_applet (SiApplet *self)
{
+ GtkWidget *item;
+
self->menu_bar = si_menu_bar_new ();
gtk_container_add (GTK_CONTAINER (self), self->menu_bar);
gtk_widget_show (self->menu_bar);
@@ -49,6 +54,11 @@ setup_applet (SiApplet *self)
"position",
G_BINDING_DEFAULT |
G_BINDING_SYNC_CREATE);
+
+ self->input_source = si_input_source_new (GP_APPLET (self));
+
+ item = si_indicator_get_menu_item (self->input_source);
+ gtk_menu_shell_append (GTK_MENU_SHELL (self->menu_bar), item);
}
static void
@@ -58,6 +68,18 @@ si_applet_constructed (GObject *object)
setup_applet (SI_APPLET (object));
}
+static void
+si_applet_dispose (GObject *object)
+{
+ SiApplet *self;
+
+ self = SI_APPLET (object);
+
+ g_clear_object (&self->input_source);
+
+ G_OBJECT_CLASS (si_applet_parent_class)->dispose (object);
+}
+
static void
si_applet_class_init (SiAppletClass *self_class)
{
@@ -66,6 +88,7 @@ si_applet_class_init (SiAppletClass *self_class)
object_class = G_OBJECT_CLASS (self_class);
object_class->constructed = si_applet_constructed;
+ object_class->dispose = si_applet_dispose;
}
static void
diff --git a/system-indicators/si-input-source.c b/system-indicators/si-input-source.c
new file mode 100644
index 0000000..915820a
--- /dev/null
+++ b/system-indicators/si-input-source.c
@@ -0,0 +1,1112 @@
+/*
+ * Copyright (C) 2019 Alberts Muktupāvels
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "config.h"
+#include "si-input-source.h"
+
+#include <glib/gi18n-lib.h>
+#include <libgnome-panel/gp-image-menu-item.h>
+#include <locale.h>
+#include <utime.h>
+
+#include "dbus/gf-input-sources-gen.h"
+
+struct _SiInputSource
+{
+ SiIndicator parent;
+
+ char *icon_theme_path;
+
+ GtkWidget *menu;
+
+ guint name_id;
+
+ GSettings *settings;
+
+ GCancellable *cancellable;
+
+ GfInputSourcesGen *input_sources;
+
+ char *icon_text;
+};
+
+G_DEFINE_TYPE (SiInputSource, si_input_source, SI_TYPE_INDICATOR)
+
+static GString *
+cairo_path_to_string (cairo_path_t *path,
+ cairo_matrix_t *matrix)
+{
+ char *locale;
+ GString *string;
+ gint i;
+
+ locale = g_strdup (setlocale (LC_NUMERIC, NULL));
+ setlocale (LC_NUMERIC, "C");
+
+ string = g_string_new (NULL);
+ for (i = 0; i < path->num_data; i += path->data[i].header.length)
+ {
+ cairo_path_data_t *data;
+ gdouble x1, y1;
+ gdouble x2, y2;
+ gdouble x3, y3;
+
+ data = &path->data[i];
+
+ switch (data->header.type)
+ {
+ case CAIRO_PATH_MOVE_TO:
+ x1 = data[1].point.x;
+ y1 = data[1].point.y;
+
+ cairo_matrix_transform_point (matrix, &x1, &y1);
+
+ g_string_append_printf (string, "M %f,%f ", x1, y1);
+ break;
+
+ case CAIRO_PATH_LINE_TO:
+ x1 = data[1].point.x;
+ y1 = data[1].point.y;
+
+ cairo_matrix_transform_point (matrix, &x1, &y1);
+
+ g_string_append_printf (string, "L %f,%f ", x1, y1);
+ break;
+
+ case CAIRO_PATH_CURVE_TO:
+ x1 = data[1].point.x;
+ y1 = data[1].point.y;
+ x2 = data[2].point.x;
+ y2 = data[2].point.y;
+ x3 = data[3].point.x;
+ y3 = data[3].point.y;
+
+ cairo_matrix_transform_point (matrix, &x1, &y1);
+ cairo_matrix_transform_point (matrix, &x2, &y2);
+ cairo_matrix_transform_point (matrix, &x3, &y3);
+
+ g_string_append_printf (string,
+ "C %f,%f %f,%f %f,%f ",
+ x1,
+ y1,
+ x2,
+ y2,
+ x3,
+ y3);
+ break;
+
+ case CAIRO_PATH_CLOSE_PATH:
+ g_string_append (string, "Z ");
+ break;
+
+ default:
+ break;
+ }
+ }
+
+ setlocale (LC_NUMERIC, locale);
+ g_free (locale);
+
+ return string;
+}
+
+static PangoLayout *
+get_pango_layout (const char *text,
+ const char *font_family,
+ int font_weight,
+ int font_size)
+{
+ GdkScreen *screen;
+ PangoContext *context;
+ PangoFontDescription *font_desc;
+ PangoLayout *layout;
+
+ screen = gdk_screen_get_default ();
+ context = gdk_pango_context_get_for_screen (screen);
+ font_desc = pango_font_description_new ();
+
+ pango_font_description_set_family (font_desc, font_family);
+ pango_font_description_set_absolute_size (font_desc, font_size * PANGO_SCALE);
+ pango_font_description_set_weight (font_desc, font_weight);
+ pango_font_description_set_stretch (font_desc, PANGO_STRETCH_NORMAL);
+ pango_font_description_set_style (font_desc, PANGO_STYLE_NORMAL);
+ pango_font_description_set_variant (font_desc, PANGO_VARIANT_NORMAL);
+
+ layout = pango_layout_new (context);
+ g_object_unref (context);
+
+ pango_layout_set_text (layout, text, -1);
+ pango_layout_set_font_description (layout, font_desc);
+ pango_font_description_free (font_desc);
+
+ return layout;
+}
+
+static cairo_path_t *
+get_cairo_path (const char *text,
+ const char *font_family,
+ int font_weight,
+ int font_size,
+ cairo_matrix_t *matrix)
+{
+ PangoLayout *layout;
+ cairo_surface_t *surface;
+ cairo_t *cr;
+ int width;
+ int height;
+ double scale;
+ cairo_path_t *path;
+
+ layout = get_pango_layout (text, font_family, font_weight, font_size);
+ surface = cairo_image_surface_create (CAIRO_FORMAT_ARGB32, 16, 16);
+ cr = cairo_create (surface);
+
+ pango_layout_get_pixel_size (layout, &width, &height);
+
+ scale = MIN (1.0, MIN (14.0 / width, 14.0 / height));
+ cairo_scale (cr, scale, scale);
+
+ cairo_move_to (cr, (16 - width * scale) / 2.0, (16 - height * scale) / 2.0);
+
+ pango_cairo_layout_path (cr, layout);
+ path = cairo_copy_path (cr);
+ cairo_get_matrix (cr, matrix);
+
+ cairo_destroy (cr);
+ cairo_surface_destroy (surface);
+ g_object_unref (layout);
+
+ return path;
+}
+
+static char *
+generate_path_description (const char *text,
+ const char *font_family,
+ int font_weight,
+ int font_size)
+{
+ cairo_path_t *path;
+ cairo_matrix_t matrix;
+ GString *string;
+
+ path = get_cairo_path (text, font_family, font_weight, font_size, &matrix);
+ string = cairo_path_to_string (path, &matrix);
+ cairo_path_destroy (path);
+
+ return g_string_free (string, FALSE);
+}
+
+static GString *
+generate_svg (const char *text,
+ const char *font_family,
+ int font_weight,
+ int font_size,
+ const char *bg_color,
+ const char *fg_color,
+ gboolean symbolic)
+{
+ char *path_d;
+ GString *svg;
+
+ path_d = generate_path_description (text, font_family, font_weight, font_size);
+ svg = g_string_new ("<?xml version='1.0' encoding='utf-8' standalone='no'?>");
+
+ g_string_append (svg,
+ "<svg xmlns='http://www.w3.org/2000/svg' "
+ "width='16' height='16' viewBox='0 0 16 16'>");
+
+ if (symbolic)
+ {
+ g_string_append (svg, "<defs><mask id='m'>");
+ g_string_append (svg,
+ "<rect width='16' height='16' "
+ "style='fill:#ffffff!important'/>");
+
+ g_string_append_printf (svg,
+ "<path d='%s' style='fill:#000000!important'/>",
+ path_d);
+
+ g_string_append (svg, "</mask></defs>");
+ }
+
+ g_string_append_printf (svg,
+ "<rect x='0' y='0' width='16' height='16' "
+ "rx='2.0' ry='2.0' mask='%s' style='fill:%s;'/>",
+ symbolic ? "url(#m)" : "none",
+ symbolic ? "#bebebe" : bg_color);
+
+ if (!symbolic)
+ {
+ g_string_append_printf (svg,
+ "<path d='%s' style='fill:%s'/>",
+ path_d,
+ fg_color);
+ }
+
+ g_free (path_d);
+
+ return g_string_append (svg, "</svg>");
+}
+
+static void
+ensure_file_exists (const char *icon_theme_path,
+ const char *icon_name,
+ const char *text,
+ const char *font_family,
+ int font_weight,
+ int font_size,
+ const char *bg_color,
+ const char *fg_color,
+ gboolean symbolic)
+{
+ char *filename;
+ char *path;
+ GFile *file;
+ GFile *parent;
+ GString *svg;
+
+ filename = g_strdup_printf ("%s.svg", icon_name);
+ path = g_build_filename (icon_theme_path,
+ "hicolor",
+ "scalable",
+ "status",
+ filename,
+ NULL);
+
+ g_free (filename);
+
+ if (g_file_test (filename, G_FILE_TEST_EXISTS))
+ {
+ g_free (path);
+ return;
+ }
+
+ file = g_file_new_for_path (path);
+ g_free (path);
+
+ parent = g_file_get_parent (file);
+
+ svg = generate_svg (text,
+ font_family,
+ font_weight,
+ font_size,
+ bg_color,
+ fg_color,
+ symbolic);
+
+ g_file_make_directory_with_parents (parent, NULL, NULL);
+ g_file_replace_contents (file,
+ svg->str,
+ svg->len,
+ NULL,
+ FALSE,
+ G_FILE_CREATE_NONE,
+ NULL,
+ NULL,
+ NULL);
+
+ utime (icon_theme_path, NULL);
+ gtk_icon_theme_rescan_if_needed (gtk_icon_theme_get_default ());
+
+ g_string_free (svg, TRUE);
+ g_object_unref (parent);
+ g_object_unref (file);
+}
+
+static gchar *
+generate_icon_name (const char *text,
+ const char *font_family,
+ int font_weight,
+ int font_size,
+ const char *bg_color,
+ const char *fg_color,
+ gboolean symbolic)
+{
+ char *str;
+ char *hash;
+ GString *icon_name;
+
+ str = g_strdup_printf ("%s-%s-%d-%d-%s-%s",
+ text,
+ font_family,
+ font_weight,
+ font_size,
+ bg_color,
+ fg_color);
+
+ hash = g_compute_checksum_for_string (G_CHECKSUM_MD5, str, -1);
+ g_free (str);
+
+ icon_name = g_string_new (hash);
+ g_free (hash);
+
+ if (symbolic)
+ g_string_append (icon_name, "-symbolic");
+
+ return g_string_free (icon_name, FALSE);
+}
+
+static gchar *
+get_icon_name (SiInputSource *self)
+{
+ gboolean symbolic;
+ char *font_family;
+ int font_weight;
+ int font_size;
+ char *bg_color;
+ char *fg_color;
+ char *icon_name;
+
+ symbolic = g_settings_get_boolean (self->settings, "generate-symbolic-icon");
+ font_family = g_settings_get_string (self->settings, "icon-font-family");
+ font_weight = g_settings_get_int (self->settings, "icon-font-weight");
+ font_size = 8;
+ bg_color = g_settings_get_string (self->settings, "icon-bg-color");
+ fg_color = g_settings_get_string (self->settings, "icon-fg-color");
+
+ icon_name = generate_icon_name (self->icon_text,
+ font_family,
+ font_weight,
+ font_size,
+ bg_color,
+ fg_color,
+ symbolic);
+
+ ensure_file_exists (self->icon_theme_path,
+ icon_name,
+ self->icon_text,
+ font_family,
+ font_weight,
+ font_size,
+ bg_color,
+ fg_color,
+ symbolic);
+
+ g_free (font_family);
+ g_free (bg_color);
+ g_free (fg_color);
+
+ return icon_name;
+}
+
+static void
+update_icon (SiInputSource *self)
+{
+ char *icon_name;
+
+ icon_name = get_icon_name (self);
+
+ si_indicator_set_icon_name (SI_INDICATOR (self), icon_name);
+ g_free (icon_name);
+}
+
+static void
+update_indicator_icon (SiInputSource *self,
+ GVariant *current_source)
+{
+ GVariantDict dict;
+ const char *icon_text;
+ const char *tooltip;
+ GtkWidget *item;
+
+ g_variant_dict_init (&dict, current_source);
+
+ if (!g_variant_dict_lookup (&dict, "icon-text", "&s", &icon_text))
+ icon_text = NULL;
+
+ if (!g_variant_dict_lookup (&dict, "tooltip", "&s", &tooltip))
+ tooltip = NULL;
+
+ if (g_strcmp0 (self->icon_text, icon_text) != 0)
+ {
+ g_clear_pointer (&self->icon_text, g_free);
+ self->icon_text = g_strdup (icon_text);
+
+ update_icon (self);
+ }
+
+ item = si_indicator_get_menu_item (SI_INDICATOR (self));
+ gtk_widget_set_tooltip_text (item , tooltip);
+}
+
+static void
+settings_changed_cb (GSettings *settings,
+ const char *key,
+ SiInputSource *self)
+{
+ update_icon (self);
+}
+
+static void
+activate_cb (GObject *object,
+ GAsyncResult *res,
+ gpointer user_data)
+{
+ GError *error;
+
+ error = NULL;
+ gf_input_sources_gen_call_activate_finish (GF_INPUT_SOURCES_GEN (object),
+ res,
+ &error);
+
+ if (error != NULL)
+ {
+ if (!g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED))
+ g_warning ("%s", error->message);
+
+ g_error_free (error);
+ return;
+ }
+}
+
+static void
+item_activate_cb (GtkMenuItem *item,
+ SiInputSource *self)
+{
+ guint *index;
+
+ if (!gtk_check_menu_item_get_active (GTK_CHECK_MENU_ITEM (item)))
+ return;
+
+ g_cancellable_cancel (self->cancellable);
+
+ g_object_unref (self->cancellable);
+ self->cancellable = g_cancellable_new ();
+
+ index = g_object_get_data (G_OBJECT (item), "index");
+
+ gf_input_sources_gen_call_activate (self->input_sources,
+ *index,
+ self->cancellable,
+ activate_cb,
+ self);
+}
+
+static int
+append_input_sources (SiInputSource *self,
+ GVariant *input_sources)
+{
+ GVariantIter iter;
+ GSList *group;
+ GVariant *child;
+
+ g_variant_iter_init (&iter, input_sources);
+
+ group = NULL;
+ while ((child = g_variant_iter_next_value (&iter)))
+ {
+ guint index;
+ const char *short_name;
+ const char *display_name;
+ gboolean active;
+ GtkWidget *item;
+ GtkWidget *hbox;
+ GtkWidget *label;
+ guint *data;
+
+ g_variant_get (child,
+ "(u&s&sb)",
+ &index,
+ &short_name,
+ &display_name,
+ &active);
+
+ item = gtk_radio_menu_item_new (group);
+ gtk_menu_shell_append (GTK_MENU_SHELL (self->menu), item);
+ gtk_widget_show (item);
+
+ group = gtk_radio_menu_item_get_group (GTK_RADIO_MENU_ITEM (item));
+ gtk_check_menu_item_set_active (GTK_CHECK_MENU_ITEM (item), active);
+
+ hbox = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 0);
+ gtk_container_add (GTK_CONTAINER (item), hbox);
+ gtk_widget_show (hbox);
+
+ label = gtk_label_new (display_name);
+ gtk_box_pack_start (GTK_BOX (hbox), label, TRUE, TRUE, 0);
+ gtk_label_set_xalign (GTK_LABEL (label), 0.0);
+ gtk_widget_show (label);
+
+ label = gtk_label_new (short_name);
+ gtk_box_pack_start (GTK_BOX (hbox), label, FALSE, FALSE, 10);
+ gtk_label_set_xalign (GTK_LABEL (label), 0.0);
+ gtk_widget_show (label);
+
+ data = g_new0 (guint, 1);
+ g_object_set_data_full (G_OBJECT (item), "index", data, g_free);
+ *data = index;
+
+ g_signal_connect (item, "activate", G_CALLBACK (item_activate_cb), self);
+
+ g_variant_unref (child);
+ }
+
+ return g_variant_iter_n_children (&iter);
+}
+
+static void
+activate_property_cb (GObject *object,
+ GAsyncResult *res,
+ gpointer user_data)
+{
+ GError *error;
+
+ error = NULL;
+ gf_input_sources_gen_call_activate_property_finish (GF_INPUT_SOURCES_GEN (object),
+ res,
+ &error);
+
+ if (error != NULL)
+ {
+ if (!g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED))
+ g_warning ("%s", error->message);
+
+ g_error_free (error);
+ return;
+ }
+}
+
+static void
+property_activate_cb (GtkMenuItem *item,
+ SiInputSource *self)
+{
+ const char *key;
+
+ g_cancellable_cancel (self->cancellable);
+
+ g_object_unref (self->cancellable);
+ self->cancellable = g_cancellable_new ();
+
+ key = g_object_get_data (G_OBJECT (item), "key");
+
+ gf_input_sources_gen_call_activate_property (self->input_sources,
+ key,
+ self->cancellable,
+ activate_property_cb,
+ self);
+}
+
+static void
+append_properties_to_menu (SiInputSource *self,
+ GVariantIter *iter,
+ GtkWidget *menu)
+{
+ GVariant *child;
+
+ while ((child = g_variant_iter_next_value (iter)))
+ {
+ const char *key;
+ GVariant *ret;
+ GVariantDict *dict;
+ const char *type;
+ const char *label;
+ const char *tooltip;
+ GtkWidget *item;
+
+ g_variant_get (child, "(&s@a{sv})", &key, &ret);
+
+ dict = g_variant_dict_new (ret);
+ g_variant_unref (ret);
+
+ if (!g_variant_dict_lookup (dict, "type", "&s", &type))
+ {
+ g_variant_dict_unref (dict);
+ g_variant_unref (child);
+ continue;
+ }
+
+ if (!g_variant_dict_lookup (dict, "label", "&s", &label))
+ label = "";
+
+ if (!g_variant_dict_lookup (dict, "tooltip", "&s", &tooltip))
+ tooltip = NULL;
+
+ if (g_strcmp0 (type, "toggle") == 0)
+ {
+ item = gtk_check_menu_item_new ();
+ }
+ else if (g_strcmp0 (type, "radio") == 0)
+ {
+ item = gtk_radio_menu_item_new (NULL);
+ }
+ else if (g_strcmp0 (type, "separator") == 0)
+ {
+ item = gtk_separator_menu_item_new ();
+ }
+ else
+ {
+ item = gtk_menu_item_new ();
+ }
+
+ gtk_menu_item_set_label (GTK_MENU_ITEM (item), label);
+ gtk_widget_set_tooltip_text (item , tooltip);
+
+ if (g_strcmp0 (type, "menu") == 0)
+ {
+ GtkWidget *submenu;
+ GVariant *variant;
+ GVariantIter subiter;
+
+ submenu = gtk_menu_new ();
+ gtk_menu_item_set_submenu (GTK_MENU_ITEM (item), submenu);
+
+ variant = g_variant_dict_lookup_value (dict,
+ "menu",
+ G_VARIANT_TYPE ("a(sa{sv})"));
+
+ if (variant != NULL)
+ {
+ g_variant_iter_init (&subiter, variant);
+ append_properties_to_menu (self, &subiter, submenu);
+ g_variant_unref (variant);
+ }
+ else
+ {
+ gtk_widget_hide (item);
+ }
+ }
+ else if (g_strcmp0 (type, "toggle") == 0 ||
+ g_strcmp0 (type, "radio") == 0)
+ {
+ const char *state;
+
+ if (!g_variant_dict_lookup (dict, "state", "&s", &state))
+ state = NULL;
+
+ if (g_strcmp0 (state, "checked") == 0)
+ gtk_check_menu_item_set_active (GTK_CHECK_MENU_ITEM (item), TRUE);
+ }
+
+ if (g_strcmp0 (type, "menu") != 0 &&
+ g_strcmp0 (type, "separator") != 0)
+ {
+ g_object_set_data_full (G_OBJECT (item),
+ "key",
+ g_strdup (key),
+ g_free);
+
+ g_signal_connect (item,
+ "activate",
+ G_CALLBACK (property_activate_cb),
+ self);
+ }
+
+ gtk_menu_shell_append (GTK_MENU_SHELL (self->menu), item);
+ gtk_widget_show (item);
+
+ g_variant_dict_unref (dict);
+ g_variant_unref (child);
+ }
+}
+
+static int
+append_properties (SiInputSource *self,
+ GVariant *current_source)
+{
+ GVariantDict dict;
+ GVariant *properties;
+ GVariantIter iter;
+ int items;
+
+ g_variant_dict_init (&dict, current_source);
+
+ properties = g_variant_dict_lookup_value (&dict,
+ "properties",
+ G_VARIANT_TYPE ("a(sa{sv})"));
+
+ if (properties == NULL)
+ return 0;
+
+ items = g_variant_iter_init (&iter, properties);
+
+ if (items > 0)
+ {
+ GtkWidget *separator;
+
+ separator = gtk_separator_menu_item_new ();
+ gtk_menu_shell_append (GTK_MENU_SHELL (self->menu), separator);
+ gtk_widget_show (separator);
+
+ append_properties_to_menu (self, &iter, self->menu);
+ }
+
+ g_variant_unref (properties);
+
+ return items;
+}
+
+static void
+watch_child (GPid pid,
+ gint status,
+ gpointer user_data)
+{
+}
+
+static void
+spawn_keyboard_display (const char *description)
+{
+ char **argv;
+ GSpawnFlags flags;
+ GPid pid;
+ GError *error;
+
+ argv = g_new0 (gchar *, 4);
+ flags = G_SPAWN_SEARCH_PATH | G_SPAWN_DO_NOT_REAP_CHILD;
+ error = NULL;
+
+ argv[0] = g_strdup ("gkbd-keyboard-display");
+ argv[1] = g_strdup ("-l");
+ argv[2] = g_strdup (description);
+ argv[3] = NULL;
+
+ g_spawn_async (NULL, argv, NULL, flags, NULL, NULL, &pid, &error);
+ g_strfreev (argv);
+
+ if (error != NULL)
+ {
+ g_warning ("%s", error->message);
+ g_error_free (error);
+
+ return;
+ }
+
+ g_child_watch_add (pid, watch_child, NULL);
+}
+
+static void
+show_layout_cb (GtkMenuItem *menuitem,
+ SiInputSource *self)
+{
+ const char *description;
+
+ description = g_object_get_data (G_OBJECT (menuitem), "description");
+ if (description == NULL)
+ return;
+
+ spawn_keyboard_display (description);
+}
+
+static void
+append_show_layout_item (SiInputSource *self,
+ GVariant *current_source)
+{
+ GVariantDict dict;
+ const char *layout;
+ const char *layout_variant;
+ GtkWidget *item;
+
+ g_variant_dict_init (&dict, current_source);
+
+ if (!g_variant_dict_lookup (&dict, "layout", "&s", &layout))
+ layout = NULL;
+
+ if (!g_variant_dict_lookup (&dict, "layout-variant", "&s", &layout_variant))
+ layout_variant = NULL;
+
+ item = gtk_menu_item_new_with_label (_("Show Keyboard Layout"));
+ gtk_menu_shell_append (GTK_MENU_SHELL (self->menu), item);
+ gtk_widget_show (item);
+
+ g_signal_connect (item, "activate", G_CALLBACK (show_layout_cb), self);
+
+ if (layout != NULL && *layout != '\0')
+ {
+ char *description;
+
+ if (layout_variant != NULL && *layout_variant != '\0')
+ description = g_strdup_printf ("%s\t%s", layout, layout_variant);
+ else
+ description = g_strdup (layout);
+
+ g_object_set_data_full (G_OBJECT (item),
+ "description",
+ description,
+ g_free);
+ }
+ else
+ {
+ gtk_widget_set_sensitive (item, FALSE);
+ }
+}
+
+static void
+remove_item_cb (GtkWidget *widget,
+ gpointer data)
+{
+ gtk_widget_destroy (widget);
+}
+
+static int
+update_indicator_menu (SiInputSource *self,
+ GVariant *input_sources,
+ GVariant *current_source)
+{
+ int count;
+ GtkWidget *separator;
+
+ gtk_container_foreach (GTK_CONTAINER (self->menu), remove_item_cb, NULL);
+
+ count = append_input_sources (self, input_sources);
+ count += append_properties (self, current_source);
+
+ separator = gtk_separator_menu_item_new ();
+ gtk_menu_shell_append (GTK_MENU_SHELL (self->menu), separator);
+ gtk_widget_show (separator);
+
+ append_show_layout_item (self, current_source);
+
+ return count;
+}
+
+static void
+update_indicator (SiInputSource *self,
+ GVariant *input_sources,
+ GVariant *current_source)
+{
+ int count;
+ GtkWidget *menu_item;
+
+ update_indicator_icon (self, current_source);
+ count = update_indicator_menu (self, input_sources, current_source);
+
+ menu_item = si_indicator_get_menu_item (SI_INDICATOR (self));
+ gtk_widget_set_visible (menu_item, count > 1);
+}
+
+static void
+get_input_sources_cb (GObject *object,
+ GAsyncResult *res,
+ gpointer user_data)
+{
+ GError *error;
+ GVariant *input_sources;
+ GVariant *current_source;
+ SiInputSource *self;
+
+ error = NULL;
+ gf_input_sources_gen_call_get_input_sources_finish (GF_INPUT_SOURCES_GEN (object),
+ &input_sources,
+ ¤t_source,
+ res,
+ &error);
+
+ if (error != NULL)
+ {
+ if (!g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED))
+ g_warning ("%s", error->message);
+
+ g_error_free (error);
+ return;
+ }
+
+ self = SI_INPUT_SOURCE (user_data);
+
+ update_indicator (self, input_sources, current_source);
+
+ g_variant_unref (input_sources);
+ g_variant_unref (current_source);
+}
+
+static void
+changed_cb (GfInputSourcesGen *input_sources,
+ SiInputSource *self)
+{
+ g_cancellable_cancel (self->cancellable);
+
+ g_object_unref (self->cancellable);
+ self->cancellable = g_cancellable_new ();
+
+ gf_input_sources_gen_call_get_input_sources (self->input_sources,
+ self->cancellable,
+ get_input_sources_cb,
+ self);
+}
+
+static void
+input_sources_ready_cb (GObject *object,
+ GAsyncResult *res,
+ gpointer user_data)
+{
+ GError *error;
+ GfInputSourcesGen *input_sources;
+ SiInputSource *self;
+
+ error = NULL;
+ input_sources = gf_input_sources_gen_proxy_new_for_bus_finish (res, &error);
+
+ if (error != NULL)
+ {
+ if (!g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED))
+ g_warning ("%s", error->message);
+
+ g_error_free (error);
+ return;
+ }
+
+ self = SI_INPUT_SOURCE (user_data);
+ self->input_sources = input_sources;
+
+ g_signal_connect (self->input_sources, "changed",
+ G_CALLBACK (changed_cb), self);
+
+ gf_input_sources_gen_call_get_input_sources (self->input_sources,
+ self->cancellable,
+ get_input_sources_cb,
+ self);
+}
+
+static void
+name_appeared_handler_cb (GDBusConnection *connection,
+ const gchar *name,
+ const gchar *name_owner,
+ gpointer user_data)
+{
+ SiInputSource *self;
+
+ self = SI_INPUT_SOURCE (user_data);
+
+ self->cancellable = g_cancellable_new ();
+
+ gf_input_sources_gen_proxy_new_for_bus (G_BUS_TYPE_SESSION,
+ G_DBUS_PROXY_FLAGS_NONE,
+ "org.gnome.Flashback.InputSources",
+ "/org/gnome/Flashback/InputSources",
+ self->cancellable,
+ input_sources_ready_cb,
+ self);
+}
+
+static void
+name_vanished_handler_cb (GDBusConnection *connection,
+ const gchar *name,
+ gpointer user_data)
+{
+ SiInputSource *self;
+ GtkWidget *menu_item;
+
+ self = SI_INPUT_SOURCE (user_data);
+
+ g_clear_object (&self->input_sources);
+
+ menu_item = si_indicator_get_menu_item (SI_INDICATOR (self));
+ gtk_widget_hide (menu_item);
+}
+
+static void
+si_input_source_constructed (GObject *object)
+{
+ SiInputSource *self;
+ GtkWidget *menu_item;
+ GpApplet *applet;
+ const char *schema;
+
+ self = SI_INPUT_SOURCE (object);
+
+ G_OBJECT_CLASS (si_input_source_parent_class)->constructed (object);
+
+ menu_item = si_indicator_get_menu_item (SI_INDICATOR (self));
+ gtk_menu_item_set_submenu (GTK_MENU_ITEM (menu_item), self->menu);
+
+ applet = si_indicator_get_applet (SI_INDICATOR (self));
+ schema = "org.gnome.gnome-flashback.system-indicators.input-source";
+
+ self->settings = gp_applet_settings_new (applet, schema);
+
+ g_signal_connect (self->settings,
+ "changed",
+ G_CALLBACK (settings_changed_cb),
+ self);
+}
+
+static void
+si_input_source_dispose (GObject *object)
+{
+ SiInputSource *self;
+
+ self = SI_INPUT_SOURCE (object);
+
+ if (self->name_id != 0)
+ {
+ g_bus_unwatch_name (self->name_id);
+ self->name_id = 0;
+ }
+
+ g_clear_object (&self->settings);
+
+ g_cancellable_cancel (self->cancellable);
+ g_clear_object (&self->cancellable);
+
+ g_clear_object (&self->input_sources);
+
+ G_OBJECT_CLASS (si_input_source_parent_class)->dispose (object);
+}
+
+static void
+si_input_source_finalize (GObject *object)
+{
+ SiInputSource *self;
+
+ self = SI_INPUT_SOURCE (object);
+
+ g_clear_pointer (&self->icon_theme_path, g_free);
+ g_clear_pointer (&self->icon_text, g_free);
+
+ G_OBJECT_CLASS (si_input_source_parent_class)->finalize (object);
+}
+
+static void
+si_input_source_class_init (SiInputSourceClass *self_class)
+{
+ GObjectClass *object_class;
+
+ object_class = G_OBJECT_CLASS (self_class);
+
+ object_class->constructed = si_input_source_constructed;
+ object_class->dispose = si_input_source_dispose;
+ object_class->finalize = si_input_source_finalize;
+}
+
+static void
+si_input_source_init (SiInputSource *self)
+{
+ self->icon_theme_path = g_build_filename (g_get_user_cache_dir (),
+ "gnome-flashback",
+ "input-sources",
+ "icons",
+ NULL);
+
+ gtk_icon_theme_append_search_path (gtk_icon_theme_get_default (),
+ self->icon_theme_path);
+
+ self->menu = gtk_menu_new ();
+
+ self->name_id = g_bus_watch_name (G_BUS_TYPE_SESSION,
+ "org.gnome.Flashback.InputSources",
+ G_BUS_NAME_WATCHER_FLAGS_NONE,
+ name_appeared_handler_cb,
+ name_vanished_handler_cb,
+ self,
+ NULL);
+}
+
+SiIndicator *
+si_input_source_new (GpApplet *applet)
+{
+ return g_object_new (SI_TYPE_INPUT_SOURCE,
+ "applet", applet,
+ NULL);
+}
diff --git a/system-indicators/si-input-source.h b/system-indicators/si-input-source.h
new file mode 100644
index 0000000..c51e64e
--- /dev/null
+++ b/system-indicators/si-input-source.h
@@ -0,0 +1,34 @@
+/*
+ * Copyright (C) 2019 Alberts Muktupāvels
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef SI_INPUT_SOURCE_H
+#define SI_INPUT_SOURCE_H
+
+#include <libgnome-panel/gp-applet.h>
+#include "si-indicator.h"
+
+G_BEGIN_DECLS
+
+#define SI_TYPE_INPUT_SOURCE (si_input_source_get_type ())
+G_DECLARE_FINAL_TYPE (SiInputSource, si_input_source,
+ SI, INPUT_SOURCE, SiIndicator)
+
+SiIndicator *si_input_source_new (GpApplet *applet);
+
+G_END_DECLS
+
+#endif
[
Date Prev][
Date Next] [
Thread Prev][
Thread Next]
[
Thread Index]
[
Date Index]
[
Author Index]