gnome-shell/src/shell-realms.c
Bruce Leidl ee57f6de95 Implement 'detached' workspace context
There is always at least one workspace context which is the context
that is currently being used by the GNOME desktop. If no running realm
is associated with this context then we consider this context to be
'detached' and store it so that the next time a context is needed to
attach to a running realm, this 'detached' context will be used rather
than asking mutter to create a new fresh context.
2025-04-07 13:52:58 +00:00

651 lines
19 KiB
C

#include <meta/display.h>
#include <meta/meta-workspace-manager.h>
#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));
}