#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));
}