634 lines
18 KiB
C
634 lines
18 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;
|
|
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);
|
|
}
|
|
|
|
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 (G_OBJECT(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_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));
|
|
}
|