#include #include #include "shell-realm-item.h" #include "shell-realm-tracker.h" #include "shell-realms-private.h" #include "shell-global.h" struct _ShellRealms { GObject parent; GHashTable *realms; GList *running_realms; ShellRealmItem *current_realm; ShellRealmsWindowFrames *frames; MetaWorkspaceContext *detached_context; }; G_DEFINE_TYPE (ShellRealms, shell_realms, G_TYPE_OBJECT); enum { REALM_CONTEXT_SWITCHED, LAST_SIGNAL, }; enum { PROP_0, PROP_CURRENT_REALM, }; static guint shell_realms_signals [LAST_SIGNAL] = { 0 }; /** * shell_realms_current_realm: * @realms: A #ShellRealms instance * * Returns: (transfer none) (nullable): The current realm as a #ShellRealmItem * or %NULL if no realm is current. */ ShellRealmItem * shell_realms_current_realm (ShellRealms *realms) { return realms->current_realm; } /** * shell_realms_realm_by_name: * @realms: a #ShellRealms instance * @realm_name: The name of a realm to look up * * Returns: (transfer none) (nullable): A realm #ShellRealmItem or %NULL * if name not found */ ShellRealmItem * shell_realms_realm_by_name (ShellRealms *realms, const gchar *realm_name) { if (realm_name == NULL) { return NULL; } ShellRealmItem *item = g_hash_table_lookup (realms->realms, realm_name); if (!item) { g_warning("ShellRealms: No realm found for name '%s'", realm_name); } return item; } /** * shell_realms_realm_by_context_id: * @realms: a #ShellRealms instance * @context_id: A context id to search for. * * Returns: (transfer none) (nullable): The realm #ShellRealmItem for the realm * with a workspace context id matching the specified value or %NULL if no such realm is found */ ShellRealmItem * shell_realms_realm_by_context_id (ShellRealms *realms, guint context_id) { if (context_id == 0) { return NULL; } for (GList *iter = realms->running_realms; iter; iter = iter->next) { ShellRealmItem *item = iter->data; if (shell_realm_item_get_context_id (item) == context_id) { return item; } } return NULL; } static gchar * realm_name_from_window_pid (ShellRealms *realms, MetaWindow *window) { ShellRealmTracker *tracker = shell_realm_tracker_get_default(); pid_t pid = meta_window_get_pid (window); if (pid > 0) { return shell_realm_tracker_call_realm_from_citadel_pid (tracker, pid); } return NULL; } /** * shell_realms_realm_by_window: * @realms: a #ShellRealms instance * @window: A window to find the corresponding realm for. * * Returns: (transfer none) (nullable): The realm #ShellRealmItem for the realm * the application the window belongs to is running in or %NULL if no realm is found */ ShellRealmItem * shell_realms_realm_by_window (ShellRealms *realms, MetaWindow *window) { ShellRealmItem *realm = NULL; gchar *realm_name = realm_name_from_window_pid (realms, window); if (realm_name && g_strcmp0 (CITADEL_REALM_TAG, realm_name)) { realm = shell_realms_realm_by_name (realms, realm_name); } g_free(realm_name); return realm; } /** * shell_realms_is_citadel_window: * @realms: A #ShellRealms instance * @window: A #MetaWindow * * Return #TRUE if the window belongs to an application running inside of Citadel * rather than running in a realm. * * Returns: If window belongs to an application running in Citadel return #True */ gboolean shell_realms_is_citadel_window (ShellRealms *realms, MetaWindow *window) { gchar *realm_name = realm_name_from_window_pid (realms, window); gboolean is_citadel = g_strcmp0 (CITADEL_REALM_TAG, realm_name) == 0; g_free(realm_name); return is_citadel; } gboolean shell_realms_is_foreign_window (ShellRealms *realms, MetaWindow *window) { MetaWorkspace *workspace = meta_window_get_workspace (window); guint current_id = meta_workspace_get_context_id (workspace); ShellRealmItem *realm_item = shell_realms_realm_by_window (realms, window); if (current_id && realm_item) { guint realm_id = shell_realm_item_get_context_id (realm_item); return current_id != realm_id; } return FALSE; } /** * shell_realms_get_running_realms: * @realms: the #ShellRealms instance * * Returns all running realms as a list of #ShellRealmItem * * Returns: (transfer none) (element-type ShellRealmItem): a list of * #ShellRealmItem for all running realms. * */ GList * shell_realms_get_running_realms (ShellRealms *realms) { return realms->running_realms; } /** * shell_realms_get_all_realms: * @realms: the #ShellRealms instance * * Returns all realms as a list of #ShellRealmItem * * Returns: (transfer container) (element-type ShellRealmItem): all realms as * a list of #ShellRealmItem */ GList * shell_realms_get_all_realms (ShellRealms *realms) { return g_hash_table_get_values (realms->realms); } /** * shell_realms_window_frames: * @realms: the #ShellRealms instance * * Returns the window frames manager. * * Returns: (transfer none): a #ShellRealmsWindowFrames instance */ ShellRealmsWindowFrames * shell_realms_window_frames (ShellRealms *realms) { return realms->frames; } static gboolean shell_realms_is_on_running_list (ShellRealms *realms, ShellRealmItem *item) { return (g_list_index (realms->running_realms, item) >= 0); } static void shell_realms_remove_running_realm (ShellRealms *realms, ShellRealmItem *item) { if (!shell_realms_is_on_running_list (realms, item)) { return; } realms->running_realms = g_list_remove(realms->running_realms, item); g_object_unref (item); } static void shell_realms_update_running_list_for_item (ShellRealms *realms, ShellRealmItem *item) { gboolean running = shell_realm_item_is_running (item); if (running) { if (!shell_realms_is_on_running_list (realms, item)) { realms->running_realms = g_list_append (realms->running_realms, g_object_ref (item)); } // If realm is current realm, make sure it's at the front of the list if (shell_realm_item_is_current (item) && g_list_index (realms->running_realms, item) > 0) { realms->running_realms = g_list_remove (realms->running_realms, item); realms->running_realms = g_list_prepend (realms->running_realms, item); } } else { shell_realms_remove_running_realm (realms, item); } } static void shell_realms_set_current_item (ShellRealms *realms, ShellRealmItem *item) { if (realms->current_realm == item) { return; } else if (realms->current_realm) { shell_realm_item_set_current_flag (realms->current_realm, FALSE); } shell_realm_item_set_current_flag (item, TRUE); realms->current_realm = item; g_object_notify (realms, "current-realm"); } /* * If a realm already exists for 'realm_name' update it with provided information, otherwise * create a new ShellRealmItem and add it to hash table unless it is a system realm. Returns * the existing or newly created ShellRealmItem or returns NULL if the realm is a system realm. */ static ShellRealmItem * shell_realms_add_realm_item (ShellRealms *realms, const char *realm_name, const char *description, guint64 namespace, guint8 status) { g_assert (realm_name != NULL); ShellRealmItem *item = g_hash_table_lookup (realms->realms, realm_name); if (item) { shell_realm_item_update (item, realm_name, namespace, status); return item; } item = shell_realm_item_new (realm_name, description, namespace, status); if (shell_realm_item_is_system (item)) { g_clear_object (&item); return NULL; } g_hash_table_insert (realms->realms, g_strdup (realm_name), item); return item; } static void remove_workspace_context (ShellRealms *realms, ShellRealmItem *realm) { MetaWorkspaceContext *context = shell_realm_item_get_context (realm); if (context) { guint id = meta_workspace_context_id (context); shell_realm_item_set_context (realm, NULL); if (!meta_workspace_context_is_current (context)) { meta_workspace_context_remove (context); } else { realms->detached_context = context; } } } static ShellRealmItem * find_realm_with_context (ShellRealms *realms, MetaWorkspaceContext *context) { GHashTableIter iter; gpointer value; if (context == NULL) { return NULL; } g_hash_table_iter_init (&iter, realms->realms); while (g_hash_table_iter_next (&iter, NULL, &value)) { ShellRealmItem *item = value; if (shell_realm_item_get_context (item) == context) { return item; } } return NULL; } static void acquire_active_workspace_context (ShellRealms *realms, ShellRealmItem *realm) { MetaDisplay *display = shell_global_get_display (shell_global_get ()); MetaWorkspaceManager *workspace_manager = meta_display_get_workspace_manager (display); MetaWorkspaceContext *active_context = meta_workspace_manager_active_context (workspace_manager); if (shell_realm_item_get_context (realm) == active_context) { return; } if (!shell_realm_item_is_current (realm)) { g_warning ("ShellRealms: Cannot acquire active workspace context for realm that is not the current realm"); return; } if (realms->detached_context == active_context) { shell_realm_item_set_context(realm, realms->detached_context); realms->detached_context = NULL; return; } ShellRealmItem *other_realm = find_realm_with_context (realms, active_context); if (other_realm) { g_warning ("Stealing active workspace context from realm-%s for realm-%s", shell_realm_item_get_realm_name(other_realm), shell_realm_item_get_realm_name(realm)); if (shell_realm_item_is_current (other_realm)) { shell_realm_item_set_current_flag (other_realm, false); } shell_realm_item_set_context (other_realm, NULL); if (shell_realm_item_is_running (other_realm)) { MetaWorkspaceContext *context = meta_workspace_context_new(workspace_manager); shell_realm_item_set_context(other_realm, context); } } shell_realm_item_set_context (realm, active_context); } static void acquire_workspace_context (ShellRealms *realms, ShellRealmItem *realm) { if (shell_realm_item_is_system (realm) || shell_realm_item_get_context (realm) != NULL) { return; } if (shell_realm_item_is_current (realm)) { acquire_active_workspace_context (realms, realm); return; } if (realms->detached_context) { guint id = meta_workspace_context_id(realms->detached_context); shell_realm_item_set_context(realm, realms->detached_context); realms->detached_context = NULL; return; } MetaDisplay *display = shell_global_get_display(shell_global_get()); MetaWorkspaceManager *workspace_manager = meta_display_get_workspace_manager(display); MetaWorkspaceContext *context = meta_workspace_context_new(workspace_manager); guint id = meta_workspace_context_id(context); shell_realm_item_set_context(realm, context); } void shell_realms_process_list_realm (ShellRealms *realms, const char *realm_name, const char *description, guint64 namespace, guint8 status) { // Ignore system realms if (shell_realm_is_flag_set (status, SHELL_REALM_ITEM_STATUS_SYSTEM)) { return; } ShellRealmItem *item = shell_realms_add_realm_item (realms, realm_name, description, namespace, status); gboolean running = shell_realm_item_is_running(item); gboolean current = shell_realm_item_is_current(item); shell_realms_update_running_list_for_item (realms, item); shell_realm_item_set_tagged (item, TRUE); // If realm is current, make sure it has a context. This also ensures that the // first context requested is for the current realm. if (shell_realm_item_is_current (item)) { shell_realms_set_current_item (realms, item); acquire_workspace_context (realms, item); } if (!shell_realm_item_is_running (item) && shell_realm_item_get_context (item) != NULL) { remove_workspace_context (realms, item); } } // When processing list of realms returned from "List" dbus method, // // 1) first all the existing realms are "untagged" // 2) As each realm on list is processed it is marked as "tagged" // 3) After processing list, realm items that are not tagged were not in list // returned from server so remove them. // void shell_realms_untag_all (ShellRealms *realms) { GList *item_list = g_hash_table_get_values (realms->realms); for (GList *iter = item_list; iter; iter = iter->next) { ShellRealmItem *item = iter->data; shell_realm_item_set_tagged (item, FALSE); iter = iter->next; } g_list_free (item_list); } void shell_realms_remove_untagged (ShellRealms *realms) { GHashTableIter iter; gpointer value; g_hash_table_iter_init (&iter, realms->realms); while (g_hash_table_iter_next (&iter, NULL, &value)) { ShellRealmItem *item = value; if (!shell_realm_item_is_tagged (item)) { shell_realms_remove_running_realm (realms, item); remove_workspace_context (realms, item); g_hash_table_iter_remove (&iter); } } } void shell_realms_update_contexts (ShellRealms *realms) { for (GList *iter = realms->running_realms; iter; iter = iter->next) { ShellRealmItem *item = iter->data; if (shell_realm_item_get_context (item) == NULL) { acquire_workspace_context (realms, item); } } } // Signal handlers void shell_realms_on_realm_started (ShellRealms *realms, const gchar *realm_name, guint64 namespace, guint8 status) { ShellRealmItem *item = shell_realms_realm_by_name (realms, realm_name); if (item) { shell_realm_item_update (item, realm_name, namespace, status); if (!shell_realm_item_is_system (item)) { shell_realms_update_running_list_for_item (realms, item); acquire_workspace_context(realms, item); } } } static void activate_workspace_context (ShellRealmItem *realm) { MetaWorkspaceContext *context = shell_realm_item_get_context (realm); if (context) { meta_workspace_context_activate (context); } } void shell_realms_on_realm_current (ShellRealms *realms, const gchar *realm_name) { if (strlen(realm_name) == 0) { g_warning("on_realm_current(NULL)"); if(realms->current_realm) { shell_realm_item_set_current_flag (realms->current_realm, FALSE); realms->current_realm = NULL; } return; } ShellRealmItem *item = shell_realms_realm_by_name (realms, realm_name); if (item && realms->current_realm != item) { shell_realms_set_current_item (realms, item); shell_realms_update_running_list_for_item (realms, item); activate_workspace_context (item); // If we really did switch to another context and we have a detached context saved, then // now it can be removed which will move any windows on the detached context to the newly switched context. if (realms->detached_context && shell_realm_item_get_context(item) != NULL) { meta_workspace_context_remove(realms->detached_context); realms->detached_context = NULL; } g_signal_emit (realms, shell_realms_signals[REALM_CONTEXT_SWITCHED], 0); } } void shell_realms_on_realm_stopped (ShellRealms *realms, const gchar *realm_name) { ShellRealmItem *item = shell_realms_realm_by_name (realms, realm_name); if (item) { shell_realm_item_set_running_flag (item, FALSE); remove_workspace_context (realms, item); shell_realms_remove_running_realm (realms, item); } } void shell_realms_on_realm_removed (ShellRealms *realms, const gchar *realm_name) { ShellRealmItem *item = shell_realms_realm_by_name (realms, realm_name); if (item) { shell_realm_item_set_running_flag (item, FALSE); remove_workspace_context (realms, item); shell_realms_remove_running_realm (realms, item); g_hash_table_remove (realms->realms, realm_name); } } void shell_realms_on_realm_new (ShellRealms *realms, const gchar *realm_name, const gchar *description, guint8 status) { if (realm_name == NULL || shell_realm_is_flag_set (status, SHELL_REALM_ITEM_STATUS_SYSTEM)) { return; } if (!g_hash_table_contains (realms->realms, realm_name)) { (void) shell_realms_add_realm_item (realms, realm_name, description, 0, status); } else { g_warning ("ShellRealms: RealmNew signal received for realm '%s' but it already exists", realm_name); } } /** * shell_realms_get_default: * * Return Value: (transfer none): The global #ShellRealms singleton */ ShellRealms * shell_realms_get_default(void) { static ShellRealms *instance; if (instance == NULL) { instance = g_object_new (SHELL_TYPE_REALMS, NULL); } return instance; } static void shell_realms_init(ShellRealms *realms) { MetaDisplay *display = shell_global_get_display(shell_global_get()); MetaWorkspaceManager *workspace_manager = meta_display_get_workspace_manager(display); realms->realms = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, g_object_unref); realms->running_realms = NULL; realms->current_realm = NULL; realms->detached_context = meta_workspace_manager_active_context(workspace_manager); } static void shell_realms_finalize (GObject *obj) { ShellRealms *realms = SHELL_REALMS (obj); realms->current_realm = NULL; g_list_free_full (realms->running_realms, g_object_unref); g_hash_table_destroy (realms->realms); g_object_unref (realms->frames); realms->frames = NULL; G_OBJECT_CLASS (shell_realms_parent_class)->finalize (obj); } static void shell_realms_get_property (GObject *gobject, guint prop_id, GValue *value, GParamSpec *pspec) { ShellRealms *realms = SHELL_REALMS (gobject); switch (prop_id) { case PROP_CURRENT_REALM: if (realms->current_realm) { g_value_set_object (value, realms->current_realm); } break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (gobject, prop_id, pspec); break; } } static void shell_realms_class_init (ShellRealmsClass *klass) { GObjectClass *object_class = G_OBJECT_CLASS (klass); object_class->get_property = shell_realms_get_property; object_class->finalize = shell_realms_finalize; shell_realms_signals[REALM_CONTEXT_SWITCHED] = g_signal_new ("realm-context-switched", G_TYPE_FROM_CLASS(klass), G_SIGNAL_RUN_LAST, 0, NULL, NULL, NULL, G_TYPE_NONE, 0); g_object_class_install_property (object_class, PROP_CURRENT_REALM, g_param_spec_object ("current-realm", "Current Realm", "The currently active realm", SHELL_TYPE_REALM_ITEM, G_PARAM_READABLE | G_PARAM_STATIC_STRINGS)); }