#include "shell-realms-window-frames.h" #include "shell-realms.h" #define CITADEL_SETTINGS_SCHEMA "com.subgraph.citadel" #define FRAME_COLOR_LIST_KEY "frame-color-list" #define REALM_FRAME_COLORS_KEY "realm-frame-colors" #define REALM_FRAME_ALPHA 200 struct _ShellRealmsWindowFrames { GObject parent; GSettings *settings; GHashTable *realm_frame_colors; GList *default_colors; GList *frame_disabled_windows; }; G_DEFINE_TYPE (ShellRealmsWindowFrames, shell_realms_window_frames, G_TYPE_OBJECT); enum { REALM_FRAME_COLORS_CHANGED, LAST_SIGNAL, }; static guint shell_realms_window_frames_signals [LAST_SIGNAL] = { 0 }; static void shell_realms_window_frames_process_color (ShellRealmsWindowFrames *frames, const char *entry) { GdkRGBA rgba; gchar **split = g_strsplit (entry, ":", -1); if (g_strv_length (split) != 2) { g_warning("ShellRealmsWindowFrames: Unable to parse realm-frame-colors entry: %s", entry); g_strfreev (split); return; } if (!gdk_rgba_parse (&rgba, split[1])) { g_warning("ShellRealmsWindowFrames: Failed to parse RGBA component of realm frame color entry: %s", entry); } else { g_hash_table_insert (frames->realm_frame_colors, g_strdup (split[0]), gdk_rgba_copy (&rgba)); } g_strfreev (split); } static void load_realm_frame_colors (ShellRealmsWindowFrames *frames) { guint n_entries, i; char **entries; entries = g_settings_get_strv (frames->settings, REALM_FRAME_COLORS_KEY); n_entries = g_strv_length (entries); for (i = 0; i < n_entries; i++) { shell_realms_window_frames_process_color (frames, entries[i]); } g_strfreev (entries); } static void on_realm_frame_colors_changed(GSettings *settings, const gchar *key, ShellRealmsWindowFrames *frames) { load_realm_frame_colors (frames); g_signal_emit (frames, shell_realms_window_frames_signals[REALM_FRAME_COLORS_CHANGED], 0); } static void load_default_colors (ShellRealmsWindowFrames *frames) { guint n_entries, i; char **entries; GdkRGBA rgba; entries = g_settings_get_strv (frames->settings, FRAME_COLOR_LIST_KEY); n_entries = g_strv_length (entries); g_clear_list(&frames->default_colors, (GDestroyNotify) gdk_rgba_free); for (i = 0; i < n_entries; i++) { if (gdk_rgba_parse (&rgba, entries[i])) { frames->default_colors = g_list_append (frames->default_colors, gdk_rgba_copy(&rgba)); } } g_strfreev (entries); } static void on_frame_color_list_changed (GSettings *settings, const gchar *key, ShellRealmsWindowFrames *frames) { load_default_colors (frames); } static void shell_realms_window_frames_init (ShellRealmsWindowFrames *frames) { frames->settings = g_settings_new (CITADEL_SETTINGS_SCHEMA); frames->realm_frame_colors = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, (GDestroyNotify) gdk_rgba_free); frames->default_colors = NULL; frames->frame_disabled_windows = NULL; g_signal_connect(frames->settings, "changed::" FRAME_COLOR_LIST_KEY, G_CALLBACK(on_frame_color_list_changed), frames); g_signal_connect(frames->settings, "changed::" REALM_FRAME_COLORS_KEY, G_CALLBACK(on_realm_frame_colors_changed), frames); load_default_colors (frames); load_realm_frame_colors (frames); } static void shell_realms_window_frames_finalize (GObject *obj) { ShellRealmsWindowFrames *frames = SHELL_REALMS_WINDOW_FRAMES (obj); g_object_unref (frames->settings); g_hash_table_destroy (frames->realm_frame_colors); g_list_free_full (frames->default_colors, (GDestroyNotify) gdk_rgba_free); g_list_free (frames->frame_disabled_windows); G_OBJECT_CLASS (shell_realms_window_frames_parent_class)->finalize (obj); } static void shell_realms_window_frames_class_init (ShellRealmsWindowFramesClass *klass) { GObjectClass *object_class = G_OBJECT_CLASS (klass); object_class->finalize = shell_realms_window_frames_finalize; shell_realms_window_frames_signals[REALM_FRAME_COLORS_CHANGED] = g_signal_new ("realm-frame-colors-changed", G_TYPE_FROM_CLASS(klass), G_SIGNAL_RUN_LAST, 0, NULL, NULL, NULL, G_TYPE_NONE, 0); } static gboolean disabled_list_contains (ShellRealmsWindowFrames *frames, guint32 window_id) { return g_list_find (frames->frame_disabled_windows, GUINT_TO_POINTER(window_id)) != NULL; } static bool remove_from_disabled_list (ShellRealmsWindowFrames *frames, guint32 window_id) { if (disabled_list_contains (frames, window_id)) { frames->frame_disabled_windows = g_list_remove (frames->frame_disabled_windows, GUINT_TO_POINTER(window_id)); g_signal_emit (frames, shell_realms_window_frames_signals[REALM_FRAME_COLORS_CHANGED], 0); return TRUE; } return FALSE; } static bool add_to_disabled_list (ShellRealmsWindowFrames *frames, guint32 window_id) { if (!disabled_list_contains (frames, window_id)) { frames->frame_disabled_windows = g_list_append (frames->frame_disabled_windows, GUINT_TO_POINTER(window_id)); g_signal_emit (frames, shell_realms_window_frames_signals[REALM_FRAME_COLORS_CHANGED], 0); return TRUE; } return FALSE; } static gint color_compare(gconstpointer c1, gconstpointer c2) { return gdk_rgba_equal (c1, c2) ? 0 : 1; } static GdkRGBA * allocate_color (ShellRealmsWindowFrames *frames) { guint n_colors = g_list_length (frames->default_colors); // 1) No default colors? return a built in color if (n_colors == 0) { GdkRGBA rgba; gdk_rgba_parse (&rgba, "rgb(153, 193, 241)"); return gdk_rgba_copy (&rgba); } // 2) No default colors? Find first color on default color list that isn't used already GList *used_colors = g_hash_table_get_values (frames->realm_frame_colors); for (GList *iter = frames->default_colors; iter; iter = iter->next) { GdkRGBA *rgba = iter->data; if (!g_list_find_custom (used_colors, rgba, color_compare)) { return rgba; } } g_list_free (used_colors); // 3) Choose a random element of the default list guint index = (guint) g_random_int_range(0, (gint32) n_colors); return g_list_nth_data (frames->default_colors, index); } static void shell_realms_window_frames_store_colors (ShellRealmsWindowFrames *frames) { GHashTableIter iter; gpointer key, value; GPtrArray *entries = g_ptr_array_new_with_free_func(g_free); g_hash_table_iter_init (&iter, frames->realm_frame_colors); while (g_hash_table_iter_next(&iter, &key, &value)) { gchar *name = key; GdkRGBA *rgba = value; char *rgba_str = gdk_rgba_to_string (rgba); gchar *entry = g_strconcat (name, ":", rgba_str, NULL); g_ptr_array_add (entries, entry); g_free (rgba_str); } g_ptr_array_sort (entries, (GCompareFunc) g_strcmp0); g_ptr_array_add (entries, NULL); g_settings_set_strv (frames->settings, REALM_FRAME_COLORS_KEY, (const gchar * const *) entries->pdata); g_ptr_array_unref (entries); } static const char * shell_realms_window_frames_realm_name_for_window (ShellRealmsWindowFrames *frames, ShellRealms *realms, MetaWindow *window) { ShellRealmItem *realm = shell_realms_realm_by_window (realms, window); if (realm) { return shell_realm_item_get_realm_name (realm); } else { return NULL; } } static void on_window_unmanaged (MetaWindow *window, ShellRealmsWindowFrames *frames) { g_signal_handlers_disconnect_by_func (window, G_CALLBACK (on_window_unmanaged), frames); guint32 id = meta_window_get_stable_sequence (window); remove_from_disabled_list (frames, id); } static gboolean is_ignored_window (MetaWindow *window) { switch (meta_window_get_window_type (window)) { case META_WINDOW_MENU: case META_WINDOW_TOOLTIP: case META_WINDOW_POPUP_MENU: case META_WINDOW_DROPDOWN_MENU: return TRUE; default: return FALSE; } } /** * shell_realms_window_frames_is_frame_enabled: * @frames: a #ShellRealmsWindowFrames instance * @window: a #MetaWindow * * Return #TRUE if frame has not been disabled for this window. * * Returns: #TRUE if frame has not been disabled for this window. */ gboolean shell_realms_window_frames_is_frame_enabled (ShellRealmsWindowFrames *frames, MetaWindow *window) { guint32 id = meta_window_get_stable_sequence (window); return !disabled_list_contains (frames, id); } /** * shell_realms_window_frames_has_frame: * @frames: a #ShellRealmsWindowFrames instance * @window: a #MetaWindow * * Return #TRUE if this window needs a frame. * * Returns: #TRUE if a frame should be drawn for this window. */ gboolean shell_realms_window_frames_has_frame (ShellRealmsWindowFrames *frames, MetaWindow *window) { return !is_ignored_window(window) && meta_window_is_on_foreign_workspace_context (window); } /** * shell_realms_window_frames_set_frame_enabled: * @frames: a #ShellRealmsWindowFrames instance * @window: a #MetaWindow * @enabled: Set to #FALSE to disable drawing frame for this window * */ void shell_realms_window_frames_set_frame_enabled (ShellRealmsWindowFrames *frames, MetaWindow *window, gboolean enabled) { guint32 id = meta_window_get_stable_sequence (window); if (enabled) { if (remove_from_disabled_list (frames, id)) { g_signal_handlers_disconnect_by_func (window, G_CALLBACK (on_window_unmanaged), frames); } } else if (add_to_disabled_list (frames, id)) { g_signal_connect_object (window, "unmanaged", G_CALLBACK(on_window_unmanaged), frames, 0); } } static ClutterColor * rgba_to_clutter_color(GdkRGBA *rgba) { guint8 r = (guint8) (0.5 + CLAMP(rgba->red, 0.0, 1.0) * 255.0); guint8 g = (guint8) (0.5 + CLAMP(rgba->green, 0.0, 1.0) * 255.0); guint8 b = (guint8) (0.5 + CLAMP(rgba->blue, 0.0, 1.0) * 255.0); return clutter_color_new (r, g, b, REALM_FRAME_ALPHA); } /** * shell_realms_window_frames_color_for_window: * @frames: a #ShellRealmsWindowFrames instance * @window: a #MetaWindow * * Returns a color to use for painting window frame. * * Return value: (transfer full) (nullable): The frame color or %NULL if no frame should be drawn. */ ClutterColor * shell_realms_window_frames_color_for_window (ShellRealmsWindowFrames *frames, MetaWindow *window) { if (!shell_realms_window_frames_has_frame (frames, window)) { return NULL; } ShellRealms *realms = shell_realms_get_default(); const gchar *name = shell_realms_window_frames_realm_name_for_window (frames, realms, window); GdkRGBA *rgba = g_hash_table_lookup (frames->realm_frame_colors, name); if (!rgba) { rgba = allocate_color (frames); g_hash_table_insert (frames->realm_frame_colors, g_strdup(name), rgba); shell_realms_window_frames_store_colors (frames); } return rgba_to_clutter_color (rgba); } /** * shell_realms_window_frames_label_for_window: * @frames: a #ShellRealmsWindowFrames instance * @window: a #MetaWindow * * Return the label text for window if the window requires a frame. * * Return value: (transfer none) (nullable): The label text or %NULL if no label should be displayed */ const gchar * shell_realms_window_frames_label_for_window (ShellRealmsWindowFrames *frames, MetaWindow *window) { if (!shell_realms_window_frames_has_frame (frames, window)) { return NULL; } if (meta_window_get_window_type (window) != META_WINDOW_NORMAL) { return NULL; } ShellRealms *realms = shell_realms_get_default(); if (shell_realms_is_citadel_window (realms, window)) { return "Citadel"; } else { return shell_realms_window_frames_realm_name_for_window (frames, realms, window); } }