diff --git a/meta-citadel/classes/external-tree.bbclass b/meta-citadel/classes/external-tree.bbclass new file mode 100644 index 0000000..0239803 --- /dev/null +++ b/meta-citadel/classes/external-tree.bbclass @@ -0,0 +1,33 @@ + +S = "${WORKDIR}${@source_path(d)}" +SRC_URI = "${@source_uri(d)}" + + +def source_path(d): + var = d.getVar("EXTERNAL_TREE_VAR") + tree_path = d.getVar(var) + + if tree_path: + return tree_path + else: + return "/git" + +def source_uri(d): + var = d.getVar("EXTERNAL_TREE_VAR") + tree_path = d.getVar(var) + + if tree_path: + return "file://" + tree_path + else: + return d.getVar("GIT_URI") + +# Set debug build if $EXTERNAL_TREE_VAR is set for faster builds +DEBUG_BUILD = "${@debug_build(d)}" + +def debug_build(d): + var = d.getVar("EXTERNAL_TREE_VAR") + tree_path = d.getVar(var) + if tree_path: + return "1" + else: + return "0" diff --git a/meta-citadel/conf/local.conf.sample b/meta-citadel/conf/local.conf.sample index e87dd4d..da33cb5 100644 --- a/meta-citadel/conf/local.conf.sample +++ b/meta-citadel/conf/local.conf.sample @@ -215,3 +215,7 @@ INHERIT+="toaster buildhistory" # # CITADEL_TOOLS_PATH = "/home/user/citadel-tools" # +# Same as above for GNOME Shell and Mutter: +# +# CITADEL_GNOME_SHELL_PATH = "/home/user/citadel-gnome/gnome-shell" +# CITADEL_MUTTER_PATH = "/home/user/citadel-gnome/mutter" diff --git a/meta-gnome/recipes-gnome/gnome-shell/gnome-shell/0001-Disabled-calendar-events-from-user-session.patch b/meta-gnome/recipes-gnome/gnome-shell/gnome-shell/0001-Disabled-calendar-events-from-user-session.patch deleted file mode 100644 index 03d2660..0000000 --- a/meta-gnome/recipes-gnome/gnome-shell/gnome-shell/0001-Disabled-calendar-events-from-user-session.patch +++ /dev/null @@ -1,22 +0,0 @@ -From ac70fbe1edeef149c55d5779008429aec9945b76 Mon Sep 17 00:00:00 2001 -From: Bruce Leidl -Date: Tue, 5 Oct 2021 11:17:47 +0000 -Subject: [PATCH] Disabled calendar events from user session - ---- - js/ui/sessionMode.js | 2 +- - 1 file changed, 1 insertion(+), 1 deletion(-) - -diff --git a/js/ui/sessionMode.js b/js/ui/sessionMode.js -index afcd67c..b686efb 100644 ---- a/js/ui/sessionMode.js -+++ b/js/ui/sessionMode.js -@@ -71,7 +71,7 @@ const _modes = { - - 'user': { - hasOverview: true, -- showCalendarEvents: true, -+ showCalendarEvents: false, - showWelcomeDialog: true, - allowSettings: true, - allowExtensions: true, diff --git a/meta-gnome/recipes-gnome/gnome-shell/gnome-shell/0001-Disabled-calendar-events-from-user-session.patch.old b/meta-gnome/recipes-gnome/gnome-shell/gnome-shell/0001-Disabled-calendar-events-from-user-session.patch.old deleted file mode 100644 index 0e0b87d..0000000 --- a/meta-gnome/recipes-gnome/gnome-shell/gnome-shell/0001-Disabled-calendar-events-from-user-session.patch.old +++ /dev/null @@ -1,22 +0,0 @@ -From a2ea7fac46ea2b6599df0095223627b17181f390 Mon Sep 17 00:00:00 2001 -From: xSmurf -Date: Wed, 14 Nov 2018 01:47:43 -0500 -Subject: [PATCH] Disabled calendar events from user session... - ---- - js/ui/sessionMode.js | 2 +- - 1 file changed, 1 insertion(+), 1 deletion(-) - -diff --git a/js/ui/sessionMode.js b/js/ui/sessionMode.js -index 2136e94..3f35ab2 100644 ---- a/js/ui/sessionMode.js -+++ b/js/ui/sessionMode.js -@@ -69,7 +69,7 @@ const _modes = { - - 'user': { - hasOverview: true, -- showCalendarEvents: true, -+ showCalendarEvents: false, - allowSettings: true, - allowExtensions: true, - allowScreencast: true, diff --git a/meta-gnome/recipes-gnome/gnome-shell/gnome-shell/0001-Patch-gnome-shell-to-disable-logging-out.patch b/meta-gnome/recipes-gnome/gnome-shell/gnome-shell/0001-Patch-gnome-shell-to-disable-logging-out.patch deleted file mode 100644 index 465669e..0000000 --- a/meta-gnome/recipes-gnome/gnome-shell/gnome-shell/0001-Patch-gnome-shell-to-disable-logging-out.patch +++ /dev/null @@ -1,24 +0,0 @@ -From 3fb6b84dc41a0c4abfa5b493635ee6a2c4abd0f8 Mon Sep 17 00:00:00 2001 -From: David McKinney -Date: Sun, 14 Apr 2019 18:57:03 +0000 -Subject: [PATCH] Patch gnome-shell to disable logging out - ---- - js/misc/systemActions.js | 4 ++-- - 1 file changed, 2 insertions(+), 2 deletions(-) - -diff --git a/js/misc/systemActions.js b/js/misc/systemActions.js -index aa79bfe..4036783 100644 ---- a/js/misc/systemActions.js -+++ b/js/misc/systemActions.js -@@ -361,8 +361,8 @@ const SystemActions = GObject.registerClass({ - - _updateLogout() { - let user = this._userManager.get_user(GLib.get_user_name()); -- -- let allowLogout = !this._lockdownSettings.get_boolean(DISABLE_LOG_OUT_KEY); -+ // Disable allowLogout globally in gnome-shell -+ let allowLogout = false; - let alwaysShow = global.settings.get_boolean(ALWAYS_SHOW_LOG_OUT_KEY); - let systemAccount = user.system_account; - let localAccount = user.local_account; diff --git a/meta-gnome/recipes-gnome/gnome-shell/gnome-shell/0001-Remove-calendar-server-fix-build.patch b/meta-gnome/recipes-gnome/gnome-shell/gnome-shell/0001-Remove-calendar-server-fix-build.patch deleted file mode 100644 index f9b246f..0000000 --- a/meta-gnome/recipes-gnome/gnome-shell/gnome-shell/0001-Remove-calendar-server-fix-build.patch +++ /dev/null @@ -1,100 +0,0 @@ -From 9d4edfc419cdb2868a6a69bf1ed4565478789a35 Mon Sep 17 00:00:00 2001 -From: David McKinney -Date: Wed, 8 Jul 2020 14:07:06 +0000 -Subject: [PATCH] Remove calendar server, fix build - ---- - js/dbusServices/meson.build | 2 +- - meson.build | 10 +++++----- - src/meson.build | 2 +- - subprojects/extensions-app/js/meson.build | 2 +- - subprojects/extensions-app/meson.build | 2 +- - 5 files changed, 9 insertions(+), 9 deletions(-) - -diff --git a/js/dbusServices/meson.build b/js/dbusServices/meson.build -index 68e8bd1..16efaeb 100644 ---- a/js/dbusServices/meson.build -+++ b/js/dbusServices/meson.build -@@ -27,7 +27,7 @@ foreach service, dir : dbus_services - - serviceconf = configuration_data() - serviceconf.set('service', service) -- serviceconf.set('gjs', gjs.path()) -+ serviceconf.set('gjs', gjs) - serviceconf.set('pkgdatadir', pkgdatadir) - - configure_file( -diff --git a/meson.build b/meson.build -index 58f66cd..1dc4729 100644 ---- a/meson.build -+++ b/meson.build -@@ -19,8 +19,8 @@ cogl_pango_pc = 'mutter-cogl-pango-' + mutter_api_version - libmutter_pc = 'libmutter-' + mutter_api_version - libmutter_test_pc = 'libmutter-test-' + mutter_api_version - --ecal_req = '>= 3.33.1' --eds_req = '>= 3.33.1' -+#ecal_req = '>= 3.33.1' -+#eds_req = '>= 3.33.1' - gcr_req = '>= 3.7.5' - gio_req = '>= 2.56.0' - gi_req = '>= 1.49.1' -@@ -72,8 +72,8 @@ else - endif - - atk_bridge_dep = dependency('atk-bridge-2.0') --ecal_dep = dependency('libecal-2.0', version: ecal_req) --eds_dep = dependency('libedataserver-1.2', version: eds_req) -+#ecal_dep = dependency('libecal-2.0', version: ecal_req) -+#eds_dep = dependency('libedataserver-1.2', version: eds_req) - gcr_dep = dependency('gcr-base-3', version: gcr_req) - gdk_x11_dep = dependency('gdk-x11-3.0') - gdk_pixbuf_dep = dependency('gdk-pixbuf-2.0') -@@ -139,7 +139,7 @@ endif - - mutter_typelibdir = mutter_dep.get_pkgconfig_variable('typelibdir') - python = find_program('python3') --gjs = find_program('gjs') -+gjs = '/usr/bin/gjs' - - cc = meson.get_compiler('c') - -diff --git a/src/meson.build b/src/meson.build -index 53b8b52..5fa9fdf 100644 ---- a/src/meson.build -+++ b/src/meson.build -@@ -1,7 +1,7 @@ - service_data = configuration_data() - service_data.set('libexecdir', libexecdir) - --subdir('calendar-server') -+#subdir('calendar-server') - subdir('hotplug-sniffer') - subdir('st') - subdir('tray') -diff --git a/subprojects/extensions-app/js/meson.build b/subprojects/extensions-app/js/meson.build -index f311db6..8f7bba4 100644 ---- a/subprojects/extensions-app/js/meson.build -+++ b/subprojects/extensions-app/js/meson.build -@@ -9,7 +9,7 @@ endif - launcherconf.set('prefix', prefix) - launcherconf.set('libdir', libdir) - launcherconf.set('pkgdatadir', pkgdatadir) --launcherconf.set('gjs', gjs.path()) -+launcherconf.set('gjs', gjs) - - configure_file( - input: prgname + '.in', -diff --git a/subprojects/extensions-app/meson.build b/subprojects/extensions-app/meson.build -index 6143eef..a619f63 100644 ---- a/subprojects/extensions-app/meson.build -+++ b/subprojects/extensions-app/meson.build -@@ -44,7 +44,7 @@ localedir = join_paths(datadir, 'locale') - metainfodir = join_paths(datadir, 'metainfo') - servicedir = join_paths(datadir, 'dbus-1', 'services') - --gjs = find_program('gjs') -+gjs = '/usr/bin/gjs' - appstream_util = find_program('appstream-util', required: false) - desktop_file_validate = find_program('desktop-file-validate', required: false) - diff --git a/meta-gnome/recipes-gnome/gnome-shell/gnome-shell/0001-Remove-log-out-label-from-power-off-in-status-UI.patch b/meta-gnome/recipes-gnome/gnome-shell/gnome-shell/0001-Remove-log-out-label-from-power-off-in-status-UI.patch deleted file mode 100644 index 09aac8b..0000000 --- a/meta-gnome/recipes-gnome/gnome-shell/gnome-shell/0001-Remove-log-out-label-from-power-off-in-status-UI.patch +++ /dev/null @@ -1,22 +0,0 @@ -From 5e5da06bd97175bed4a2719e5e67a5b2b51e4efc Mon Sep 17 00:00:00 2001 -From: David McKinney -Date: Fri, 12 Feb 2021 13:58:36 +0000 -Subject: [PATCH] Remove log out label from power off in status UI - ---- - js/ui/status/system.js | 2 +- - 1 file changed, 1 insertion(+), 1 deletion(-) - -diff --git a/js/ui/status/system.js b/js/ui/status/system.js -index 6f71109..b96780c 100644 ---- a/js/ui/status/system.js -+++ b/js/ui/status/system.js -@@ -113,7 +113,7 @@ class Indicator extends PanelMenu.SystemIndicator { - bindFlags); - - this._sessionSubMenu = new PopupMenu.PopupSubMenuMenuItem( -- _('Power Off / Log Out'), true); -+ _('Power Off'), true); - this._sessionSubMenu.icon.icon_name = 'system-shutdown-symbolic'; - - item = new PopupMenu.PopupMenuItem(_('Suspend')); diff --git a/meta-gnome/recipes-gnome/gnome-shell/gnome-shell/0001-do-not-use-python-path-from-build-environment.patch b/meta-gnome/recipes-gnome/gnome-shell/gnome-shell/0001-do-not-use-python-path-from-build-environment.patch deleted file mode 100644 index a720bbd..0000000 --- a/meta-gnome/recipes-gnome/gnome-shell/gnome-shell/0001-do-not-use-python-path-from-build-environment.patch +++ /dev/null @@ -1,30 +0,0 @@ -From 4398d05df199e3c63d1af8de0774582d430368d5 Mon Sep 17 00:00:00 2001 -From: brl -Date: Sun, 24 Dec 2017 17:44:02 -0500 -Subject: [PATCH] do not use python path from build environment - ---- - src/gnome-shell-extension-tool.in | 2 +- - src/gnome-shell-perf-tool.in | 2 +- - 2 files changed, 2 insertions(+), 2 deletions(-) - -diff --git a/src/gnome-shell-extension-tool.in b/src/gnome-shell-extension-tool.in -index fb3d0d8..8da34ff 100755 ---- a/src/gnome-shell-extension-tool.in -+++ b/src/gnome-shell-extension-tool.in -@@ -1,4 +1,4 @@ --#!@PYTHON@ -+#!/usr/bin/env python3 - # -*- mode: Python; indent-tabs-mode: nil; -*- - - import subprocess -diff --git a/src/gnome-shell-perf-tool.in b/src/gnome-shell-perf-tool.in -index a1b5d59..163a5b9 100755 ---- a/src/gnome-shell-perf-tool.in -+++ b/src/gnome-shell-perf-tool.in -@@ -1,4 +1,4 @@ --#!@PYTHON@ -+#!/usr/bin/env python3 - # -*- mode: Python; indent-tabs-mode: nil; -*- - - import datetime diff --git a/meta-gnome/recipes-gnome/gnome-shell/gnome-shell/0002-Citadel-Gnome-Shell-changes.patch b/meta-gnome/recipes-gnome/gnome-shell/gnome-shell/0002-Citadel-Gnome-Shell-changes.patch deleted file mode 100644 index 4325534..0000000 --- a/meta-gnome/recipes-gnome/gnome-shell/gnome-shell/0002-Citadel-Gnome-Shell-changes.patch +++ /dev/null @@ -1,4021 +0,0 @@ -From fa02a178a28487474df5c550116f6379b872c63e Mon Sep 17 00:00:00 2001 -From: Bruce Leidl -Date: Tue, 5 Oct 2021 11:34:09 +0000 -Subject: [PATCH] Citadel Gnome Shell changes - ---- - data/org.gnome.shell.gschema.xml.in | 8 + - data/theme/gnome-shell-sass/_widgets.scss | 2 + - .../gnome-shell-sass/widgets/_realms.scss | 17 + - data/theme/gnome-shell.css | 67 ++- - data/theme/meson.build | 1 + - js/js-resources.gresource.xml | 7 + - js/ui/main.js | 4 + - js/ui/overview.js | 2 +- - js/ui/overviewControls.js | 3 + - js/ui/realms/realmIndicator.js | 37 ++ - js/ui/realms/realmManager.js | 55 +++ - js/ui/realms/realmSearchProvider.js | 326 +++++++++++++ - js/ui/realms/realmSwitcher.js | 325 +++++++++++++ - js/ui/realms/realmWindowFrame.js | 308 ++++++++++++ - js/ui/realms/realmWindowMenu.js | 133 +++++ - js/ui/windowManager.js | 41 +- - js/ui/windowMenu.js | 20 +- - js/ui/workspacesView.js | 12 + - src/gnome-shell-plugin.c | 19 + - src/meson.build | 9 + - src/shell-app-cache.c | 2 +- - src/shell-app-system.c | 179 ++++++- - src/shell-app-system.h | 6 +- - src/shell-global.c | 3 + - src/shell-realm-item.c | 396 +++++++++++++++ - src/shell-realm-item.h | 35 ++ - src/shell-realm-tracker.c | 310 ++++++++++++ - src/shell-realm-tracker.h | 14 + - src/shell-realms-private.h | 21 + - src/shell-realms-window-frames.c | 386 +++++++++++++++ - src/shell-realms-window-frames.h | 17 + - src/shell-realms.c | 460 ++++++++++++++++++ - src/shell-realms.h | 26 + - src/shell-window-tracker.c | 42 +- - 34 files changed, 3245 insertions(+), 48 deletions(-) - create mode 100644 data/theme/gnome-shell-sass/widgets/_realms.scss - create mode 100644 js/ui/realms/realmIndicator.js - create mode 100644 js/ui/realms/realmManager.js - create mode 100644 js/ui/realms/realmSearchProvider.js - create mode 100644 js/ui/realms/realmSwitcher.js - create mode 100644 js/ui/realms/realmWindowFrame.js - create mode 100644 js/ui/realms/realmWindowMenu.js - create mode 100644 src/shell-realm-item.c - create mode 100644 src/shell-realm-item.h - create mode 100644 src/shell-realm-tracker.c - create mode 100644 src/shell-realm-tracker.h - create mode 100644 src/shell-realms-private.h - create mode 100644 src/shell-realms-window-frames.c - create mode 100644 src/shell-realms-window-frames.h - create mode 100644 src/shell-realms.c - create mode 100644 src/shell-realms.h - -diff --git a/data/org.gnome.shell.gschema.xml.in b/data/org.gnome.shell.gschema.xml.in -index a805d52..edc03b9 100644 ---- a/data/org.gnome.shell.gschema.xml.in -+++ b/data/org.gnome.shell.gschema.xml.in -@@ -230,6 +230,14 @@ - ["<Super>9"] - Switch to application 9 - -+ -+ ["<Primary>Tab"] -+ Open Realm Switcher -+ -+ -+ ["<Shift><Primary>Tab"] -+ Open Realm Switcher Backwards -+ - - - ui/status/remoteAccess.js - ui/status/system.js - ui/status/thunderbolt.js -+ -+ ui/realms/realmIndicator.js -+ ui/realms/realmManager.js -+ ui/realms/realmSearchProvider.js -+ ui/realms/realmSwitcher.js -+ ui/realms/realmWindowFrame.js -+ ui/realms/realmWindowMenu.js - - -diff --git a/js/ui/main.js b/js/ui/main.js -index c7bac11..e032b14 100644 ---- a/js/ui/main.js -+++ b/js/ui/main.js -@@ -49,6 +49,7 @@ const PointerA11yTimeout = imports.ui.pointerA11yTimeout; - const ParentalControlsManager = imports.misc.parentalControlsManager; - const Config = imports.misc.config; - const Util = imports.misc.util; -+const RealmManager = imports.ui.realms.realmManager; - - const WELCOME_DIALOG_LAST_SHOWN_VERSION = 'welcome-dialog-last-shown-version'; - // Make sure to mention the point release, otherwise it will show every time -@@ -91,6 +92,7 @@ var kbdA11yDialog = null; - var inputMethod = null; - var introspectService = null; - var locatePointer = null; -+var realmManager = null; - let _startDate; - let _defaultCssStylesheet = null; - let _cssStylesheet = null; -@@ -264,6 +266,8 @@ function _initializeUI() { - extensionManager = new ExtensionSystem.ExtensionManager(); - extensionManager.init(); - -+ realmManager = new RealmManager.RealmManager(); -+ - if (sessionMode.isGreeter && screenShield) { - layoutManager.connect('startup-prepared', () => { - screenShield.showDialog(); -diff --git a/js/ui/overview.js b/js/ui/overview.js -index 87bf835..12054e1 100644 ---- a/js/ui/overview.js -+++ b/js/ui/overview.js -@@ -251,7 +251,7 @@ var Overview = class { - DND.addDragMonitor(this._dragMonitor); - // Remember the workspace we started from - let workspaceManager = global.workspace_manager; -- this._lastActiveWorkspaceIndex = workspaceManager.get_active_workspace_index(); -+ this._lastActiveWorkspaceIndex = workspaceManager.get_active_workspace_id(); - } - - _onDragEnd(time) { -diff --git a/js/ui/overviewControls.js b/js/ui/overviewControls.js -index 6cbaa22..97780e9 100644 ---- a/js/ui/overviewControls.js -+++ b/js/ui/overviewControls.js -@@ -342,6 +342,8 @@ class ControlsManager extends St.Widget { - workspaceManager.connect('notify::n-workspaces', - this._updateAdjustment.bind(this)); - -+ this._contextSwitchedId = workspaceManager.connect('context-switched', this._updateAdjustment.bind(this)); -+ - this._searchController = new SearchController.SearchController( - this._searchEntry, - this.dash.showAppsButton); -@@ -653,6 +655,7 @@ class ControlsManager extends St.Widget { - - _onDestroy() { - global.workspace_manager.disconnect(this._nWorkspacesNotifyId); -+ global.workspace_manager.disconnect(this._contextSwitchedId); - } - - _updateAdjustment() { -diff --git a/js/ui/realms/realmIndicator.js b/js/ui/realms/realmIndicator.js -new file mode 100644 -index 0000000..d71e346 ---- /dev/null -+++ b/js/ui/realms/realmIndicator.js -@@ -0,0 +1,37 @@ -+const { Clutter, GObject, Shell, St } = imports.gi; -+const PanelMenu = imports.ui.panelMenu; -+ -+var RealmPanelIndicator = GObject.registerClass( -+class RealmPanelIndicator extends PanelMenu.Button { -+ _init() { -+ super._init(0.5, "Current Realm"); -+ -+ this._label = new St.Label({ -+ style_class: 'current-realm-label', -+ y_align: Clutter.ActorAlign.CENTER -+ }); -+ this.add_child(this._label); -+ -+ this.update(); -+ } -+ -+ clear() { -+ this._label.set_text(''); -+ } -+ -+ update() { -+ let realm_name = ''; -+ const realms = Shell.Realms.get_default(); -+ let current = realms.current_realm; -+ if (current) { -+ realm_name = current.realm_name; -+ this._label.set_text(`realm-${realm_name}`); -+ } -+ -+ if (realm_name.length > 0) { -+ this._label.set_text(`realm-${realm_name}`); -+ } else { -+ this._label.set_text(''); -+ } -+ } -+ }); -diff --git a/js/ui/realms/realmManager.js b/js/ui/realms/realmManager.js -new file mode 100644 -index 0000000..30e27a4 ---- /dev/null -+++ b/js/ui/realms/realmManager.js -@@ -0,0 +1,55 @@ -+const { Clutter, Gio, Meta, Shell, St } = imports.gi; -+ -+const Main = imports.ui.main; -+const RealmIndicator = imports.ui.realms.realmIndicator; -+const RealmSwitcher = imports.ui.realms.realmSwitcher; -+const Lightbox = imports.ui.lightbox; -+const RealmSearchProvider = imports.ui.realms.realmSearchProvider; -+const RealmWindowFrame = imports.ui.realms.realmWindowFrame; -+ -+var RealmManager = class { -+ constructor() { -+ -+ this._realmIndicator = new RealmIndicator.RealmPanelIndicator(); -+ Main.panel.addToStatusArea('RealmIndicator', this._realmIndicator); -+ -+ this._switchAction = Main.wm.addKeybinding('switch-realm', -+ new Gio.Settings({ schema_id: "org.gnome.shell.keybindings"}), -+ Meta.KeyBindingFlags.NONE, -+ Shell.ActionMode.NORMAL, -+ this._switchRealms.bind(this)); -+ -+ this._switchActionBackward = Main.wm.addKeybinding('switch-realm-backward', -+ new Gio.Settings({ schema_id: "org.gnome.shell.keybindings"}), -+ Meta.KeyBindingFlags.IS_REVERSED, -+ Shell.ActionMode.NORMAL, -+ this._switchRealms.bind(this)); -+ -+ const realms = Shell.Realms.get_default(); -+ realms.connect('realm-context-switched', () => { -+ Main.overview.dash._queueRedisplay(); -+ this._realmIndicator.update(); -+ }); -+ -+ this._switchAnimation = new RealmSwitcher.ContextSwitchAnimationController(this._realmIndicator); -+ -+ this._searchResults = Main.overview._overview.controls._searchController._searchResults; -+ this._searchProvider = new RealmSearchProvider.RealmSearchProvider(); -+ this._searchProvider.createResultDisplay(this._searchResults); -+ this._searchResults._registerProvider(this._searchProvider); -+ -+ this._frameManager = new RealmWindowFrame.WindowFrameManager(); -+ } -+ -+ animateSwitch(from, to, onComplete) { -+ this._switchAnimation.animateSwitch(from, to, onComplete); -+ } -+ -+ _switchRealms(display, window, binding) { -+ let popup = new RealmSwitcher.SwitchRealmPopup(this._switchAction, this._switchActionBackward); -+ if (!popup.show(binding.is_reversed(), binding.get_name(), binding.get_mask())) -+ popup.fadeAndDestroy(); -+ } -+ -+}; -+ -diff --git a/js/ui/realms/realmSearchProvider.js b/js/ui/realms/realmSearchProvider.js -new file mode 100644 -index 0000000..00ad04d ---- /dev/null -+++ b/js/ui/realms/realmSearchProvider.js -@@ -0,0 +1,326 @@ -+const { Clutter, GObject, Pango, Shell, St } = imports.gi; -+ -+const Search = imports.ui.search; -+const Main = imports.ui.main; -+const Util = imports.misc.util; -+ -+// Based on ProviderInfo in search.js -+var RealmProviderInfo = GObject.registerClass( -+class RealmProviderInfo extends St.Button { -+ _init() { -+ super._init({ -+ style_class: 'search-provider-icon', -+ reactive: false, -+ can_focus: false, -+ accessible_name: "Realms", -+ track_hover: false, -+ y_align: Clutter.ActorAlign.START, -+ }); -+ -+ this._content = new St.BoxLayout({ vertical: false, -+ style_class: 'list-search-provider-content' }); -+ this.set_child(this._content); -+ -+ let icon = new St.Icon({ icon_size: this.PROVIDER_ICON_SIZE, -+ icon_name: 'computer' }); -+ -+ let detailsBox = new St.BoxLayout({ style_class: 'list-search-provider-details', -+ vertical: true, -+ x_expand: true }); -+ -+ let nameLabel = new St.Label({ -+ text: "Realms", -+ x_align: Clutter.ActorAlign.START -+ }); -+ -+ this._moreLabel = new St.Label({ x_align: Clutter.ActorAlign.START }); -+ -+ detailsBox.add_actor(nameLabel); -+ detailsBox.add_actor(this._moreLabel); -+ -+ -+ this._content.add_actor(icon); -+ this._content.add_actor(detailsBox); -+ } -+ -+ get PROVIDER_ICON_SIZE() { -+ return 48; -+ } -+ -+ setMoreCount(count) { -+ this._moreLabel.text = ngettext("%d more", "%d more", count).format(count); -+ this._moreLabel.visible = count > 0; -+ } -+}); -+ -+var MAX_LIST_SEARCH_RESULTS_ROWS = 10; -+ -+// Based on ListSearchResult in search.js -+var RealmSearchResult = GObject.registerClass( -+class ListSearchResult extends Search.SearchResult { -+ _init(provider, metaInfo, resultsView) { -+ super._init(provider, metaInfo, resultsView); -+ -+ this.style_class = 'list-search-result'; -+ -+ let content = new St.BoxLayout({ -+ style_class: 'list-search-result-content', -+ vertical: false, -+ x_align: Clutter.ActorAlign.FILL, -+ x_expand: true, -+ y_expand: true, -+ }); -+ this.set_child(content); -+ -+ this._termsChangedId = 0; -+ -+ let titleBox = new St.BoxLayout({ -+ style_class: 'list-search-result-title', -+ y_align: Clutter.ActorAlign.CENTER, -+ }); -+ -+ content.add_child(titleBox); -+ -+ // An icon for, or thumbnail of, content -+ let icon = this.metaInfo['createIcon'](this.ICON_SIZE); -+ if (icon) -+ titleBox.add(icon); -+ -+ let title = new St.Label({ -+ text: this.metaInfo['name'], -+ y_align: Clutter.ActorAlign.CENTER, -+ }); -+ titleBox.add_child(title); -+ -+ this.label_actor = title; -+ -+ if (this.metaInfo['description']) { -+ this._descriptionLabel = new St.Label({ -+ style_class: 'list-search-result-description', -+ x_expand: true, -+ x_align: Clutter.ActorAlign.START, -+ y_align: Clutter.ActorAlign.CENTER, -+ }); -+ this._descriptionLabel.clutter_text.ellipsize = Pango.EllipsizeMode.NONE; -+ content.add_child(this._descriptionLabel); -+ -+ this._termsChangedId = -+ this._resultsView.connect('terms-changed', -+ this._highlightTerms.bind(this)); -+ -+ this._highlightTerms(); -+ } -+ -+ let id = this.metaInfo['id']; -+ -+ if (id != ':new:') { -+ this.configButton = new St.Button({ -+ style_class: 'button', -+ track_hover: true, -+ can_focus: true, -+ child: new St.Icon({ -+ style_class: 'realm-config-icon', -+ icon_name: 'emblem-system-symbolic', -+ icon_size: 24, -+ }), -+ }); -+ -+ this.configButton.connect('clicked', () => { -+ Main.overview.toggle(); -+ Util.spawn(['/usr/libexec/realm-config-ui', id]); -+ }); -+ content.add_child(this.configButton); -+ } -+ -+ this.connect('destroy', this._onDestroy.bind(this)); -+ } -+ -+ get ICON_SIZE() { -+ return 24; -+ } -+ -+ _highlightTerms() { -+ let markup = this._resultsView.highlightTerms(this.metaInfo['description'].split('\n')[0]); -+ this._descriptionLabel.clutter_text.set_markup(markup); -+ } -+ -+ _onDestroy() { -+ if (this._termsChangedId) -+ this._resultsView.disconnect(this._termsChangedId); -+ this._termsChangedId = 0; -+ } -+}); -+ -+// Based on ListSearchResults in search.js -+var RealmSearchResults = GObject.registerClass( -+class RealmSearchResults extends Search.SearchResultsBase { -+ _init(provider, resultsView) { -+ super._init(provider, resultsView); -+ -+ this._container = new St.BoxLayout({ style_class: 'search-section-content' }); -+ this.providerInfo = new RealmProviderInfo(); -+ this.providerInfo.connect('key-focus-in', this._keyFocusIn.bind(this)); -+ this.providerInfo.connect('clicked', () => { -+ Main.overview.toggle(); -+ }); -+ -+ this._container.add_child(this.providerInfo); -+ -+ this._content = new St.BoxLayout({ -+ style_class: 'list-search-results', -+ vertical: true, -+ x_expand: true, -+ }); -+ this._container.add_child(this._content); -+ -+ this._resultDisplayBin.set_child(this._container); -+ } -+ -+ _setMoreCount(count) { -+ this.providerInfo.setMoreCount(count); -+ } -+ -+ _getMaxDisplayedResults() { -+ return MAX_LIST_SEARCH_RESULTS_ROWS; -+ } -+ -+ _clearResultDisplay() { -+ this._content.remove_all_children(); -+ } -+ -+ _createResultDisplay(meta) { -+ return super._createResultDisplay(meta) || -+ new RealmSearchResult(this.provider, meta, this._resultsView); -+ } -+ -+ _addItem(display) { -+ this._content.add_actor(display); -+ } -+ -+ getFirstResult() { -+ if (this._content.get_n_children() > 0) -+ return this._content.get_child_at_index(0); -+ else -+ return null; -+ } -+ -+}); -+ -+var RealmSearchProvider = class RealmSearchProvider { -+ constructor() { -+ this._shellRealms = Shell.Realms.get_default(); -+ this.id = 'realms'; -+ this.isRemoteProvider = false; -+ this.canLaunchSearch = false; -+ this.display = null; -+ } -+ -+ createResultDisplay(resultsView) { -+ this.display = new RealmSearchResults(this, resultsView); -+ this.display.connect('notify::focus-child', resultsView._focusChildChanged.bind(resultsView)); -+ this.display.hide(); -+ resultsView._content.add(this.display); -+ } -+ -+ createIcon(size, realm) { -+ if (realm.is_running()) { -+ return new St.Icon({ icon_name: 'emblem-synchronizing', icon_size: size }); -+ } else { -+ return new St.Icon({ icon_name: 'exaile', icon_size: size }); -+ } -+ } -+ -+ createRealmMeta(realm) { -+ let id = realm.get_realm_name(); -+ let description = ''; -+ if (realm.is_running()) { -+ description = `Set realm-${id} as current realm`; -+ } else { -+ description = `Start realm-${id}`; -+ } -+ -+ return { -+ id: id, -+ name: `realm-${id}`, -+ description: description, -+ createIcon: size => { -+ return this.createIcon(size, realm); -+ }, -+ }; -+ } -+ -+ createNewRealmMeta() { -+ return { -+ id: ':new:', -+ name: 'New Realm', -+ description: 'Create a new realm', -+ createIcon: size => { -+ return new St.Icon({ -+ icon_name: 'computer', -+ icon_size: size, -+ }); -+ } -+ } -+ } -+ -+ getResultMetas (ids, callback) { -+ let metas = []; -+ -+ for (let id of ids) { -+ if (id == ":new:") { -+ metas.push(this.createNewRealmMeta()); -+ } else { -+ let realm = this._shellRealms.realm_by_name(id); -+ if (realm && !realm.is_current()) { -+ metas.push(this.createRealmMeta(realm)); -+ } else { -+ log(`No realm found for ${id}`); -+ } -+ } -+ } -+ callback(metas); -+ } -+ -+ activateResult (resultId, _terms) { -+ -+ if (resultId == ':new:') { -+ Main.overview.toggle(); -+ Util.spawn(['/usr/libexec/realm-config-ui', '--new']); -+ return; -+ } -+ -+ let realm = this._shellRealms.realm_by_name(resultId); -+ if (realm) { -+ realm.set_current(); -+ } else { -+ log(`No realm found for ${resultId}`); -+ } -+ } -+ -+ filterResults(results, maxNumber) { -+ return results.slice(0, maxNumber) -+ } -+ -+ getInitialResultSet(terms, callback, _cancellable) { -+ let realms = this._shellRealms.get_all_realms(); -+ let matches = []; -+ -+ if (terms.length == 1 && "new".startsWith(terms[0])) { -+ matches.push(":new:"); -+ } -+ -+ for (let realm of realms) { -+ if (!realm.is_current()) { -+ let name = realm.get_realm_name(); -+ if (terms.every(t => name.indexOf(t) != -1)) { -+ matches.push(name); -+ } -+ } -+ } -+ callback(matches); -+ } -+ -+ getSubsearchResultSet(previousResults, terms, callback, cancellable) { -+ this.getInitialResultSet(terms, callback, cancellable) -+ } -+} -diff --git a/js/ui/realms/realmSwitcher.js b/js/ui/realms/realmSwitcher.js -new file mode 100644 -index 0000000..507df00 ---- /dev/null -+++ b/js/ui/realms/realmSwitcher.js -@@ -0,0 +1,325 @@ -+ -+const { Clutter, GObject, Meta, Shell, St } = imports.gi; -+ -+const Background = imports.ui.background; -+const SwitcherPopup = imports.ui.switcherPopup; -+const Layout = imports.ui.layout; -+const Main = imports.ui.main; -+ -+const WINDOW_ANIMATION_TIME = 2000; -+var APP_ICON_SIZE = 96; -+ -+var RealmItem = GObject.registerClass( -+class RealmItem extends St.BoxLayout { -+ _init(realm, workspace_group) { -+ super._init({ vertical: true }); -+ this.realm = realm; -+ -+ this.add_child(workspace_group); -+ -+ this.label = new St.Label({ -+ text: `realm-${this.realm.realm_name}`, -+ x_align: Clutter.ActorAlign.CENTER, -+ }); -+ -+ this.add_child(this.label); -+ } -+ -+ activate() { -+ this.realm.set_current(); -+ } -+ -+}); -+ -+function getRealmItems() { -+ const monitor = Main.layoutManager.primaryMonitor; -+ const realms = Shell.Realms.get_default(); -+ let realm_list = realms.get_running_realms(); -+ let items = []; -+ realm_list.forEach(realm => { -+ let ws = realm.get_active_workspace(); -+ if (ws) { -+ let size = 256; // default thumbnail size -+ let scale = Math.min(1.0, size / monitor.width, size / monitor.height); -+ let wsgroup = new WorkspaceGroup(ws, monitor, scale); -+ items.push(new RealmItem(realm, wsgroup)); -+ } -+ }); -+ return items; -+} -+ -+var SwitchRealmList = GObject.registerClass( -+class SwitchRealmList extends SwitcherPopup.SwitcherList { -+ _init(items) { -+ super._init(false); -+ -+ items.forEach(item => { -+ this.addItem(item, item.label); -+ }); -+ } -+}); -+ -+var SwitchRealmPopup = GObject.registerClass( -+class SwitchRealmPopup extends SwitcherPopup.SwitcherPopup { -+ _init(action, actionBackward) { -+ super._init(); -+ this._action = action; -+ this._actionBackward = actionBackward; -+ this._items = getRealmItems(); -+ this._switcherList = new SwitchRealmList(this._items); -+ } -+ -+ _keyPressHandler(keysym, action) { -+ if (action == this._action) -+ this._select(this._next()); -+ else if (action == this._actionBackward) -+ this._select(this._previous()); -+ else if (keysym == Clutter.KEY_Left) -+ this._select(this._previous()); -+ else if (keysym == Clutter.KEY_Right) -+ this._select(this._next()); -+ else -+ return Clutter.EVENT_PROPAGATE; -+ -+ return Clutter.EVENT_STOP; -+ } -+ -+ _finish() { -+ super._finish(); -+ this._items[this._selectedIndex].activate(); -+ } -+ -+}); -+ -+const WorkspaceGroup = GObject.registerClass( -+class WorkspaceGroup extends Clutter.Actor { -+ _init(workspace, monitor, scale = 1.0) { -+ super._init(); -+ this._workspace = workspace; -+ this._monitor = monitor; -+ this._scale = scale; -+ this._windowRecords = []; -+ this.width = monitor.width * scale; -+ this.height = monitor.height * scale; -+ this._background = new Meta.BackgroundGroup({ -+ width: this.width * this._scale, -+ height: this.height * this._scale, -+ }); -+ this.add_actor(this._background); -+ this._bgManager = new Background.BackgroundManager({ -+ container: this._background, -+ monitorIndex: this._monitor.index, -+ controlPosition: false, -+ }); -+ this.clip_to_allocation = true; -+ -+ this._createWindows(); -+ this.connect('destroy', this._onDestroy.bind(this)); -+ this._restackedId = global.display.connect('restacked', this._syncStacking.bind(this)); -+ } -+ -+ get workspace() { -+ return this._workspace; -+ } -+ -+ _shouldShowWindow(window) { -+ if (!window.showing_on_its_workspace()) -+ return false; -+ -+ const geometry = global.display.get_monitor_geometry(this._monitor.index); -+ const [intersects] = window.get_frame_rect().intersect(geometry); -+ if (!intersects) -+ return false; -+ -+ const isSticky = window.is_on_all_workspaces(); -+ -+ return !isSticky && window.located_on_workspace(this._workspace); -+ } -+ -+ _syncStacking() { -+ const windowActors = global.get_window_actors().filter(w => -+ this._shouldShowWindow(w.meta_window)); -+ -+ let lastRecord; -+ -+ for (const windowActor of windowActors) { -+ const record = this._windowRecords.find(r => r.windowActor === windowActor); -+ this.set_child_above_sibling(record.clone, lastRecord ? lastRecord.clone : this._background); -+ lastRecord = record; -+ } -+ } -+ -+ _createWindows() { -+ const windowActors = global.get_window_actors().filter(w => -+ this._shouldShowWindow(w.meta_window)); -+ for (const windowActor of windowActors) { -+ let [width,height] = windowActor.get_size(); -+ const clone = new Clutter.Clone({ -+ source: windowActor, -+ width: width * this._scale, -+ height: height * this._scale, -+ x: (windowActor.x - this._monitor.x) * this._scale, -+ y: (windowActor.y - this._monitor.y) * this._scale, -+ }); -+ this.add_child(clone); -+ const record = {windowActor, clone }; -+ record.windowDestroyId = windowActor.connect('destroy', () => { -+ clone.destroy(); -+ this._windowRecords.splice(this._windowRecords.indexOf(record), 1); -+ }); -+ this._windowRecords.push(record); -+ } -+ } -+ -+ _removeWindows() { -+ for (const record of this._windowRecords) { -+ record.windowActor.disconnect(record.windowDestroyId); -+ record.clone.destroy(); -+ } -+ this._windowRecords = []; -+ } -+ -+ _onDestroy() { -+ global.display.disconnect(this._restackedId); -+ this._removeWindows(); -+ this._bgManager.destroy(); -+ } -+ -+}); -+ -+const MonitorGroup = GObject.registerClass({ -+ Properties: { -+ 'progress': GObject.ParamSpec.double( -+ 'progress', 'progress', 'progress', -+ GObject.ParamFlags.READWRITE, -+ -Infinity, Infinity, 0), -+ }, -+}, class MonitorGroup extends St.Widget { -+ _init(monitor, fromIndex, toIndex) { -+ super._init({ -+ clip_to_allocation: true, -+ style_class: 'workspace-animation', -+ }); -+ this._monitor = monitor; -+ const constraint = new Layout.MonitorConstraint({ index: monitor.index }); -+ this.add_constraint(constraint); -+ -+ this._container = new Clutter.Actor(); -+ this.add_child(this._container); -+ -+ this._progress = 0; -+ this._fadeOut = true; -+ -+ this._workspaceGroups = []; -+ this._blackBackground = new Clutter.Actor(); -+ this._blackBackground.width = monitor.width; -+ this._blackBackground.height = monitor.height; -+ let [_res, color] = Clutter.Color.from_string("#000000ff"); -+ this._blackBackground.background_color = color; -+ -+ -+ -+ this.addWorkspaceByIndex(toIndex, monitor); -+ // add opaque black actor -+ this._container.add_child(this._blackBackground); -+ this.addWorkspaceByIndex(fromIndex, monitor); -+ -+ // tween 'from' WorkspaceGroup opacity from 255 to 0 fading workspace to black background -+ // tween 'block' actor opacity from 255 to 0 revealing 'to' WorkspaceGroup -+ } -+ -+ addWorkspaceByIndex(idx, monitor) { -+ const workspaceManager = global.workspace_manager; -+ const ws = workspaceManager.get_workspace_by_index(idx); -+ if (ws) { -+ const fullscreen = ws.list_windows().some(w => w.get_monitor() === monitor.index && w.is_fullscreen()); -+ const group = new WorkspaceGroup(ws, monitor); -+ this._workspaceGroups.push(group); -+ this._container.add_child(group); -+ } -+ } -+ -+ get progress() { -+ return this._progress; -+ } -+ -+ // Interpolate opacity from 0 (full opaque) to 50 (full transparent) -+ calculateOpacity(progress) { -+ return 255 - (255 * (progress / 50.0)); -+ } -+ -+ set progress(p) { -+ const fromGroup = this._workspaceGroups[this._workspaceGroups.length - 1]; -+ this._progress = p; -+ // 0 - 50 -+ if (p < 50) { -+ this._blackBackground.opacity = 255; -+ fromGroup.opacity = this.calculateOpacity(p); -+ } else if (p < 100) { -+ if (this._fadeOut) { -+ this._fadeOut = false; -+ } -+ fromGroup.opacity = 0; -+ this._blackBackground.opacity = this.calculateOpacity(p - 50); -+ } else { -+ fromGroup.opacity = 0; -+ this._blackBackground.opacity = 0; -+ } -+ } -+}); -+ -+var ContextSwitchAnimationController = class { -+ constructor(indicator) { -+ this._switchData = null; -+ this._indicator = indicator; -+ } -+ -+ _prepareContextSwitch(fromIdx, toIdx) { -+ if (this._switchData) { -+ this._switchData.monitors[0].remove_all_transitions(); -+ this._finishContextSwitch(this._switchData); -+ } -+ -+ const switchData = {}; -+ this._switchData = switchData; -+ switchData.monitors = []; -+ switchData.inProgress = false; -+ -+ const monitor = Main.layoutManager.primaryMonitor; -+ -+ const group = new MonitorGroup(monitor, fromIdx, toIdx); -+ -+ Main.uiGroup.insert_child_above(group, global.window_group); -+ -+ switchData.monitors.push(group); -+ -+ Meta.disable_unredirect_for_display(global.display); -+ } -+ -+ _finishContextSwitch(switchData) { -+ Meta.enable_unredirect_for_display(global.display); -+ this._indicator.update(); -+ this._switchData = null; -+ switchData.monitors.forEach(m => m.destroy()); -+ if (switchData.onComplete) { -+ switchData.onComplete(); -+ } -+ } -+ -+ animateSwitch(fromIdx, toIdx, onComplete) { -+ -+ this._prepareContextSwitch(fromIdx, toIdx); -+ this._switchData.inProgress = true; -+ this._switchData.onComplete = onComplete; -+ -+ const params = { -+ duration: WINDOW_ANIMATION_TIME, -+ mode: Clutter.AnimationMode.EASE_IN_OUT_QUAD, -+ }; -+ params.onComplete = () => { -+ this._finishContextSwitch(this._switchData); -+ }; -+ this._indicator.clear(); -+ this._switchData.monitors[0].ease_property('progress', 100, params); -+ } -+} -diff --git a/js/ui/realms/realmWindowFrame.js b/js/ui/realms/realmWindowFrame.js -new file mode 100644 -index 0000000..abd4902 ---- /dev/null -+++ b/js/ui/realms/realmWindowFrame.js -@@ -0,0 +1,308 @@ -+const { Clutter, Cogl, GObject, Meta, Shell, St } = imports.gi; -+ -+var WindowFrameManager = class WindowFrameManager { -+ constructor() { -+ this._realms = Shell.Realms.get_default(); -+ let frames = this._realms.window_frames(); -+ this._frame_effects = []; -+ -+ global.window_manager.connect('map', this._handleWindowMap.bind(this)); -+ global.workspace_manager.connect('context-window-moved', this._onContextWindowMoved.bind(this)); -+ global.workspace_manager.connect('context-removed', this._onContextRemoved.bind(this)); -+ frames.connect('realm-frame-colors-changed', this._onFrameColorsChanged.bind(this)); -+ -+ this.trackWindows(); -+ } -+ -+ _onContextWindowMoved(workspaceManager, window) { -+ let actor = window.get_compositor_private(); -+ if (actor) { -+ this.handleWindow(actor); -+ } -+ return Clutter.EVENT_PROPAGATE; -+ } -+ -+ _handleWindowMap(shellwm, actor) { -+ this.handleWindow(actor); -+ return Clutter.EVENT_PROPAGATE; -+ } -+ -+ _onContextRemoved(workspaceManager, id) { -+ this.trackWindows(); -+ } -+ -+ _onFrameColorsChanged(realms) { -+ this.trackWindows(); -+ } -+ -+ trackWindows() { -+ var actors = global.get_window_actors(); -+ actors.forEach(a => this.handleWindow(a)); -+ } -+ -+ handleWindow(actor) { -+ let win = actor.metaWindow; -+ let win_id = win.get_stable_sequence(); -+ let effect = this._frame_effects[win_id]; -+ -+ let frames = this._realms.window_frames(); -+ -+ if (frames.has_frame(win) && frames.is_frame_enabled(win)) { -+ let color = frames.color_for_window(win); -+ -+ if (effect) { -+ effect.setColor(color); -+ } else { -+ let label = frames.label_for_window(win); -+ effect = new RealmFrameEffect(actor, color, label); -+ this._frame_effects[win_id] = effect; -+ } -+ } else if (effect) { -+ effect.removeEffect(actor); -+ this._frame_effects[win_id] = null; -+ } -+ } -+} -+ -+var RealmFrameEffect = GObject.registerClass( -+class RealmFrameEffect extends Clutter.Effect { -+ _init(actor, color, label_text) { -+ super._init(); -+ this._frame_width = 2; -+ this._pipeline = null; -+ this._color = color; -+ this._label_on_top = true; -+ this._label = null; -+ -+ if (label_text) { -+ this._label = this._createLabel(actor, label_text); -+ this._updateLabel(actor.metaWindow); -+ } -+ -+ this._sizeChangedId = actor.metaWindow.connect('size-changed', window => { -+ this._updateLabel(window); -+ }); -+ -+ actor.add_effect(this); -+ } -+ -+ removeEffect(actor) { -+ if (this._label) { -+ actor.remove_child(this._label); -+ this._label = null; -+ } -+ if (this._sizeChangedId) { -+ let win = actor.metaWindow; -+ win.disconnect(this._sizeChangedId); -+ this._sizeChangedId = 0; -+ } -+ actor.remove_effect(this); -+ } -+ -+ _createLabel(actor, label_text) { -+ let label = new St.Label({ -+ style_class: 'realm-frame-label', -+ z_position: 1.0, -+ -+ }); -+ label.set_text(' '+label_text+' '); -+ actor.add_child(label); -+ return label; -+ } -+ -+ _updateLabel(window) { -+ if (this._label) { -+ this._updateLabelPosition(window); -+ this._updateLabelColor(); -+ } -+ } -+ -+ _updateLabelPosition(window) { -+ -+ if (!this._label_height) { -+ // If we scale the text, the reported size of the label will not be the value we need so -+ // save the initial value. -+ this._label_height = this._label.get_height(); -+ } -+ -+ -+ let maximized = window.is_fullscreen() === true || // Fullscreen -+ [Meta.MaximizeFlags.BOTH, Meta.MaximizeFlags.VERTICAL].includes(window.get_maximized()); // Maximized -+ -+ this._label_on_top = !maximized; -+ -+ let frame_rect = window.get_frame_rect(); -+ let buffer_rect = window.get_buffer_rect(); -+ -+ let offsetX = frame_rect.x - buffer_rect.x; -+ let offsetY = frame_rect.y - buffer_rect.y; -+ -+ -+ if (window.get_client_type() === Meta.WindowClientType.WAYLAND) { -+ let scaleFactor = St.ThemeContext.get_for_stage(global.stage).scale_factor; -+ if (scaleFactor !== 1) { -+ offsetX = offsetX / scaleFactor; -+ this._label.set_style(`font-size: ${12 / scaleFactor}pt;`); -+ } -+ offsetX -= 1; -+ offsetY -= 4; -+ } -+ -+ // If label is on top and there is enough space above title bar move position up by label height -+ if (this._label_on_top && this._label_height <= offsetY) { -+ offsetY -= this._label_height; -+ } else if (maximized) { -+ offsetX = 0; -+ offsetY = 0; -+ } -+ this._label.set_position(offsetX, offsetY); -+ } -+ -+ _updateLabelColor() { -+ let fg = new Clutter.Color({ -+ red: 0, -+ green: 0, -+ blue: 0, -+ alpha: 96, -+ }); -+ -+ let bg = this._color.copy(); -+ -+ if (this._label_on_top) { -+ bg.alpha = 100; -+ } else { -+ bg.alpha = 200; -+ } -+ -+ let clutter_text = this._label.get_clutter_text(); -+ clutter_text.set_color(fg); -+ clutter_text.set_background_color(bg); -+ } -+ -+ -+ setColor(color) { -+ if (this._color && this._color.equal(color)) { -+ return; -+ } -+ this._color = color; -+ this.setPipelineColor(); -+ if (this._label) { -+ this._updateLabelColor(); -+ } -+ } -+ -+ setPipelineColor() { -+ if (!this._color || !this._pipeline) { -+ return; -+ } -+ let s = this._color.to_string(); -+ -+ let cogl_color = new Cogl.Color(); -+ cogl_color.init_from_4ub(this._color.red, this._color.green, this._color.blue, 0xc4); -+ this._pipeline.set_color(cogl_color); -+ } -+ -+ _calculate_frame_box(window, allocation) { -+ let frame_rect = window.get_frame_rect(); -+ let buffer_rect = window.get_buffer_rect(); -+ -+ let offsetX = frame_rect.x - buffer_rect.x; -+ let offsetY = frame_rect.y - buffer_rect.y; -+ -+ let top = offsetY - 3; -+ let bottom = offsetY - 1; -+ let left = offsetX - 3; -+ let right = offsetX - 1; -+ -+ let wayland = window.get_client_type() == Meta.WindowClientType.WAYLAND; -+ -+ if (wayland) { -+ bottom += 4; -+ } -+ -+ let fw = this._frame_width; -+ -+ -+ switch (window.get_maximized()) { -+ case Meta.MaximizeFlags.BOTH: -+ top += fw; -+ right += fw; -+ bottom += fw - (wayland ? 5 : 0); -+ left += fw; -+ break; -+ case Meta.MaximizeFlags.HORIZONTAL: -+ right += fw; -+ left += fw; -+ break; -+ case Meta.MaximizeFlags.VERTICAL: -+ top += fw; -+ bottom += fw; -+ break; -+ } -+ -+ if (window.is_fullscreen()) { -+ top += 3; -+ right += 2; -+ bottom -= (wayland ? 3 : 0); -+ left += 3; -+ } -+ -+ if (!wayland && !window.decorated && !window.is_fullscreen() && (window.get_maximized() !== Meta.MaximizeFlags.BOTH)) { -+ bottom += 4; -+ } -+ -+ let x = left; -+ let y = top + fw; -+ let w = allocation.get_width() - (right + left); -+ let h = allocation.get_height() - (bottom + top + fw); -+ -+ return [x, y, w, h]; -+ } -+ -+ draw_rect(node, x, y, width, height) { -+ const box = new Clutter.ActorBox(); -+ box.set_origin(x, y); -+ box.set_size(width, height); -+ node.add_rectangle(box); -+ } -+ -+ draw_hline(node, x, y, width, width_factor = 1) { -+ this.draw_rect(node, x, y, width, this._frame_width * width_factor); -+ } -+ -+ draw_vline(node, x, y, height, width_factor = 1) { -+ this.draw_rect(node, x, y, this._frame_width * width_factor, height); -+ } -+ -+ vfunc_paint_node(node, ctx) { -+ let actor = this.get_actor(); -+ -+ const actorNode = new Clutter.ActorNode(actor, -1); -+ node.add_child(actorNode); -+ -+ if (!this._pipeline) { -+ let framebuffer = ctx.get_framebuffer(); -+ let coglContext = framebuffer.get_context(); -+ this._pipeline = new Cogl.Pipeline(coglContext); -+ this.setPipelineColor(); -+ } -+ -+ const pipelineNode = new Clutter.PipelineNode(this._pipeline); -+ pipelineNode.set_name('Realm Frame'); -+ node.add_child(pipelineNode); -+ -+ let [x, y, width, height] = this._calculate_frame_box(actor.metaWindow, actor.get_allocation_box()); -+ -+ // Top -+ this.draw_hline(pipelineNode, x, y, width, 2); -+ -+ // Right -+ this.draw_vline(pipelineNode, x + width, y, height); -+ -+ // Bottom -+ this.draw_hline(pipelineNode, x, y + height, width); -+ -+ // Left -+ this.draw_vline(pipelineNode, x, y, height); -+ } -+}); -diff --git a/js/ui/realms/realmWindowMenu.js b/js/ui/realms/realmWindowMenu.js -new file mode 100644 -index 0000000..6418724 ---- /dev/null -+++ b/js/ui/realms/realmWindowMenu.js -@@ -0,0 +1,133 @@ -+ -+const { Shell, GObject } = imports.gi; -+ -+const PopupMenu = imports.ui.popupMenu; -+ -+function _windowAppId(window) { -+ const tracker = Shell.WindowTracker.get_default(); -+ const app = tracker.get_window_app(window); -+ if (app) { -+ return app.get_id(); -+ } else { -+ log(`No app found for window ${window.get_description()}`) -+ return null; -+ } -+} -+ -+function windowMenuDebugString(window) { -+ const id = _windowAppId(window); -+ const realm_name = windowRealmName(window); -+ -+ if (!realm_name) { -+ return id; -+ } else if (window.is_on_foreign_workspace_context()) { -+ return `${id} [${realm_name}]`; -+ } else { -+ return `${id} (${realm_name})`; -+ } -+} -+ -+function _createMoveWindowItem(label, realm_name, window) { -+ let item = new PopupMenu.PopupMenuItem(label); -+ item.connect('activate', () => { -+ let realms = Shell.Realms.get_default(); -+ let realm = realms.realm_by_name(realm_name); -+ -+ if (realm) { -+ realm.move_window_to_context(window); -+ } -+ }); -+ return item; -+} -+ -+// Return name of the realm the application this window belongs to is running in. -+function windowRealmName(window) { -+ const realms = Shell.Realms.get_default(); -+ -+ if (realms.is_citadel_window(window)) { -+ return "Citadel" -+ } -+ -+ let realm = realms.realm_by_window(window); -+ -+ if (realm) { -+ return realm.realm_name; -+ } else { -+ return null; -+ } -+} -+ -+// Return name of realm the context this window is currently located on belongs to -+function windowContextRealmName(window) { -+ if (window.on_all_workspaces) { -+ return windowRealmName(window); -+ } -+ -+ let ws = window.get_workspace(); -+ -+ if (!ws) { -+ return null; -+ } -+ const realms = Shell.Realms.get_default(); -+ let realm = realms.realm_by_context_id(ws.get_context_id()); -+ -+ if (realm) { -+ return realm.realm_name; -+ } else { -+ return null; -+ } -+} -+function enableFrameItem(window) { -+ const realms = Shell.Realms.get_default(); -+ const frames = realms.window_frames(); -+ if (!frames.has_frame(window)) { -+ return null; -+ } -+ let enabled = frames.is_frame_enabled(window); -+ let item = new PopupMenu.PopupMenuItem("Display colored window frame"); -+ if (enabled) { -+ item.setOrnament(PopupMenu.Ornament.CHECK); -+ } -+ -+ item.connect('activate', () => { -+ let realms = Shell.Realms.get_default(); -+ const frames = realms.window_frames(); -+ frames.set_frame_enabled(window, !enabled); -+ }); -+ -+ return item; -+} -+ -+function realmWindowMenu(window) { -+ -+ const realm_name = windowContextRealmName(window); -+ -+ if (!realm_name) { -+ return null; -+ } -+ -+ const realms = Shell.Realms.get_default(); -+ let other_realms = []; -+ -+ let running_realms = realms.get_running_realms(); -+ running_realms.forEach(realm => { -+ if (realm.realm_name != realm_name) { -+ other_realms.push(realm.realm_name); -+ } -+ }); -+ -+ if (other_realms.length == 0) { -+ return null; -+ } else if (other_realms.length == 1) { -+ let name = other_realms[0]; -+ return _createMoveWindowItem(`Move to realm-${name}`, name, window); -+ } -+ -+ let subMenu = new PopupMenu.PopupSubMenuMenuItem('Move to Realm...', true); -+ -+ other_realms.forEach(name => { -+ let item = _createMoveWindowItem(`realm-${name}`, name, window); -+ subMenu.menu.addMenuItem(item); -+ }); -+ return subMenu; -+} -\ No newline at end of file -diff --git a/js/ui/windowManager.js b/js/ui/windowManager.js -index caaecec..a8ff7f9 100644 ---- a/js/ui/windowManager.js -+++ b/js/ui/windowManager.js -@@ -198,6 +198,8 @@ var WorkspaceTracker = class { - workspaceManager.connect('workspaces-reordered', () => { - this._workspaces.sort((a, b) => a.index() - b.index()); - }); -+ workspaceManager.connect('context-switched', -+ this._workspaceContextSwitched.bind(this)); - global.window_manager.connect('switch-workspace', - this._queueCheckWorkspaces.bind(this)); - -@@ -253,6 +255,8 @@ var WorkspaceTracker = class { - emptyWorkspaces[index] = false; - } - -+ let current_context_id = workspaceManager.active_context_id(); -+ - let windows = global.get_window_actors(); - for (i = 0; i < windows.length; i++) { - let actor = windows[i]; -@@ -261,7 +265,12 @@ var WorkspaceTracker = class { - if (win.is_on_all_workspaces()) - continue; - -- let workspaceIndex = win.get_workspace().index(); -+ let workspace = win.get_workspace(); -+ -+ if (workspace.get_context_id() != current_context_id) -+ continue; -+ -+ let workspaceIndex = workspace.index(); - emptyWorkspaces[workspaceIndex] = false; - } - -@@ -339,6 +348,28 @@ var WorkspaceTracker = class { - this._checkWorkspacesId = Meta.later_add(Meta.LaterType.BEFORE_REDRAW, this._checkWorkspaces.bind(this)); - } - -+ -+ _workspaceContextSwitched() { -+ let workspaceManager = global.workspace_manager; -+ let numWorkspaces = workspaceManager.n_workspaces; -+ -+ this._workspaces.forEach(workspace => { -+ workspace.disconnect(workspace._windowAddedId); -+ workspace.disconnect(workspace._windowRemovedId); -+ }); -+ -+ this._workspaces = []; -+ -+ for (let w = 0; w < numWorkspaces; w++) { -+ let workspace = workspaceManager.get_workspace_by_index(w); -+ workspace._windowAddedId = workspace.connect('window-added', this._queueCheckWorkspaces.bind(this)); -+ workspace._windowRemovedId = workspace.connect('window-removed', this._windowRemoved.bind(this)); -+ this._workspaces[w] = workspace; -+ } -+ this._queueCheckWorkspaces(); -+ return false; -+ } -+ - _nWorkspacesChanged() { - let workspaceManager = global.workspace_manager; - let oldNumWorkspaces = this._workspaces.length; -@@ -1641,6 +1672,14 @@ var WindowManager = class { - - this._switchInProgress = true; - -+ if (direction == Meta.MotionDirection.CONTEXT_SWITCH) { -+ Main.realmManager.animateSwitch(from, to, () => { -+ this._shellwm.completed_switch_workspace(); -+ this._switchInProgress = false; -+ }); -+ return; -+ } -+ - this._workspaceAnimation.animateSwitch(from, to, direction, () => { - this._shellwm.completed_switch_workspace(); - this._switchInProgress = false; -diff --git a/js/ui/windowMenu.js b/js/ui/windowMenu.js -index 27cecda..60dda02 100644 ---- a/js/ui/windowMenu.js -+++ b/js/ui/windowMenu.js -@@ -1,11 +1,12 @@ - // -*- mode: js; js-indent-level: 4; indent-tabs-mode: nil -* - /* exported WindowMenuManager */ - --const { GLib, Meta, St } = imports.gi; -+const { GLib, Meta, Shell, St } = imports.gi; - - const BoxPointer = imports.ui.boxpointer; - const Main = imports.ui.main; - const PopupMenu = imports.ui.popupMenu; -+const RealmWindowMenu = imports.ui.realms.realmWindowMenu; - - var WindowMenu = class extends PopupMenu.PopupMenu { - constructor(window, sourceActor) { -@@ -24,6 +25,23 @@ var WindowMenu = class extends PopupMenu.PopupMenu { - - let item; - -+ let s = RealmWindowMenu.windowMenuDebugString(window); -+ -+ this.addMenuItem(new PopupMenu.PopupSeparatorMenuItem(s)); -+ -+ item = RealmWindowMenu.enableFrameItem(window); -+ if (item) { -+ this.addMenuItem(item); -+ } -+ -+ if (!window.is_on_all_workspaces()) { -+ let realmSubmenu = RealmWindowMenu.realmWindowMenu(window); -+ if (realmSubmenu) { -+ this.addMenuItem(realmSubmenu); -+ } -+ } -+ this.addMenuItem(new PopupMenu.PopupSeparatorMenuItem()); -+ - item = this.addAction(_('Hide'), () => { - window.minimize(); - }); -diff --git a/js/ui/workspacesView.js b/js/ui/workspacesView.js -index 3761881..b662d05 100644 ---- a/js/ui/workspacesView.js -+++ b/js/ui/workspacesView.js -@@ -122,6 +122,10 @@ class WorkspacesView extends WorkspacesViewBase { - this._updateWorkspacesId = - workspaceManager.connect('notify::n-workspaces', - this._updateWorkspaces.bind(this)); -+ this._contextSwitchedId = -+ workspaceManager.connect('context-switched', -+ this._refreshWorkspaces.bind(this)); -+ - this._reorderWorkspacesId = - workspaceManager.connect('workspaces-reordered', () => { - this._workspaces.sort((a, b) => { -@@ -450,6 +454,13 @@ class WorkspacesView extends WorkspacesViewBase { - } - } - -+ _refreshWorkspaces() { -+ for (let ws = this._workspaces.pop(); ws; ws = this._workspaces.pop()) { -+ ws.destroy(); -+ } -+ this._updateWorkspaces(); -+ } -+ - _updateWorkspaces() { - let workspaceManager = global.workspace_manager; - let newNumWorkspaces = workspaceManager.n_workspaces; -@@ -499,6 +510,7 @@ class WorkspacesView extends WorkspacesViewBase { - global.window_manager.disconnect(this._switchWorkspaceNotifyId); - let workspaceManager = global.workspace_manager; - workspaceManager.disconnect(this._updateWorkspacesId); -+ workspaceManager.disconnect(this._contextSwitchedId); - workspaceManager.disconnect(this._reorderWorkspacesId); - } - -diff --git a/src/gnome-shell-plugin.c b/src/gnome-shell-plugin.c -index 5364f04..c41e405 100644 ---- a/src/gnome-shell-plugin.c -+++ b/src/gnome-shell-plugin.c -@@ -46,6 +46,7 @@ - #include "shell-global-private.h" - #include "shell-perf-log.h" - #include "shell-wm-private.h" -+#include "shell-realms.h" - - #define GNOME_TYPE_SHELL_PLUGIN (gnome_shell_plugin_get_type ()) - G_DECLARE_FINAL_TYPE (GnomeShellPlugin, gnome_shell_plugin, -@@ -214,6 +215,20 @@ gnome_shell_plugin_destroy (MetaPlugin *plugin, - actor); - } - -+static void -+ensure_switched_context_current(guint ctx_id) { -+ ShellRealms *realms = shell_realms_get_default(); -+ ShellRealmItem *realm = shell_realms_realm_by_context_id(realms, ctx_id); -+ if (!realm) { -+ g_warning ("No realm found for ctx_id = %d in context switch", ctx_id); -+ return; -+ } -+ -+ if (!shell_realm_item_is_current(realm)) { -+ shell_realm_item_set_current(realm); -+ } -+} -+ - static void - gnome_shell_plugin_switch_workspace (MetaPlugin *plugin, - gint from, -@@ -221,6 +236,10 @@ gnome_shell_plugin_switch_workspace (MetaPlugin *plugin, - MetaMotionDirection direction) - { - _shell_wm_switch_workspace (get_shell_wm(), from, to, direction); -+ -+ if (direction == META_MOTION_CONTEXT_SWITCH) { -+ ensure_switched_context_current((guint) (to >> 16)); -+ } - } - - static void -diff --git a/src/meson.build b/src/meson.build -index 5fa9fdf..26bd9cb 100644 ---- a/src/meson.build -+++ b/src/meson.build -@@ -125,6 +125,7 @@ libshell_private_headers = [ - 'shell-app-cache-private.h', - 'shell-app-system-private.h', - 'shell-global-private.h', -+ 'shell-realms-private.h', - 'shell-window-tracker-private.h', - 'shell-wm-private.h' - ] -@@ -147,6 +148,14 @@ libshell_sources = [ - 'shell-perf-log.c', - 'shell-polkit-authentication-agent.c', - 'shell-polkit-authentication-agent.h', -+ 'shell-realm-item.c', -+ 'shell-realm-item.h', -+ 'shell-realms-window-frames.c', -+ 'shell-realms-window-frames.h', -+ 'shell-realms.c', -+ 'shell-realms.h', -+ 'shell-realm-tracker.c', -+ 'shell-realm-tracker.h', - 'shell-screenshot.c', - 'shell-secure-text-buffer.c', - 'shell-secure-text-buffer.h', -diff --git a/src/shell-app-cache.c b/src/shell-app-cache.c -index 44fc8b0..092e83f 100644 ---- a/src/shell-app-cache.c -+++ b/src/shell-app-cache.c -@@ -23,7 +23,7 @@ - * Shell is running. - */ - --#define DEFAULT_TIMEOUT_SECONDS 5 -+#define DEFAULT_TIMEOUT_SECONDS 2 - - struct _ShellAppCache - { -diff --git a/src/shell-app-system.c b/src/shell-app-system.c -index 828fa72..033e731 100644 ---- a/src/shell-app-system.c -+++ b/src/shell-app-system.c -@@ -12,6 +12,7 @@ - #include "shell-app-cache-private.h" - #include "shell-app-private.h" - #include "shell-window-tracker-private.h" -+#include "shell-realms.h" - #include "shell-app-system-private.h" - #include "shell-global.h" - #include "shell-util.h" -@@ -91,6 +92,110 @@ static void shell_app_system_class_init(ShellAppSystemClass *klass) - G_TYPE_NONE, 0); - } - -+/* -+ * Applications belonging to realms have a prefix added to the filenames -+ * of their desktop files indicating the name of the realm. For example -+ * in a realm called 'main' the desktop file 'org.gnome.Terminal.desktop' -+ * is renamed to 'realm-main.org.gnome.Terminal.desktop'. -+ * -+ * This has the effect of creating a separate application ID for instances -+ * of the same application in multiple realms. -+ */ -+static const char * -+realm_name_from_application_id (const char *id) -+{ -+ gchar **split = NULL; -+ const char *result = NULL; -+ -+ if (!g_str_has_prefix (id, "realm-")) { -+ return NULL; -+ } -+ -+ split = g_strsplit(id, ".", 2); -+ -+ if (split[0]) { -+ g_assert_true (g_str_has_prefix (split[0], "realm-")); -+ result = g_strdup(split[0] + 6); -+ } -+ -+ g_strfreev (split); -+ return result; -+} -+ -+ -+char * -+current_realm_name (gboolean name_only) -+{ -+ gchar *link = NULL; -+ gchar *p = NULL; -+ gchar *realm = NULL; -+ -+ link = g_file_read_link ("/run/citadel/realms/current/current.realm", NULL); -+ -+ if (link) { -+ p = g_strrstr(link, "/realm-"); -+ if (p) { -+ /* skip slash character */ -+ p++; -+ if (name_only) { -+ /* skip 'realm-' */ -+ p += 6; -+ } -+ realm = g_strdup(p); -+ } -+ g_free (link); -+ } -+ -+ return realm; -+} -+ -+static gboolean -+is_current_realm_app (const char *id, const char *current_realm) -+{ -+ -+ return !g_str_has_prefix (id, "realm-") || g_str_has_prefix (id, current_realm); -+} -+ -+static void -+refresh_installed_apps (ShellAppSystem *self) -+{ -+ const GList *l; -+ GAppInfo *app; -+ const char *app_id; -+ char *current_realm; -+ ShellAppSystemPrivate *priv = self->priv; -+ -+ g_list_free_full (g_steal_pointer (&priv->installed_apps), g_object_unref); -+ -+ l = shell_app_cache_get_all (shell_app_cache_get_default ()); -+ -+ current_realm = current_realm_name (FALSE); -+ -+ while (l) { -+ app = l->data; -+ app_id = g_app_info_get_id (app); -+ -+ if (is_current_realm_app (app_id, current_realm)) { -+ priv->installed_apps = g_list_prepend (priv->installed_apps, g_object_ref (app)); -+ } -+ l = l->next; -+ } -+ priv->installed_apps = g_list_reverse (priv->installed_apps); -+ -+ g_free (current_realm); -+} -+ -+ -+static char * -+realm_wm_class (const char *wmclass, const char *realm_name) -+{ -+ if (realm_name) { -+ return g_strdup_printf ("realm-%s.%s", realm_name, wmclass); -+ } else { -+ return g_strdup (wmclass); -+ } -+} -+ - static void - scan_startup_wm_class_to_id (ShellAppSystem *self) - { -@@ -106,6 +211,7 @@ scan_startup_wm_class_to_id (ShellAppSystem *self) - { - GAppInfo *info = l->data; - const char *startup_wm_class, *id, *old_id; -+ char *realm_wmclass; - - id = g_app_info_get_id (info); - startup_wm_class = g_desktop_app_info_get_startup_wm_class (G_DESKTOP_APP_INFO (info)); -@@ -113,12 +219,17 @@ scan_startup_wm_class_to_id (ShellAppSystem *self) - if (startup_wm_class == NULL) - continue; - -+ realm_wmclass = realm_wm_class (startup_wm_class, realm_name_from_application_id (id)); -+ - /* In case multiple .desktop files set the same StartupWMClass, prefer - * the one where ID and StartupWMClass match */ -- old_id = g_hash_table_lookup (priv->startup_wm_class_to_id, startup_wm_class); -- if (old_id == NULL || strcmp (id, startup_wm_class) == 0) -+ old_id = g_hash_table_lookup (priv->startup_wm_class_to_id, realm_wmclass); -+ if (old_id == NULL || strcmp (id, startup_wm_class) == 0) { - g_hash_table_insert (priv->startup_wm_class_to_id, -- g_strdup (startup_wm_class), g_strdup (id)); -+ g_strdup (realm_wmclass), g_strdup (id)); -+ } -+ -+ g_free (realm_wmclass); - } - } - -@@ -347,15 +458,19 @@ shell_app_system_lookup_heuristic_basename (ShellAppSystem *system, - */ - ShellApp * - shell_app_system_lookup_desktop_wmclass (ShellAppSystem *system, -- const char *wmclass) -+ const char *wmclass, -+ const char *realm_name) - { - char *canonicalized; - char *desktop_file; -+ char *classname; - ShellApp *app; - - if (wmclass == NULL) - return NULL; - -+ classname = realm_wm_class (wmclass, realm_name); -+ - /* First try without changing the case (this handles - org.example.Foo.Bar.desktop applications) - -@@ -363,14 +478,16 @@ shell_app_system_lookup_desktop_wmclass (ShellAppSystem *system, - the WM_CLASS to Org.example.Foo.Bar, but it also - sets the instance part to org.example.Foo.Bar, so we're ok - */ -- desktop_file = g_strconcat (wmclass, ".desktop", NULL); -+ desktop_file = g_strconcat (classname, ".desktop", NULL); - app = shell_app_system_lookup_heuristic_basename (system, desktop_file); - g_free (desktop_file); - -- if (app) -+ if (app) { -+ g_free (classname); - return app; -+ } - -- canonicalized = g_ascii_strdown (wmclass, -1); -+ canonicalized = g_ascii_strdown (classname, -1); - - /* This handles "Fedora Eclipse", probably others. - * Note g_strdelimit is modify-in-place. */ -@@ -382,6 +499,7 @@ shell_app_system_lookup_desktop_wmclass (ShellAppSystem *system, - - g_free (canonicalized); - g_free (desktop_file); -+ g_free (classname); - - return app; - } -@@ -398,14 +516,20 @@ shell_app_system_lookup_desktop_wmclass (ShellAppSystem *system, - */ - ShellApp * - shell_app_system_lookup_startup_wmclass (ShellAppSystem *system, -- const char *wmclass) -+ const char *wmclass, -+ const char *realm_name) - { - const char *id; -+ char *classname; - - if (wmclass == NULL) - return NULL; - -- id = g_hash_table_lookup (system->priv->startup_wm_class_to_id, wmclass); -+ classname = realm_wm_class (wmclass, realm_name); -+ -+ id = g_hash_table_lookup (system->priv->startup_wm_class_to_id, classname); -+ g_free (classname); -+ - if (id == NULL) - return NULL; - -@@ -435,6 +559,29 @@ _shell_app_system_notify_app_state_changed (ShellAppSystem *self, - g_signal_emit (self, signals[APP_STATE_CHANGED], 0, app); - } - -+static gboolean -+is_current_realm_context_app(ShellApp *app) -+{ -+ ShellRealms *realms = shell_realms_get_default(); -+ ShellRealmItem *item = shell_realms_current_realm (realms); -+ guint id = (item) ? shell_realm_item_get_context_id (item) : 0; -+ -+ GSList *iter = shell_app_get_windows (app); -+ -+ while (iter) { -+ MetaWindow *window = iter->data; -+ if (meta_window_is_on_all_workspaces (window)) { -+ return true; -+ } -+ MetaWorkspace *workspace = meta_window_get_workspace (window); -+ if (meta_workspace_get_context_id (workspace) == id) { -+ return true; -+ } -+ iter = iter->next; -+ } -+ return false; -+} -+ - /** - * shell_app_system_get_running: - * @self: A #ShellAppSystem -@@ -458,7 +605,9 @@ shell_app_system_get_running (ShellAppSystem *self) - { - ShellApp *app = key; - -- ret = g_slist_prepend (ret, app); -+ if (is_current_realm_context_app (app)) { -+ ret = g_slist_prepend (ret, app); -+ } - } - - ret = g_slist_sort (ret, (GCompareFunc)shell_app_compare); -@@ -482,12 +631,16 @@ shell_app_system_search (const char *search_string) - { - char ***results = g_desktop_app_info_search (search_string); - char ***groups, **ids; -+ char *current_realm; -+ -+ current_realm = current_realm_name (FALSE); - - for (groups = results; *groups; groups++) - for (ids = *groups; *ids; ids++) -- if (!g_utf8_validate (*ids, -1, NULL)) -+ if (!g_utf8_validate (*ids, -1, NULL) || !is_current_realm_app (*ids, current_realm)) - **ids = '\0'; - -+ g_free (current_realm); - return results; - } - -@@ -504,5 +657,7 @@ shell_app_system_search (const char *search_string) - GList * - shell_app_system_get_installed (ShellAppSystem *self) - { -- return shell_app_cache_get_all (shell_app_cache_get_default ()); -+ ShellAppSystemPrivate *priv = self->priv; -+ refresh_installed_apps (self); -+ return priv->installed_apps; - } -diff --git a/src/shell-app-system.h b/src/shell-app-system.h -index 8719dbc..6a0203e 100644 ---- a/src/shell-app-system.h -+++ b/src/shell-app-system.h -@@ -20,9 +20,11 @@ ShellApp *shell_app_system_lookup_heuristic_basename (ShellAppSystem * - const char *id); - - ShellApp *shell_app_system_lookup_startup_wmclass (ShellAppSystem *system, -- const char *wmclass); -+ const char *wmclass, -+ const char *realm_name); - ShellApp *shell_app_system_lookup_desktop_wmclass (ShellAppSystem *system, -- const char *wmclass); -+ const char *wmclass, -+ const char *realm_name); - - GSList *shell_app_system_get_running (ShellAppSystem *self); - char ***shell_app_system_search (const char *search_string); -diff --git a/src/shell-global.c b/src/shell-global.c -index e8705f5..c97510f 100644 ---- a/src/shell-global.c -+++ b/src/shell-global.c -@@ -44,6 +44,7 @@ - #include "shell-util.h" - #include "st.h" - #include "switcheroo-control.h" -+#include "shell-realm-tracker.h" - - static ShellGlobal *the_object = NULL; - -@@ -1068,6 +1069,8 @@ _shell_global_set_plugin (ShellGlobal *global, - global->focus_manager = st_focus_manager_get_for_stage (global->stage); - - update_scaling_factor (global, settings); -+ -+ shell_realm_tracker_start (); - } - - GjsContext * -diff --git a/src/shell-realm-item.c b/src/shell-realm-item.c -new file mode 100644 -index 0000000..ddb662c ---- /dev/null -+++ b/src/shell-realm-item.c -@@ -0,0 +1,396 @@ -+#include "shell-global.h" -+#include "shell-realm-item.h" -+#include "shell-realm-tracker.h" -+#include -+#include -+ -+struct _ShellRealmItem { -+ GObject parent; -+ char *realm_name; -+ char *description; -+ char *namespace; -+ MetaWorkspaceContext *context; -+ guint8 status; -+ gboolean tagged; -+ gboolean disposed; -+}; -+ -+G_DEFINE_TYPE (ShellRealmItem, shell_realm_item, G_TYPE_OBJECT); -+ -+ -+enum { -+ PROP_0, -+ PROP_ITEM_REALM_NAME, -+ PROP_ITEM_DESCRIPTION, -+ PROP_ITEM_NAMESPACE -+}; -+ -+#define REALM_STATUS_RUNNING 1 -+#define REALM_STATUS_CURRENT 2 -+#define REALM_STATUS_SYSTEM 4 -+ -+static void -+shell_realm_item_init(ShellRealmItem *item) -+{ -+} -+ -+ShellRealmItem * -+shell_realm_item_new (const char *realm_name, const char *description, const char *namespace, guint8 status) -+{ -+ ShellRealmItem *item = g_object_new (SHELL_TYPE_REALM_ITEM, NULL); -+ item->realm_name = g_strdup (realm_name); -+ item->description = g_strdup (description); -+ item->namespace = g_strdup (namespace); -+ item->status = status; -+ item->context = NULL; -+ item->tagged = FALSE; -+ item->disposed = FALSE; -+ -+ return item; -+} -+ -+void -+shell_realm_item_acquire_context (ShellRealmItem *item) -+{ -+ if (item->context || item->disposed || shell_realm_item_is_system (item)) { -+ return; -+ } -+ -+ if (!item->namespace || !shell_realm_item_is_running (item)) { -+ g_warning ("ShellRealmItem: Cannot acquire workspace context for realm '%s' because not running or no namespace", item->realm_name); -+ return; -+ } -+ -+ MetaDisplay *display = shell_global_get_display (shell_global_get()); -+ MetaWorkspaceManager *workspace_manager = meta_display_get_workspace_manager (display); -+ item->context = meta_workspace_manager_context_for_namespace (workspace_manager, item->namespace); -+} -+ -+/** -+ * shell_realm_item_get_realm_name: -+ * @item: A #ShellRealmItem instance -+ * -+ * Returns: The name of the realm for this #ShellRealmItem -+ */ -+const char * -+shell_realm_item_get_realm_name (ShellRealmItem *item) -+{ -+ return item->realm_name; -+} -+ -+/** -+ * shell_realm_item_get_description: -+ * @item: A #ShellRealmItem instance -+ * -+ * Returns: The description field for this realm or an empty string if no description is set -+ */ -+const char * -+shell_realm_item_get_description (ShellRealmItem *item) -+{ -+ if (item->description) -+ return item->description; -+ else -+ return ""; -+} -+ -+ -+/** -+ * shell_realm_item_get_namespace: -+ * @item: A #ShellRealmItem instance -+ * -+ * Returns: The namespace field for this realm or an empty string if no namespace is set -+ */ -+const char * -+shell_realm_item_get_namespace (ShellRealmItem *item) -+{ -+ if (item->namespace) -+ return item->namespace; -+ else -+ return ""; -+} -+ -+/** -+ * shell_realm_item_get_context_id: -+ * @item: A #ShellRealmItem instance -+ * -+ * Returns: The context id for the #MetaWorkspaceContext of this realm or 0 if -+ * no context exists. -+ */ -+guint -+shell_realm_item_get_context_id (ShellRealmItem *item) -+{ -+ if (shell_realm_item_is_running (item) && !item->context) { -+ shell_realm_item_acquire_context (item); -+ } -+ -+ if (item->context) { -+ return meta_workspace_context_id (item->context); -+ } else { -+ return 0; -+ } -+} -+ -+/** -+ * shell_realm_item_get_active_workspace: -+ * @item: A #ShellRealmItem -+ * -+ * Returns: (transfer none): The current workspace for the context -+ * belonging to this item. -+ */ -+MetaWorkspace * -+shell_realm_item_get_active_workspace (ShellRealmItem *item) -+{ -+ if (shell_realm_item_is_running (item) && !item->context) { -+ shell_realm_item_acquire_context (item); -+ } -+ -+ if (item->context) { -+ return meta_workspace_context_get_active_workspace (item->context); -+ } else { -+ return NULL; -+ } -+} -+ -+/** -+ * shell_realm_item_activate_context: -+ * @item: A #ShellRealmItem instance for a running realm -+ * -+ * If a #MetaWorkspaceContext is associated with this realm -+ * set it as the active workspace context. -+ */ -+void -+shell_realm_item_activate_context (ShellRealmItem *item) -+{ -+ shell_realm_item_acquire_context (item); -+ -+ if (item->context) { -+ meta_workspace_context_activate (item->context); -+ } -+} -+ -+/** -+ * shell_realm_item_set_current: -+ * @item: A #ShellRealmItem instance for a running realm -+ * -+ * Sends a DBUS request to change the current realm to this realm. This does not immediately -+ * influence any local state in GNOME shell. Once the realms daemon has changed the current realm -+ * it will emit a signal and the processing of that signal will update the local state. -+ */ -+void -+shell_realm_item_set_current (ShellRealmItem *item) { -+ ShellRealmTracker *tracker = shell_realm_tracker_get_default(); -+ if (item && item->realm_name) { -+ shell_realm_tracker_call_set_current (tracker, item->realm_name); -+ } -+} -+ -+/** -+ * shell_realm_item_move_window_to_context: -+ * @item: A #ShellRealmItem instance for a running realm -+ * @window: A #MetaWindow for some window -+ * -+ * Move window to the currently active workspace in the #MetaWorkspaceContext for -+ * this realm. -+ */ -+void -+shell_realm_item_move_window_to_context (ShellRealmItem *item, MetaWindow *window) -+{ -+ shell_realm_item_acquire_context (item); -+ -+ if (item->context) { -+ meta_workspace_context_move_window_to_context (item->context, window); -+ } else { -+ g_warning ("ShellRealmItem: Attempted to move window to realm '%s' which has no workspace context", item->realm_name); -+ } -+} -+ -+static gboolean -+is_flag_set(guint8 status, guchar flag) -+{ -+ return ((status & flag) != 0) ? TRUE : FALSE; -+} -+ -+static gboolean -+has_status_flag (ShellRealmItem *item, guchar flag) -+{ -+ return is_flag_set (item->status, flag); -+} -+ -+static void -+set_status_flag (ShellRealmItem *item, guint8 flag, gboolean value) -+{ -+ if (value) { -+ item->status |= flag; -+ } else { -+ item->status &= ~flag; -+ } -+} -+ -+/** -+ * shell_realm_item_is_current: -+ * @item: A #ShellRealmItem instance -+ * -+ * Returns: %TRUE if this #ShellRealmItem is the current realm -+ */ -+gboolean -+shell_realm_item_is_current (ShellRealmItem *item) -+{ -+ return has_status_flag (item, REALM_STATUS_CURRENT); -+} -+ -+/** -+ * shell_realm_item_is_running: -+ * @item: A #ShellRealmItem instance -+ * -+ * Returns: %TRUE if this #ShellRealmItem is running -+ */ -+gboolean -+shell_realm_item_is_running (ShellRealmItem *item) -+{ -+ return has_status_flag (item, REALM_STATUS_RUNNING); -+} -+ -+/** -+ * shell_realm_item_is_system: -+ * @item: A #ShellRealmItem instance -+ * -+ * Returns: %TRUE if this #ShellRealmItem is a system realm -+ */ -+gboolean -+shell_realm_item_is_system (ShellRealmItem *item) -+{ -+ return has_status_flag (item, REALM_STATUS_SYSTEM); -+} -+ -+ -+void shell_realm_item_set_current_flag (ShellRealmItem *item, gboolean value) -+{ -+ set_status_flag (item, REALM_STATUS_CURRENT, value); -+} -+ -+void shell_realm_item_set_running_flag (ShellRealmItem *item, gboolean value) -+{ -+ set_status_flag (item, REALM_STATUS_RUNNING, value); -+ -+ if (!value && item->context) { -+ meta_workspace_context_remove (item->context); -+ item->context = NULL; -+ } -+} -+ -+ -+void shell_realm_item_update (ShellRealmItem *item, const char *realm_name, const char *namespace, guint8 status) -+{ -+ if (g_strcmp0 (item->realm_name, realm_name)) { -+ g_message ("ShellRealmItem: Realm name changed from %s to %s", item->realm_name, realm_name); -+ g_free (item->realm_name); -+ item->realm_name = g_strdup (realm_name); -+ } -+ -+ if (g_strcmp0 (item->namespace, namespace)) { -+ g_free(item->namespace); -+ item->namespace = g_strdup (namespace); -+ } -+ -+ if (item->status != status) { -+ gboolean was_running = has_status_flag (item, REALM_STATUS_RUNNING); -+ gboolean is_running = is_flag_set (status, REALM_STATUS_RUNNING); -+ gboolean stopped = was_running && !is_running; -+ -+ item->status = status; -+ -+ if (stopped) { -+ meta_workspace_context_remove (item->context); -+ item->context = NULL; -+ } -+ } -+} -+ -+void -+shell_realm_item_set_tagged (ShellRealmItem *item, gboolean is_tagged) -+{ -+ item->tagged = is_tagged; -+} -+ -+gboolean -+shell_realm_item_is_tagged (ShellRealmItem *item) -+{ -+ return item->tagged; -+} -+ -+static void shell_realm_item_get_property (GObject *gobject, -+ guint prop_id, -+ GValue *value, -+ GParamSpec *pspec) -+{ -+ ShellRealmItem *item = SHELL_REALM_ITEM (gobject); -+ switch (prop_id) { -+ case PROP_ITEM_REALM_NAME: -+ g_value_set_string (value, shell_realm_item_get_realm_name (item)); -+ break; -+ case PROP_ITEM_DESCRIPTION: -+ g_value_set_string (value, shell_realm_item_get_description (item)); -+ break; -+ case PROP_ITEM_NAMESPACE: -+ g_value_set_string (value, shell_realm_item_get_namespace (item)); -+ break; -+ default: -+ G_OBJECT_WARN_INVALID_PROPERTY_ID (gobject, prop_id, pspec); -+ break; -+ } -+} -+ -+static void shell_realm_item_dispose (GObject *object) -+{ -+ ShellRealmItem *item = SHELL_REALM_ITEM (object); -+ if (item->context) { -+ meta_workspace_context_remove (item->context); -+ item->context = NULL; -+ } -+ item->disposed = TRUE; -+ G_OBJECT_CLASS(shell_realm_item_parent_class)->dispose (object); -+} -+ -+static void -+shell_realm_item_finalize (GObject *object) -+{ -+ ShellRealmItem *item = SHELL_REALM_ITEM (object); -+ g_free (item->realm_name); -+ g_free (item->description); -+ g_free (item->namespace); -+ -+ G_OBJECT_CLASS(shell_realm_item_parent_class)->finalize (object); -+} -+ -+static void -+shell_realm_item_class_init (ShellRealmItemClass *klass) -+{ -+ GObjectClass *gobject_class = G_OBJECT_CLASS (klass); -+ -+ gobject_class->get_property = shell_realm_item_get_property; -+ gobject_class->dispose = shell_realm_item_dispose; -+ gobject_class->finalize = shell_realm_item_finalize; -+ -+ g_object_class_install_property (gobject_class, -+ PROP_ITEM_NAMESPACE, -+ g_param_spec_string ("namespace", -+ "Context Namespace", -+ "PID namespace of context", -+ NULL, -+ G_PARAM_READABLE | G_PARAM_STATIC_STRINGS)); -+ g_object_class_install_property (gobject_class, -+ PROP_ITEM_REALM_NAME, -+ g_param_spec_string ("realm-name", -+ "Realm Name", -+ "Name of realm associated with this context", -+ NULL, -+ G_PARAM_READABLE | G_PARAM_STATIC_STRINGS)); -+ -+ g_object_class_install_property (gobject_class, -+ PROP_ITEM_DESCRIPTION, -+ g_param_spec_string ("description", -+ "Realm Description", -+ "Optional description of realm", -+ NULL, -+ G_PARAM_READABLE | G_PARAM_STATIC_STRINGS)); -+} -\ No newline at end of file -diff --git a/src/shell-realm-item.h b/src/shell-realm-item.h -new file mode 100644 -index 0000000..8e9d8ec ---- /dev/null -+++ b/src/shell-realm-item.h -@@ -0,0 +1,35 @@ -+#ifndef __SHELL_REALM_ITEM_H__ -+#define __SHELL_REALM_ITEM_H__ -+ -+#include -+#include -+ -+#define SHELL_TYPE_REALM_ITEM (shell_realm_item_get_type()) -+G_DECLARE_FINAL_TYPE (ShellRealmItem, shell_realm_item, SHELL, REALM_ITEM, GObject) -+ -+ShellRealmItem *shell_realm_item_new (const char *realm_name, const char *description, const char *namespace, guint8 status); -+ -+const char *shell_realm_item_get_realm_name (ShellRealmItem *item); -+const char *shell_realm_item_get_description (ShellRealmItem *item); -+const char *shell_realm_item_get_namespace (ShellRealmItem *item); -+guint shell_realm_item_get_context_id (ShellRealmItem *item); -+MetaWorkspace *shell_realm_item_get_active_workspace (ShellRealmItem *item); -+ -+void shell_realm_item_update (ShellRealmItem *item, const char *realm_name, const char *namespace, guint8 status); -+ -+ -+void shell_realm_item_set_current_flag (ShellRealmItem *item, gboolean value); -+void shell_realm_item_set_running_flag (ShellRealmItem *item, gboolean value); -+ -+void shell_realm_item_activate_context (ShellRealmItem *item); -+void shell_realm_item_set_current (ShellRealmItem *item); -+void shell_realm_item_move_window_to_context (ShellRealmItem *item, MetaWindow *window); -+gboolean shell_realm_item_is_current(ShellRealmItem *item); -+gboolean shell_realm_item_is_running(ShellRealmItem *item); -+gboolean shell_realm_item_is_system(ShellRealmItem *item); -+ -+void shell_realm_item_set_tagged (ShellRealmItem *item, gboolean is_tagged); -+gboolean shell_realm_item_is_tagged (ShellRealmItem *item); -+ -+void shell_realm_item_acquire_context (ShellRealmItem *item); -+#endif //__SHELL_REALM_ITEM_H__ -diff --git a/src/shell-realm-tracker.c b/src/shell-realm-tracker.c -new file mode 100644 -index 0000000..2b3c10d ---- /dev/null -+++ b/src/shell-realm-tracker.c -@@ -0,0 +1,310 @@ -+#include "shell-realm-tracker.h" -+#include "shell-realms-private.h" -+ -+#define NUM_BUS_SIGNAL_IDS 5 -+ -+#define REALMS_BUS_NAME "com.subgraph.realms" -+#define REALMS_OBJECT_PATH "/com/subgraph/realms" -+#define REALMS_MANAGER_INTERFACE "com.subgraph.realms.Manager" -+ -+struct _ShellRealmTracker { -+ GObject parent; -+ GDBusConnection *dbus; -+ guint realms_watch_id; -+ guint bus_signal_ids[NUM_BUS_SIGNAL_IDS]; -+ gboolean destroy_in_progress; -+}; -+ -+G_DEFINE_TYPE (ShellRealmTracker, shell_realm_tracker, G_TYPE_OBJECT); -+ -+static void -+shell_realm_tracker_init (ShellRealmTracker *tracker) -+{ -+ tracker->dbus = NULL; -+ tracker->realms_watch_id = 0; -+ tracker->destroy_in_progress = FALSE; -+} -+ -+static void -+shell_realm_tracker_class_init (ShellRealmTrackerClass *klass) -+{ -+} -+ -+static void -+on_realm_bus_signal(GDBusConnection *connection, -+ const gchar *sender_name, -+ const gchar *object_path, -+ const gchar *interface_name, -+ const gchar *signal_name, -+ GVariant *parameters, -+ gpointer user_data) -+{ -+ ShellRealms *realms = shell_realms_get_default(); -+ -+ const gchar *realm_name = NULL; -+ const gchar *description = NULL; -+ const gchar *namespace = NULL; -+ guint8 status = 0; -+ -+ if (g_str_equal (signal_name, "RealmStarted")) { -+ -+ g_variant_get (parameters, "(&s&sy)", &realm_name, &namespace, &status); -+ shell_realms_on_realm_started (realms, realm_name, namespace, status); -+ -+ } else if (g_str_equal (signal_name, "RealmStopped")) { -+ -+ g_variant_get (parameters, "(&sy)", &realm_name, &status); -+ shell_realms_on_realm_stopped (realms, realm_name); -+ -+ } else if (g_str_equal (signal_name, "RealmRemoved")) { -+ -+ g_variant_get (parameters, "(&s)", &realm_name); -+ shell_realms_on_realm_removed (realms, realm_name); -+ -+ } else if (g_str_equal (signal_name, "RealmCurrent")) { -+ -+ g_variant_get (parameters, "(&sy)", &realm_name, &status); -+ shell_realms_on_realm_current (realms, realm_name); -+ -+ } else if (g_str_equal (signal_name, "RealmNew")) { -+ -+ g_variant_get (parameters, "(&s&sy)", &realm_name, &description, status); -+ shell_realms_on_realm_new (realms, realm_name, description, status); -+ -+ } else { -+ g_warning("Unexpected signal name '%s' received from realms manager DBUS", signal_name); -+ } -+} -+ -+static void -+realm_state_process_elements (ShellRealmTracker *self, GVariant *response) -+{ -+ -+ GVariantIter *iter = NULL; -+ const gchar *name = NULL; -+ const gchar *description = NULL; -+ const gchar *namespace = NULL; -+ guchar status = 0; -+ -+ ShellRealms *realms = shell_realms_get_default(); -+ shell_realms_untag_all (realms); -+ -+ g_variant_get(response, "(a(ssssy))", &iter); -+ -+ // (name, desc, realmfs, namespace, status) -+ while (g_variant_iter_next(iter, "(&s&ss&sy)", &name, &description, NULL, &namespace, &status)) { -+ shell_realms_update_realm (realms, name, description, namespace, status); -+ } -+ -+ shell_realms_remove_untagged (realms); -+ g_variant_iter_free(iter); -+} -+ -+static void -+request_realm_state_finish(GObject *object, GAsyncResult *result, gpointer data) -+{ -+ ShellRealmTracker *self = data; -+ -+ GError *error = NULL; -+ -+ GVariant *response = g_dbus_connection_call_finish (G_DBUS_CONNECTION(object), result, &error); -+ -+ if (!response) { -+ if (!g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED)) { -+ g_warning("MetaRealmDbus: Error calling 'List' bus method: %s", error->message); -+ } -+ g_clear_error (&error); -+ return; -+ } -+ -+ if (self->destroy_in_progress) { -+ g_variant_unref (response); -+ return; -+ } -+ -+ realm_state_process_elements(self, response); -+ g_variant_unref(response); -+} -+ -+static void -+set_realm_current_finish (GObject *object, GAsyncResult *result, gpointer data) -+{ -+ GError *error = NULL; -+ GVariant *response = g_dbus_connection_call_finish (G_DBUS_CONNECTION(object), result, &error); -+ if (!response) { -+ if (!g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED)) { -+ g_warning("MetaRealmDbus: Error calling 'SetCurrent' bus method: %s", error->message); -+ } -+ g_clear_error (&error); -+ return; -+ } -+ g_variant_unref (response); -+} -+ -+static void -+call_dbus_method (ShellRealmTracker *self, const gchar *method, GVariant *parameters, GAsyncReadyCallback callback, gpointer user_data) -+{ -+ if (!self->dbus) { -+ g_warning("ShellRealmTracker: call_dbus_method(%s) called when no bus connection present", method); -+ return; -+ } -+ -+ g_dbus_connection_call(self->dbus, -+ REALMS_BUS_NAME, -+ REALMS_OBJECT_PATH, -+ REALMS_MANAGER_INTERFACE, -+ method, -+ parameters, -+ NULL, -+ G_DBUS_CALL_FLAGS_NO_AUTO_START, -+ -1, -+ NULL, -+ callback, -+ user_data); -+} -+ -+static void -+request_realm_state(ShellRealmTracker *self) -+{ -+ call_dbus_method (self, "List", NULL, request_realm_state_finish, self); -+} -+ -+void -+shell_realm_tracker_call_set_current (ShellRealmTracker *self, const char *realm_name) -+{ -+ call_dbus_method (self, "SetCurrent", g_variant_new("(s)", realm_name), set_realm_current_finish, NULL); -+} -+ -+ -+static void -+unsubscribe_signals (ShellRealmTracker *self) -+{ -+ for (int i = 0; i < NUM_BUS_SIGNAL_IDS; i++) { -+ if (self->bus_signal_ids[i]) { -+ if (self->dbus) { -+ g_dbus_connection_signal_unsubscribe(self->dbus, self->bus_signal_ids[i]); -+ } -+ self->bus_signal_ids[i] = 0; -+ } -+ } -+} -+ -+static guint -+bus_signal_subscribe (ShellRealmTracker *self, const gchar *signal_name) -+{ -+ g_assert(self->dbus); -+ -+ return g_dbus_connection_signal_subscribe(self->dbus, -+ REALMS_BUS_NAME, -+ REALMS_MANAGER_INTERFACE, -+ signal_name, -+ REALMS_OBJECT_PATH, -+ NULL, -+ G_DBUS_SIGNAL_FLAGS_NONE, -+ on_realm_bus_signal, -+ self, -+ NULL); -+} -+ -+static void -+subscribe_bus_signals (ShellRealmTracker *self) -+{ -+ if (self->dbus) { -+ int idx = 0; -+ self->bus_signal_ids[idx++] = bus_signal_subscribe(self, "RealmStarted"); -+ self->bus_signal_ids[idx++] = bus_signal_subscribe(self, "RealmStopped"); -+ self->bus_signal_ids[idx++] = bus_signal_subscribe(self, "RealmCurrent"); -+ self->bus_signal_ids[idx++] = bus_signal_subscribe(self, "RealmNew"); -+ self->bus_signal_ids[idx++] = bus_signal_subscribe(self, "RealmRemoved"); -+ g_assert(idx == NUM_BUS_SIGNAL_IDS); -+ } -+} -+ -+static void -+on_realm_manager_appeared (GDBusConnection *connection, const gchar *name, const gchar *name_owner, gpointer user_data) -+{ -+ ShellRealmTracker *self = user_data; -+ -+ // Avoid processing spurious events while destroying 'self' -+ if (self->destroy_in_progress) { -+ return; -+ } -+ -+ if (!self->dbus) { -+ self->dbus = g_object_ref(connection); -+ subscribe_bus_signals (self); -+ } else { -+ g_warning("Realm tracker already has a connection in on_realm_manager_appeared()"); -+ } -+ -+ request_realm_state (self); -+} -+ -+ -+static void -+on_realm_manager_vanished (GDBusConnection *connection, const gchar *name, gpointer user_data) -+{ -+ ShellRealmTracker *self = user_data; -+ -+ // Avoid processing spurious events while destroying 'self' -+ if (self->destroy_in_progress) { -+ return; -+ } -+ -+ if (!connection) { -+ g_clear_object (&self->dbus); -+ } -+ -+ unsubscribe_signals(self); -+} -+ -+/** -+ * shell_realm_tracker_get_default: -+ * -+ * Return Value: (transfer none): The global #ShellRealmTracker singleton -+ */ -+ShellRealmTracker * -+shell_realm_tracker_get_default(void) -+{ -+ static ShellRealmTracker *instance; -+ if (instance == NULL) { -+ instance = g_object_new (SHELL_TYPE_REALM_TRACKER, NULL); -+ } -+ return instance; -+} -+ -+void shell_realm_tracker_start () -+{ -+ ShellRealmTracker *tracker = shell_realm_tracker_get_default(); -+ -+ if (tracker->realms_watch_id) { -+ g_warning ("ShellRealmTracker: shell_realm_tracker_start() called when already started"); -+ return; -+ } -+ -+ tracker->realms_watch_id = g_bus_watch_name(G_BUS_TYPE_SYSTEM, -+ REALMS_BUS_NAME, -+ G_BUS_NAME_WATCHER_FLAGS_NONE, -+ on_realm_manager_appeared, -+ on_realm_manager_vanished, -+ tracker, -+ g_free); -+ -+} -+ -+void -+shell_realm_tracker_destroy(ShellRealmTracker *self) -+{ -+ if (self->dbus) { -+ unsubscribe_signals (self); -+ g_clear_object (&self->dbus); -+ } -+ -+ // event handlers check this and will bail early in case there are -+ // any in queue. see docs for g_bus_unwatch_name() -+ self->destroy_in_progress = TRUE; -+ -+ // frees 'self' in destroy notifier -+ g_bus_unwatch_name(self->realms_watch_id); -+ -+} -diff --git a/src/shell-realm-tracker.h b/src/shell-realm-tracker.h -new file mode 100644 -index 0000000..a979f95 ---- /dev/null -+++ b/src/shell-realm-tracker.h -@@ -0,0 +1,14 @@ -+#ifndef __SHELL_REALM_TRACKER_H__ -+#define __SHELL_REALM_TRACKER_H__ -+ -+#include -+ -+#define SHELL_TYPE_REALM_TRACKER (shell_realm_tracker_get_type ()) -+G_DECLARE_FINAL_TYPE (ShellRealmTracker, shell_realm_tracker, -+ SHELL, REALM_TRACKER, GObject) -+ -+ShellRealmTracker *shell_realm_tracker_get_default(void); -+void shell_realm_tracker_call_set_current (ShellRealmTracker *self, const char *realm_name); -+void shell_realm_tracker_start (); -+ -+#endif /* __SHELL_REALM_TRACKER_H__ */ -diff --git a/src/shell-realms-private.h b/src/shell-realms-private.h -new file mode 100644 -index 0000000..571f110 ---- /dev/null -+++ b/src/shell-realms-private.h -@@ -0,0 +1,21 @@ -+#ifndef __SHELL_REALMS_PRIVATE_H__ -+#define __SHELL_REALMS_PRIVATE_H__ -+#include -+#include "shell-realms.h" -+#include "shell-realms-window-frames.h" -+ -+ -+void shell_realms_untag_all (ShellRealms *realms); -+void shell_realms_remove_untagged (ShellRealms *realms); -+void shell_realms_update_realm (ShellRealms *realms, -+ const char *realm_name, -+ const char *description, -+ const char *namespace, -+ guint8 status); -+ -+void shell_realms_on_realm_started (ShellRealms *realms, const gchar *realm_name, const gchar *namespace, guint8 status); -+void shell_realms_on_realm_current (ShellRealms *realms, const gchar *realm_name); -+void shell_realms_on_realm_stopped (ShellRealms *realms, const gchar *realm_name); -+void shell_realms_on_realm_removed (ShellRealms *realms, const gchar *realm_name); -+void shell_realms_on_realm_new (ShellRealms *realms, const gchar *realm_name, const gchar *description, guint8 status); -+#endif //__SHELL_REALMS_PRIVATE_H__ -diff --git a/src/shell-realms-window-frames.c b/src/shell-realms-window-frames.c -new file mode 100644 -index 0000000..99a3aa7 ---- /dev/null -+++ b/src/shell-realms-window-frames.c -@@ -0,0 +1,386 @@ -+#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); -+ } -+} -diff --git a/src/shell-realms-window-frames.h b/src/shell-realms-window-frames.h -new file mode 100644 -index 0000000..0235473 ---- /dev/null -+++ b/src/shell-realms-window-frames.h -@@ -0,0 +1,17 @@ -+#ifndef __SHELL_REALMS_WINDOW_FRAMES_H__ -+#define __SHELL_REALMS_WINDOW_FRAMES_H__ -+ -+#include -+#include -+#include -+ -+#define SHELL_TYPE_REALMS_WINDOW_FRAMES (shell_realms_window_frames_get_type()) -+G_DECLARE_FINAL_TYPE (ShellRealmsWindowFrames, shell_realms_window_frames, SHELL, REALMS_WINDOW_FRAMES, GObject) -+ -+gboolean shell_realms_window_frames_has_frame (ShellRealmsWindowFrames *frames, MetaWindow *window); -+gboolean shell_realms_window_frames_is_frame_enabled (ShellRealmsWindowFrames *frames, MetaWindow *window); -+void shell_realms_window_frames_set_frame_enabled (ShellRealmsWindowFrames *frames, MetaWindow *window, gboolean enabled); -+ClutterColor *shell_realms_window_frames_color_for_window (ShellRealmsWindowFrames *frames, MetaWindow *window); -+const gchar *shell_realms_window_frames_label_for_window (ShellRealmsWindowFrames *frames, MetaWindow *window); -+ -+#endif // __SHELL_REALMS_WINDOW_FRAMES_H__ -diff --git a/src/shell-realms.c b/src/shell-realms.c -new file mode 100644 -index 0000000..01718b0 ---- /dev/null -+++ b/src/shell-realms.c -@@ -0,0 +1,460 @@ -+ -+#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; -+}; -+ -+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) -+{ -+ 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; -+} -+ -+/** -+ * 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) -+{ -+ const char *window_ns = meta_window_namespace (window); -+ -+ if (!window_ns) { -+ return NULL; -+ } -+ -+ for (GList *iter = realms->running_realms; iter; iter = iter->next) { -+ ShellRealmItem *item = iter->data; -+ if (g_strcmp0 (window_ns, shell_realm_item_get_namespace (item)) == 0) { -+ return item; -+ } -+ } -+ return NULL; -+} -+ -+/** -+ * 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) -+{ -+ MetaDisplay *display = shell_global_get_display (shell_global_get()); -+ MetaWorkspaceManager *workspace_manager = meta_display_get_workspace_manager (display); -+ -+ const char *mutter_ns = meta_workspace_manager_mutter_namespace (workspace_manager); -+ const char *window_ns = meta_window_namespace (window); -+ -+ return g_strcmp0 (mutter_ns, window_ns) == 0; -+} -+ -+/** -+ * 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; -+} -+ -+/* -+ * 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, -+ const char *namespace, -+ guint8 status) -+{ -+ 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; -+} -+ -+void -+shell_realms_update_realm (ShellRealms *realms, -+ const char *realm_name, -+ const char *description, -+ const char *namespace, -+ guint8 status) -+{ -+ ShellRealmItem *item = shell_realms_add_realm_item (realms, realm_name, description, namespace, status); -+ -+ // Ignore system realms -+ if (!item) { -+ return; -+ } -+ -+ 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); -+ shell_realm_item_acquire_context (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); -+ g_hash_table_iter_remove (&iter); -+ } -+ } -+} -+ -+// Signal handlers -+ -+void -+shell_realms_on_realm_started (ShellRealms *realms, const gchar *realm_name, const gchar *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); -+ shell_realm_item_acquire_context (item); -+ } -+ } -+} -+ -+void -+shell_realms_on_realm_current (ShellRealms *realms, const gchar *realm_name) -+{ -+ 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); -+ shell_realm_item_activate_context (item); -+ 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); -+ 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_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 (!g_hash_table_contains (realms->realms, realm_name)) { -+ (void) shell_realms_add_realm_item (realms, realm_name, description, NULL, 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) -+{ -+ 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->frames = g_object_new (SHELL_TYPE_REALMS_WINDOW_FRAMES, NULL); -+} -+ -+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)); -+} -\ No newline at end of file -diff --git a/src/shell-realms.h b/src/shell-realms.h -new file mode 100644 -index 0000000..198b510 ---- /dev/null -+++ b/src/shell-realms.h -@@ -0,0 +1,26 @@ -+#ifndef __SHELL_REALMS_H__ -+#define __SHELL_REALMS_H__ -+ -+#include -+#include -+#include "shell-realm-item.h" -+#include "shell-realms-window-frames.h" -+ -+#define SHELL_TYPE_REALMS (shell_realms_get_type()) -+G_DECLARE_FINAL_TYPE(ShellRealms, shell_realms, SHELL, REALMS, GObject) -+ -+ShellRealms *shell_realms_get_default(void); -+ -+ -+ShellRealmItem *shell_realms_current_realm (ShellRealms *realms); -+ShellRealmItem *shell_realms_realm_by_name(ShellRealms *realms, const gchar *realm_name); -+ShellRealmItem *shell_realms_realm_by_context_id (ShellRealms *realms, guint context_id); -+ShellRealmItem *shell_realms_realm_by_window (ShellRealms *realms, MetaWindow *window); -+gboolean shell_realms_is_citadel_window (ShellRealms *realms, MetaWindow *window); -+ -+ShellRealmsWindowFrames *shell_realms_window_frames (ShellRealms *realms); -+ -+GList *shell_realms_get_running_realms (ShellRealms *realms); -+GList *shell_realms_get_all_realms (ShellRealms *realms); -+ -+#endif //__SHELL_REALMS_H__ -diff --git a/src/shell-window-tracker.c b/src/shell-window-tracker.c -index 5c9d2ec..d75db22 100644 ---- a/src/shell-window-tracker.c -+++ b/src/shell-window-tracker.c -@@ -15,6 +15,7 @@ - #include "shell-window-tracker-private.h" - #include "shell-app-private.h" - #include "shell-global.h" -+#include "shell-realms.h" - #include "st.h" - - /* This file includes modified code from -@@ -129,6 +130,22 @@ check_app_id_prefix (ShellApp *app, - return g_str_has_prefix (shell_app_get_id (app), prefix); - } - -+static const char * -+get_window_realm_name (MetaWindow *window) { -+ -+ ShellRealms *realms = shell_realms_get_default(); -+ if (shell_realms_is_citadel_window (realms, window)) { -+ return NULL; -+ } -+ ShellRealmItem *item = shell_realms_realm_by_window(realms, window); -+ -+ if (item) { -+ return shell_realm_item_get_realm_name (item); -+ } else { -+ return NULL; -+ } -+} -+ - /* - * get_app_from_window_wmclass: - * -@@ -146,6 +163,7 @@ get_app_from_window_wmclass (MetaWindow *window) - const char *wm_class; - const char *wm_instance; - const char *sandbox_id; -+ const char *realm_name; - g_autofree char *app_prefix = NULL; - - appsys = shell_app_system_get_default (); -@@ -154,6 +172,8 @@ get_app_from_window_wmclass (MetaWindow *window) - if (sandbox_id) - app_prefix = g_strdup_printf ("%s.", sandbox_id); - -+ realm_name = get_window_realm_name (window); -+ - /* Notes on the heuristics used here: - much of the complexity here comes from the desire to support - Chrome apps. -@@ -191,23 +211,23 @@ get_app_from_window_wmclass (MetaWindow *window) - - /* first try a match from WM_CLASS (instance part) to StartupWMClass */ - wm_instance = meta_window_get_wm_class_instance (window); -- app = shell_app_system_lookup_startup_wmclass (appsys, wm_instance); -+ app = shell_app_system_lookup_startup_wmclass (appsys, wm_instance, realm_name); - if (app != NULL && check_app_id_prefix (app, app_prefix)) - return g_object_ref (app); - - /* then try a match from WM_CLASS to StartupWMClass */ - wm_class = meta_window_get_wm_class (window); -- app = shell_app_system_lookup_startup_wmclass (appsys, wm_class); -+ app = shell_app_system_lookup_startup_wmclass (appsys, wm_class, realm_name); - if (app != NULL && check_app_id_prefix (app, app_prefix)) - return g_object_ref (app); - - /* then try a match from WM_CLASS (instance part) to .desktop */ -- app = shell_app_system_lookup_desktop_wmclass (appsys, wm_instance); -+ app = shell_app_system_lookup_desktop_wmclass (appsys, wm_instance, realm_name); - if (app != NULL && check_app_id_prefix (app, app_prefix)) - return g_object_ref (app); - - /* finally, try a match from WM_CLASS to .desktop */ -- app = shell_app_system_lookup_desktop_wmclass (appsys, wm_class); -+ app = shell_app_system_lookup_desktop_wmclass (appsys, wm_class, realm_name); - if (app != NULL && check_app_id_prefix (app, app_prefix)) - return g_object_ref (app); - -@@ -236,7 +256,19 @@ get_app_from_id (MetaWindow *window, - - appsys = shell_app_system_get_default (); - -- desktop_file = g_strconcat (id, ".desktop", NULL); -+ const char *realm_name = NULL; -+ ShellRealms *realms = shell_realms_get_default(); -+ ShellRealmItem *item = shell_realms_realm_by_window (realms, window); -+ -+ if (item) { -+ realm_name = shell_realm_item_get_realm_name (item); -+ } -+ -+ if (realm_name) { -+ desktop_file = g_strconcat ("realm-", realm_name, ".", id, ".desktop", NULL); -+ } else { -+ desktop_file = g_strconcat (id, ".desktop", NULL); -+ } - app = shell_app_system_lookup_app (appsys, desktop_file); - if (app) - return g_object_ref (app); diff --git a/meta-gnome/recipes-gnome/gnome-shell/gnome-shell_41.1.bb b/meta-gnome/recipes-gnome/gnome-shell/gnome-shell_41.1.bb index badef96..cee8adf 100644 --- a/meta-gnome/recipes-gnome/gnome-shell/gnome-shell_41.1.bb +++ b/meta-gnome/recipes-gnome/gnome-shell/gnome-shell_41.1.bb @@ -5,22 +5,15 @@ REQUIRED_DISTRO_FEATURES = "x11 systemd pam" ERROR_QA_remove = "unknown-configure-option" +SRCREV = "d85bd654a3c830a8c3982286c1876321c96faf7d" + +EXTERNAL_TREE_VAR="CITADEL_GNOME_SHELL_PATH" +GIT_URI = "gitsm://github.com/brl/gnome-shell.git;branch=citadel;protocol=https" GNOMEBASEBUILDCLASS = "meson" -inherit gnomebase gsettings gettext gobject-introspection gobject-introspection-data features_check bash-completion -def gnome_verdir(v): - return oe.utils.trim_version(v, 1) +inherit gnomebase gsettings gettext gobject-introspection gobject-introspection-data features_check bash-completion external-tree -SRC_URI = "${GNOME_MIRROR}/${GNOMEBN}/${@gnome_verdir("${PV}")}/${GNOMEBN}-${PV}.tar.${GNOME_COMPRESS_TYPE};name=archive \ - file://0001-do-not-use-python-path-from-build-environment.patch \ - file://0001-Remove-calendar-server-fix-build.patch \ - file://0001-Remove-log-out-label-from-power-off-in-status-UI.patch \ - file://0001-Disabled-calendar-events-from-user-session.patch \ - file://0002-Citadel-Gnome-Shell-changes.patch \ - " - -SRC_URI[archive.sha256sum] = "5f742456dfe00605c0f090a3728ca62797bc49e50af852bbd7285da1a0517ff6" DEPENDS = " \ diff --git a/meta-gnome/recipes-gnome/mutter/mutter/0001-Citadel-changes-to-Mutter.patch b/meta-gnome/recipes-gnome/mutter/mutter/0001-Citadel-changes-to-Mutter.patch deleted file mode 100644 index 697225a..0000000 --- a/meta-gnome/recipes-gnome/mutter/mutter/0001-Citadel-changes-to-Mutter.patch +++ /dev/null @@ -1,1283 +0,0 @@ -From 3a4b623a1844634ad2276d2784e4f5ab20eb4767 Mon Sep 17 00:00:00 2001 -From: Bruce Leidl -Date: Mon, 4 Oct 2021 13:10:48 +0000 -Subject: [PATCH] Citadel changes to Mutter - ---- - src/compositor/compositor.c | 9 +- - src/core/meta-workspace-manager-private.h | 69 ++++ - src/core/meta-workspace-manager.c | 450 +++++++++++++++++++++- - src/core/util-private.h | 2 + - src/core/util.c | 48 +++ - src/core/window-private.h | 3 + - src/core/window.c | 41 +- - src/core/workspace-private.h | 3 + - src/core/workspace.c | 168 ++++---- - src/meta/common.h | 4 +- - src/meta/meta-workspace-manager.h | 44 +++ - src/meta/types.h | 1 + - src/meta/window.h | 6 + - src/meta/workspace.h | 6 + - 14 files changed, 769 insertions(+), 85 deletions(-) - -diff --git a/src/compositor/compositor.c b/src/compositor/compositor.c -index 9cdd39c..879950a 100644 ---- a/src/compositor/compositor.c -+++ b/src/compositor/compositor.c -@@ -747,8 +747,13 @@ meta_compositor_switch_workspace (MetaCompositor *compositor, - meta_compositor_get_instance_private (compositor); - gint to_indx, from_indx; - -- to_indx = meta_workspace_index (to); -- from_indx = meta_workspace_index (from); -+ if (direction == META_MOTION_CONTEXT_SWITCH) { -+ to_indx = meta_workspace_get_id (to); -+ from_indx = meta_workspace_get_id (from); -+ } else { -+ to_indx = meta_workspace_index (to); -+ from_indx = meta_workspace_index (from); -+ } - - priv->switch_workspace_in_progress++; - -diff --git a/src/core/meta-workspace-manager-private.h b/src/core/meta-workspace-manager-private.h -index 261c4d4..4b5d4f4 100644 ---- a/src/core/meta-workspace-manager-private.h -+++ b/src/core/meta-workspace-manager-private.h -@@ -30,6 +30,36 @@ - #include "meta/types.h" - #include "meta/meta-workspace-manager.h" - -+// -+// Stores list of MetaWorkspaces and pointer to an active MetaWorkspace. When this context becomes -+// the current context, the workspace fields are swapped into the corresponding fields in -+// MetaWorkspaceManager. -+// -+// -+struct _MetaWorkspaceContext -+{ -+ -+ GObject parent; -+ // Link back to MetaWorkspaceManager -+ MetaWorkspaceManager *manager; -+ -+ // MetaWorkspace list belonging to this context. Copied to field with same name in -+ // MetaWorkspaceManager when this context is active. Any code which changes this list -+ // must make sure this context is not currently active in which case the list in -+ // MetaWorkspaceManager must be changed instead. -+ GList *workspaces; -+ -+ // Active MetaWorkspace for this context. Also copied to workspace manager upon activation. -+ // The rule above about not writing if context is currently active also applies to this field. -+ MetaWorkspace *active_workspace; -+ -+ // PID namespace -+ gchar *namespace; -+ -+ // A unique ID value for this context. -+ guint id; -+}; -+ - struct _MetaWorkspaceManager - { - GObject parent; -@@ -37,8 +67,21 @@ struct _MetaWorkspaceManager - MetaDisplay *display; - MetaWorkspace *active_workspace; - -+ GList *all_workspaces; - GList *workspaces; - -+ // List of WorkspaceContext -+ GList *context_list; -+ -+ gchar *mutter_namespace; -+ -+ // Current active WorkspaceContext. MetaWorkspaceManager state (workspaces, active_workspace) -+ // will be saved here when a new WorkspaceContext is made active. -+ MetaWorkspaceContext *active_context; -+ -+ // The next id value to allocate when creating a new WorkspaceContext -+ guint next_context_id; -+ - int rows_of_workspaces; - int columns_of_workspaces; - MetaDisplayCorner starting_corner; -@@ -93,4 +136,30 @@ void meta_workspace_manager_update_num_workspaces (MetaWorkspaceManager *workspa - guint32 timestamp, - int new_num); - -+MetaWorkspaceContext *meta_workspace_context_new (MetaWorkspaceManager *manager, const char *namespace); -+ -+void meta_workspace_context_make_active (MetaWorkspaceContext *context); -+ -+MetaWorkspaceContext *meta_workspace_manager_lookup_context (MetaWorkspaceManager *workspace_manager, -+ guint context_id); -+ -+ -+void meta_workspace_manager_append_context_workspace (MetaWorkspaceManager *manager, -+ MetaWorkspace *workspace); -+ -+void meta_workspace_manager_remove_context_workspace (MetaWorkspaceManager *manager, -+ MetaWorkspace *workspace); -+ -+int meta_workspace_manager_context_workspace_index (MetaWorkspaceManager *workspace_manager, -+ MetaWorkspace *workspace); -+ -+int meta_workspace_manager_get_workspace_id (MetaWorkspaceManager *workspace_manager, -+ MetaWorkspace *workspace); -+MetaWorkspace * -+meta_workspace_manager_lookup_workspace_by_id (MetaWorkspaceManager *workspace_manager, int workspace_id); -+ -+gboolean -+meta_workspace_manager_is_window_on_foreign_context (MetaWorkspaceManager *workspace_manager, MetaWindow *window); -+ -+ - #endif /* META_WORKSPACE_MANAGER_PRIVATE_H */ -diff --git a/src/core/meta-workspace-manager.c b/src/core/meta-workspace-manager.c -index 61fbc00..1ebb240 100644 ---- a/src/core/meta-workspace-manager.c -+++ b/src/core/meta-workspace-manager.c -@@ -44,6 +44,9 @@ enum - WORKSPACES_REORDERED, - ACTIVE_WORKSPACE_CHANGED, - SHOWING_DESKTOP_CHANGED, -+ CONTEXT_SWITCHED, -+ CONTEXT_WINDOW_MOVED, -+ CONTEXT_REMOVED, - LAST_SIGNAL - }; - -@@ -178,6 +181,29 @@ meta_workspace_manager_class_init (MetaWorkspaceManagerClass *klass) - 0, NULL, NULL, NULL, - G_TYPE_NONE, 0); - -+ workspace_manager_signals[CONTEXT_SWITCHED] = -+ g_signal_new ("context-switched", -+ G_TYPE_FROM_CLASS (klass), -+ G_SIGNAL_RUN_LAST, -+ 0, NULL, NULL, NULL, -+ G_TYPE_NONE, 0); -+ -+ workspace_manager_signals[CONTEXT_WINDOW_MOVED] = -+ g_signal_new("context-window-moved", -+ G_TYPE_FROM_CLASS(klass), -+ G_SIGNAL_RUN_LAST, -+ 0, NULL, NULL, NULL, -+ G_TYPE_NONE, 1, -+ META_TYPE_WINDOW); -+ -+ workspace_manager_signals[CONTEXT_REMOVED] = -+ g_signal_new("context-removed", -+ G_TYPE_FROM_CLASS(klass), -+ G_SIGNAL_RUN_LAST, -+ 0, NULL, NULL, NULL, -+ G_TYPE_NONE, 1, -+ G_TYPE_INT); -+ - g_object_class_install_property (object_class, - PROP_LAYOUT_COLUMNS, - g_param_spec_int ("layout-columns", -@@ -201,6 +227,7 @@ meta_workspace_manager_class_init (MetaWorkspaceManagerClass *klass) - "Number of workspaces", - 1, G_MAXINT, 1, - G_PARAM_READABLE)); -+ - } - - static void -@@ -213,7 +240,7 @@ meta_workspace_manager_reload_work_areas (MetaWorkspaceManager *workspace_manage - { - GList *l; - -- for (l = workspace_manager->workspaces; l; l = l->next) -+ for (l = workspace_manager->all_workspaces; l; l = l->next) - { - MetaWorkspace *workspace = l->data; - -@@ -230,12 +257,24 @@ meta_workspace_manager_new (MetaDisplay *display) - - workspace_manager->display = display; - workspace_manager->active_workspace = NULL; -+ workspace_manager->all_workspaces = NULL; - workspace_manager->workspaces = NULL; - workspace_manager->rows_of_workspaces = 1; - workspace_manager->columns_of_workspaces = -1; - workspace_manager->vertical_workspaces = FALSE; - workspace_manager->starting_corner = META_DISPLAY_TOPLEFT; - -+ workspace_manager->context_list = NULL; -+ workspace_manager->active_context = NULL; -+ workspace_manager->next_context_id = 1; -+ workspace_manager->mutter_namespace = meta_read_pid_namespace (getpid()); -+ -+ MetaWorkspaceContext *context = meta_workspace_context_new (workspace_manager, NULL); -+ -+ workspace_manager->workspaces = g_steal_pointer (&context->workspaces); -+ workspace_manager->active_workspace = g_steal_pointer (&context->active_workspace); -+ workspace_manager->active_context = context; -+ - /* This is the default layout extracted from default - * variable values in update_num_workspaces () - * This can be overridden using _NET_DESKTOP_LAYOUT in -@@ -246,11 +285,6 @@ meta_workspace_manager_new (MetaDisplay *display) - 1, - -1); - -- /* There must be at least one workspace at all times, -- * so create that required workspace. -- */ -- meta_workspace_new (workspace_manager); -- - meta_workspace_manager_init_workspaces (workspace_manager); - - meta_prefs_add_listener (prefs_changed_callback, workspace_manager); -@@ -301,6 +335,9 @@ MetaWorkspace * - meta_workspace_manager_get_workspace_by_index (MetaWorkspaceManager *workspace_manager, - int idx) - { -+ if ((idx >> 16) & 0xFFFF) { -+ return meta_workspace_manager_lookup_workspace_by_id (workspace_manager, idx); -+ } - return g_list_nth_data (workspace_manager->workspaces, idx); - } - -@@ -1001,7 +1038,7 @@ meta_workspace_manager_unshow_desktop (MetaWorkspaceManager *workspace_manager) - GList * - meta_workspace_manager_get_workspaces (MetaWorkspaceManager *workspace_manager) - { -- return workspace_manager->workspaces; -+ return workspace_manager->all_workspaces; - } - - int -@@ -1015,6 +1052,17 @@ meta_workspace_manager_get_active_workspace_index (MetaWorkspaceManager *workspa - return meta_workspace_index (active); - } - -+int -+meta_workspace_manager_get_active_workspace_id (MetaWorkspaceManager *workspace_manager) -+{ -+ MetaWorkspace *active = workspace_manager->active_workspace; -+ -+ if (!active) -+ return -1; -+ -+ return meta_workspace_get_id (active); -+} -+ - /** - * meta_workspace_manager_get_active_workspace: - * @workspace_manager: A #MetaWorkspaceManager -@@ -1033,9 +1081,15 @@ meta_workspace_manager_workspace_switched (MetaWorkspaceManager *workspace_manag - int to, - MetaMotionDirection direction) - { -- g_signal_emit (workspace_manager, -- workspace_manager_signals[WORKSPACE_SWITCHED], 0, -- from, to, direction); -+ if (direction == META_MOTION_CONTEXT_SWITCH) { -+ g_signal_emit (workspace_manager, -+ workspace_manager_signals[CONTEXT_SWITCHED], -+ 0, NULL); -+ } else { -+ g_signal_emit (workspace_manager, -+ workspace_manager_signals[WORKSPACE_SWITCHED], 0, -+ from, to, direction); -+ } - } - - static void -@@ -1058,3 +1112,379 @@ prefs_changed_callback (MetaPreference pref, - timestamp, new_num); - } - } -+ -+ -+/** -+ * meta_workspace_manager_set_builtin_struts_all: -+ * @workspace_manager: a #MetaWorkspaceManager -+ * @struts: (element-type Meta.Strut) (transfer none): list of #MetaStrut -+ * -+ * Sets a list of struts on every workspace that will be used in addition to the struts -+ * of the windows in the workspace when computing the work area of -+ * the workspace. -+ */ -+void meta_workspace_manager_set_builtin_struts_all(MetaWorkspaceManager *workspace_manager, -+ GSList *struts) -+{ -+ GList *context_iter = workspace_manager->context_list; -+ while (context_iter) { -+ MetaWorkspaceContext *context = context_iter->data; -+ for (GList *ws_iter = context->workspaces; ws_iter; ws_iter = ws_iter->next) { -+ MetaWorkspace *workspace = ws_iter->data; -+ meta_workspace_set_builtin_struts(workspace, struts); -+ } -+ context_iter = context_iter->next; -+ } -+} -+ -+ -+ -+/** -+ * meta_workspace_manager_lookup_context: -+ * @workspace_manager: a #MetaWorkspaceManager -+ * @context_id: id value of context to look up -+ * -+ * Look up WorkspaceContext by id and return it. If no context is found with -+ * the specified id the return value is %NULL. -+ * -+ * Return value: (transfer none) (nullable): the workspace context with the specified id -+ * or %NULL if no context with matching id exists. -+ */ -+MetaWorkspaceContext * -+meta_workspace_manager_lookup_context (MetaWorkspaceManager *workspace_manager, guint context_id) -+{ -+ for (GList *iter = workspace_manager->context_list; iter; iter = iter->next) { -+ MetaWorkspaceContext *context = iter->data; -+ if (context_id == context->id) { -+ return context; -+ } -+ } -+ return NULL; -+} -+ -+const char * -+meta_workspace_manager_mutter_namespace (MetaWorkspaceManager *workspace_manager) -+{ -+ if (!workspace_manager->mutter_namespace) { -+ workspace_manager->mutter_namespace = meta_read_pid_namespace (getpid()); -+ } -+ return workspace_manager->mutter_namespace; -+} -+ -+/* -+ * Return pointer to the live workspace list depending on whether or not the context is currently active. -+ * While a workspace context is active, the list must be accessed through the pointer workspace_manager->workspaces -+ */ -+GList ** -+meta_workspace_manager_workspace_list_for_context (MetaWorkspaceManager *workspace_manager, guint context_id) -+{ -+ if (workspace_manager->active_context && workspace_manager->active_context->id == context_id) { -+ return &workspace_manager->workspaces; -+ } -+ -+ MetaWorkspaceContext *context = meta_workspace_manager_lookup_context (workspace_manager, context_id); -+ -+ if (context) { -+ return &context->workspaces; -+ } else { -+ g_warning ("MetaWorkspaceManager: Failed to find context workspace list (context_id = %d)", context_id); -+ return NULL; -+ } -+} -+ -+void -+meta_workspace_manager_append_context_workspace (MetaWorkspaceManager *workspace_manager, MetaWorkspace *workspace) -+{ -+ GList **p_workspaces = meta_workspace_manager_workspace_list_for_context (workspace_manager, workspace->context_id); -+ if (p_workspaces) { -+ *p_workspaces = g_list_append (*p_workspaces, workspace); -+ } -+} -+ -+void -+meta_workspace_manager_remove_context_workspace (MetaWorkspaceManager *workspace_manager, MetaWorkspace *workspace) -+{ -+ GList **p_workspaces = meta_workspace_manager_workspace_list_for_context (workspace_manager, workspace->context_id); -+ if (p_workspaces) { -+ *p_workspaces = g_list_remove (*p_workspaces, workspace); -+ } -+ workspace_manager->all_workspaces = g_list_remove (workspace_manager->all_workspaces, workspace); -+} -+ -+static int -+workspace_index_in_context (MetaWorkspaceManager *workspace_manager, MetaWorkspace *workspace) -+{ -+ GList **p_workspaces = meta_workspace_manager_workspace_list_for_context (workspace_manager, workspace->context_id); -+ if (p_workspaces) { -+ return g_list_index (*p_workspaces, workspace); -+ } else { -+ g_warning ("MetaWorkspaceManager: Could not find context for id=%d in workspace_index_in_context()", workspace->context_id); -+ return -1; -+ } -+} -+ -+int -+meta_workspace_manager_get_workspace_id (MetaWorkspaceManager *workspace_manager, MetaWorkspace *workspace) -+{ -+ int idx = workspace_index_in_context (workspace_manager, workspace); -+ -+ if (idx >= 0) { -+ idx |= (int)(workspace->context_id << 16); -+ } else { -+ g_warning ("MetaWorkspaceManager: Workspace with context_id = %d not found in meta_workspace_manager_get_workspace_id()", workspace->context_id); -+ } -+ return idx; -+} -+ -+int -+meta_workspace_manager_context_workspace_index (MetaWorkspaceManager *workspace_manager, MetaWorkspace *workspace) -+{ -+ -+ if (!(workspace_manager->active_context && workspace_manager->active_context->id == workspace->context_id)) { -+ guint active = (workspace_manager->active_context) ? (workspace_manager->active_context->id) : 0; -+ g_warning ("MetaWorkspaceManager: context_workspace_index() called on workspace not in active context. (ws->context_id = %d, active->context_id=%d)", -+ workspace->context_id, active); -+ } -+ -+ int idx = workspace_index_in_context (workspace_manager, workspace); -+ if (idx < 0) { -+ g_warning ("MetaWorkspaceManager: Failed to find workspace with context_id=%d in context_workspace_index()", -+ workspace->context_id); -+ } -+ return idx; -+} -+ -+MetaWorkspace * -+meta_workspace_manager_lookup_workspace_by_id (MetaWorkspaceManager *workspace_manager, int workspace_id) -+{ -+ uint context_id = (workspace_id >> 16) & 0xFFFF; -+ if (context_id) { -+ GList **p_workspaces = meta_workspace_manager_workspace_list_for_context (workspace_manager, context_id); -+ int idx = workspace_id & 0xFFFF; -+ if (p_workspaces) { -+ return g_list_nth_data (*p_workspaces, idx); -+ } -+ } -+ return NULL; -+} -+ -+/** -+ * meta_workspace_manager_context_for_namespace: -+ * @workspace_manager: a #MetaWorkspaceManager -+ * @namespace: namespace of context -+ * -+ * Find an existing WorkspaceContext for the specified namespace. If no WorkspaceContext -+ * currently exists for namespace create a new one. -+ * -+ * Return value: (transfer none): the workspace context for the specified namespace -+ */ -+MetaWorkspaceContext * -+meta_workspace_manager_context_for_namespace (MetaWorkspaceManager *workspace_manager, const gchar *namespace) -+{ -+ MetaWorkspaceContext *anonymous_context = NULL; -+ -+ for (GList *iter = workspace_manager->context_list; iter; iter = iter->next) { -+ MetaWorkspaceContext *context = iter->data; -+ if (context->namespace == NULL && anonymous_context == NULL) { -+ anonymous_context = context; -+ } else if (!g_strcmp0 (context->namespace, namespace )) { -+ return context; -+ } -+ } -+ -+ if (anonymous_context) { -+ anonymous_context->namespace = g_strdup (namespace); -+ return anonymous_context; -+ } -+ -+ return meta_workspace_context_new (workspace_manager, namespace); -+} -+ -+guint -+meta_workspace_manager_active_context_id (MetaWorkspaceManager *workspace_manager) -+{ -+ if (workspace_manager->active_context) { -+ return workspace_manager->active_context->id; -+ } else { -+ return 0; -+ } -+} -+ -+G_DEFINE_TYPE (MetaWorkspaceContext, meta_workspace_context, G_TYPE_OBJECT) -+ -+guint -+meta_workspace_context_id (MetaWorkspaceContext *context) -+{ -+ return context->id; -+} -+ -+void -+meta_workspace_context_remove (MetaWorkspaceContext *context) -+{ -+ if (meta_workspace_context_is_current (context)) { -+ g_warning ("MetaWorkspaceManager: attempt to remove active context ignored"); -+ return; -+ } -+ -+ for (GList *iter = context->workspaces; iter; iter = iter->next) { -+ MetaWorkspace *workspace = iter->data; -+ meta_workspace_relocate_windows (workspace, context->manager->active_workspace); -+ } -+ context->active_workspace = NULL; -+ g_list_free_full (g_steal_pointer(&context->workspaces), (GDestroyNotify) meta_workspace_remove); -+ -+ g_signal_emit (context->manager, workspace_manager_signals[CONTEXT_REMOVED], -+ 0, context->id); -+ -+ g_clear_pointer (&context->namespace, g_free); -+ context->manager->context_list = g_list_remove (context->manager->context_list, context); -+ g_object_unref (context); -+} -+ -+static void -+meta_workspace_context_class_init (MetaWorkspaceContextClass *klass) -+{ -+} -+ -+static void -+meta_workspace_context_init (MetaWorkspaceContext *context) -+{ -+} -+ -+MetaWorkspaceContext * -+meta_workspace_context_new (MetaWorkspaceManager *manager, const char *namespace) -+{ -+ guint context_id = manager->next_context_id++; -+ -+ MetaWorkspaceContext *context = g_object_new (META_TYPE_WORKSPACE_CONTEXT, NULL); -+ -+ context->manager = manager; -+ context->workspaces = NULL; -+ context->active_workspace = NULL; -+ context->namespace = NULL; -+ context->id = context_id; -+ -+ if (namespace) { -+ context->namespace = g_strdup (namespace); -+ } -+ -+ manager->context_list = g_list_append (manager->context_list, context); -+ -+ context->active_workspace = meta_workspace_new_with_context_id (manager, context_id); -+ g_object_notify (G_OBJECT (manager), "n-workspaces"); -+ -+ return context; -+} -+ -+ -+/* -+ * Synchronize the state stored in the MetaWorkspaceManager to 'context' -+ * by copying the relevant fields to the WorkspaceContext structure. -+ * -+ * The state which is saved is the list of workspaces and the active -+ * workspace pointer: -+ * -+ * workspace_manager->active_workspace -+ * workspace_manager->workspaces -+ */ -+static void -+workspace_context_sync_manager_state (MetaWorkspaceContext *context) -+{ -+ context->active_workspace = context->manager->active_workspace; -+ context->workspaces = context->manager->workspaces; -+} -+ -+gboolean -+meta_workspace_context_is_current (MetaWorkspaceContext *context) -+{ -+ return context && context->manager->active_context == context; -+} -+ -+/** -+ * meta_workspace_context_get_active_workspace: -+ * @context: A #MetaWorkspaceContext -+ * -+ * Returns: (transfer none): The current workspace for this context -+ */ -+MetaWorkspace * -+meta_workspace_context_get_active_workspace (MetaWorkspaceContext *context) -+{ -+ if (meta_workspace_context_is_current (context)) { -+ return context->manager->active_workspace; -+ } else { -+ return context->active_workspace; -+ } -+} -+ -+void -+meta_workspace_context_move_window_to_context (MetaWorkspaceContext *context, MetaWindow *window) -+{ -+ if (!context->active_workspace) { -+ g_warning ("MetaWorkspaceContext: Cannot move window to context (%d) because no workspace is active", context->id); -+ return; -+ } -+ -+ if (window->workspace->context_id != context->id) { -+ meta_window_change_workspace (window, context->active_workspace); -+ g_signal_emit (context->manager, workspace_manager_signals[CONTEXT_WINDOW_MOVED], 0, window); -+ } -+} -+ -+ -+void -+meta_workspace_context_make_active (MetaWorkspaceContext *context) { -+ -+ MetaWorkspaceManager *manager = context->manager; -+ -+ workspace_context_sync_manager_state (manager->active_context); -+ -+ manager->active_context = context; -+ -+ /* -+ * Do not update manager->active_workspace because meta_workspace_activate() expects -+ * old value -+ */ -+ -+ /* steal pointer to avoid writing code elsewhere that changes this list while context is active */ -+ manager->workspaces = g_steal_pointer (&context->workspaces); -+} -+ -+/* -+ * Activate a WorkspaceContext instance by copying the context state fields -+ * into the MetaWorkspaceManager structure. -+ * -+ * If 'context' is already the current WorkspaceContext do nothing. -+ * -+ * If some other context is active, save the context fields by calling -+ * -+ * workspace_context_sync_manager_state() -+ */ -+void -+meta_workspace_context_activate (MetaWorkspaceContext *context) -+{ -+ if (meta_workspace_context_is_current (context)) { -+ return; -+ } -+ -+ meta_workspace_context_make_active (context); -+ -+ guint32 timestamp; -+ -+ timestamp = meta_display_get_current_time_roundtrip (context->manager->display); -+ -+ /* Will set manager->active_workspace from context->active_workspace */ -+ meta_workspace_activate ( g_steal_pointer (&context->active_workspace), timestamp); -+} -+ -+gboolean -+meta_workspace_manager_is_window_on_foreign_context (MetaWorkspaceManager *workspace_manager, MetaWindow *window) -+{ -+ const char *ns = meta_window_namespace (window); -+ -+ if (!ns || !g_strcmp0 (ns, workspace_manager->mutter_namespace) || !window->workspace) { -+ return FALSE; -+ } -+ -+ MetaWorkspaceContext *context = meta_workspace_manager_context_for_namespace (workspace_manager, ns); -+ return context->id != window->workspace->context_id; -+} -diff --git a/src/core/util-private.h b/src/core/util-private.h -index 01b6639..27d480e 100644 ---- a/src/core/util-private.h -+++ b/src/core/util-private.h -@@ -58,4 +58,6 @@ void meta_get_clutter_debug_flags (ClutterDebugFlag *debug_flags, - ClutterDrawDebugFlag *draw_flags, - ClutterPickDebugFlag *pick_flags); - -+char * meta_read_pid_namespace (pid_t pid); -+ - #endif -diff --git a/src/core/util.c b/src/core/util.c -index 74a884c..17a13bd 100644 ---- a/src/core/util.c -+++ b/src/core/util.c -@@ -794,3 +794,51 @@ meta_get_debug_paint_flags (void) - { - return debug_paint_flags; - } -+ -+static GFileInfo * -+pid_namespace_file_info (pid_t pid) -+{ -+ char *filename = g_strdup_printf("/proc/%u/ns/pid", pid); -+ GFile *file = g_file_new_for_path(filename); -+ g_free(filename); -+ -+ GError *error = NULL; -+ -+ GFileInfo *info = g_file_query_info(file, -+ G_FILE_ATTRIBUTE_STANDARD_IS_SYMLINK "," G_FILE_ATTRIBUTE_STANDARD_SYMLINK_TARGET, -+ G_FILE_QUERY_INFO_NOFOLLOW_SYMLINKS, -+ NULL, -+ &error); -+ -+ g_object_unref(file); -+ -+ if (info == NULL) { -+ g_warning("MetaWindow: Error attempting to read /proc/%u/ns/pid symlink: %s", pid, error->message); -+ g_error_free (error); -+ return NULL; -+ } -+ -+ if (!g_file_info_get_is_symlink (info)) { -+ g_warning("MetaWindow: /proc/%u/ns/pid exists but is not a symlink?", pid); -+ g_object_unref (info); -+ return NULL; -+ } -+ -+ return info; -+ -+} -+ -+char * -+meta_read_pid_namespace (pid_t pid) -+{ -+ char *value = NULL; -+ GFileInfo *info = pid_namespace_file_info (pid); -+ const char *target = g_file_info_get_symlink_target (info); -+ -+ if (target) { -+ value = g_strdup (target); -+ } -+ g_object_unref (info); -+ -+ return value; -+} -diff --git a/src/core/window-private.h b/src/core/window-private.h -index 3bd75fe..293cc88 100644 ---- a/src/core/window-private.h -+++ b/src/core/window-private.h -@@ -568,6 +568,9 @@ struct _MetaWindow - - gboolean has_valid_cgroup; - GFile *cgroup_path; -+ -+ guint namespace_set: 1; -+ gchar *namespace; - }; - - struct _MetaWindowClass -diff --git a/src/core/window.c b/src/core/window.c -index e543814..02164e0 100644 ---- a/src/core/window.c -+++ b/src/core/window.c -@@ -356,6 +356,7 @@ meta_window_finalize (GObject *object) - g_free (window->gtk_app_menu_object_path); - g_free (window->gtk_menubar_object_path); - g_free (window->placement.rule); -+ g_free (window->namespace); - - G_OBJECT_CLASS (meta_window_parent_class)->finalize (object); - } -@@ -1578,7 +1579,7 @@ meta_window_unmanage (MetaWindow *window, - g_assert (window->workspace == NULL); - - #ifndef G_DISABLE_CHECKS -- tmp = workspace_manager->workspaces; -+ tmp = workspace_manager->all_workspaces; - while (tmp != NULL) - { - MetaWorkspace *workspace = tmp->data; -@@ -4903,7 +4904,7 @@ set_workspace_state (MetaWindow *window, - else if (window->on_all_workspaces) - { - GList *l; -- for (l = workspace_manager->workspaces; l != NULL; l = l->next) -+ for (l = workspace_manager->all_workspaces; l != NULL; l = l->next) - { - MetaWorkspace *ws = l->data; - meta_workspace_remove_window (ws, window); -@@ -4918,7 +4919,7 @@ set_workspace_state (MetaWindow *window, - else if (window->on_all_workspaces) - { - GList *l; -- for (l = workspace_manager->workspaces; l != NULL; l = l->next) -+ for (l = workspace_manager->all_workspaces; l != NULL; l = l->next) - { - MetaWorkspace *ws = l->data; - meta_workspace_add_window (ws, window); -@@ -5276,8 +5277,10 @@ meta_window_change_workspace_by_index (MetaWindow *window, - workspace = - meta_workspace_manager_get_workspace_by_index (workspace_manager, space_index); - -- if (!workspace && append) -+ if (!workspace && append) { - workspace = meta_workspace_manager_append_new_workspace (workspace_manager, FALSE, META_CURRENT_TIME); -+ } -+ - - if (workspace) - meta_window_change_workspace (window, workspace); -@@ -5618,7 +5621,7 @@ meta_window_get_workspaces (MetaWindow *window) - MetaWorkspaceManager *workspace_manager = window->display->workspace_manager; - - if (window->on_all_workspaces) -- return workspace_manager->workspaces; -+ return workspace_manager->all_workspaces; - else if (window->workspace != NULL) - return window->workspace->list_containing_self; - else if (window->constructing) -@@ -8822,3 +8825,31 @@ meta_window_get_client_type (MetaWindow *window) - { - return window->client_type; - } -+ -+/** -+ * meta_window_namespace: -+ * @window: a #MetaWindow -+ * -+ * Returns string identifying PID namespace this window belongs to -+ * if known -+ * -+ * Return value: (transfer none): the pid namespace string, or %NULL -+ */ -+const char * -+meta_window_namespace (MetaWindow *window) -+{ -+ if (!window->namespace_set) { -+ window->namespace_set = TRUE; -+ pid_t pid = meta_window_get_pid (window); -+ if (pid) { -+ window->namespace = meta_read_pid_namespace (pid); -+ } -+ } -+ return window->namespace; -+} -+ -+gboolean -+meta_window_is_on_foreign_workspace_context (MetaWindow *window) -+{ -+ return meta_workspace_manager_is_window_on_foreign_context (window->display->workspace_manager, window); -+} -\ No newline at end of file -diff --git a/src/core/workspace-private.h b/src/core/workspace-private.h -index f0d896f..38c3b4c 100644 ---- a/src/core/workspace-private.h -+++ b/src/core/workspace-private.h -@@ -39,6 +39,7 @@ struct _MetaWorkspace - GObject parent_instance; - MetaDisplay *display; - MetaWorkspaceManager *manager; -+ guint context_id; - - GList *windows; - -@@ -74,6 +75,8 @@ struct _MetaWorkspaceClass - }; - - MetaWorkspace* meta_workspace_new (MetaWorkspaceManager *workspace_manager); -+MetaWorkspace* meta_workspace_new_with_context_id (MetaWorkspaceManager *workspace_manager, -+ guint context_id); - void meta_workspace_remove (MetaWorkspace *workspace); - void meta_workspace_add_window (MetaWorkspace *workspace, - MetaWindow *window); -diff --git a/src/core/workspace.c b/src/core/workspace.c -index 76ba87b..3819c83 100644 ---- a/src/core/workspace.c -+++ b/src/core/workspace.c -@@ -237,7 +237,7 @@ meta_workspace_init (MetaWorkspace *workspace) - } - - MetaWorkspace * --meta_workspace_new (MetaWorkspaceManager *workspace_manager) -+meta_workspace_new_with_context_id (MetaWorkspaceManager *workspace_manager, guint context_id) - { - MetaDisplay *display = workspace_manager->display; - MetaWorkspace *workspace; -@@ -247,9 +247,11 @@ meta_workspace_new (MetaWorkspaceManager *workspace_manager) - - workspace->display = display; - workspace->manager = workspace_manager; -+ workspace->context_id = context_id; -+ -+ workspace_manager->all_workspaces = -+ g_list_append (workspace_manager->all_workspaces, workspace); - -- workspace_manager->workspaces = -- g_list_append (workspace_manager->workspaces, workspace); - workspace->windows = NULL; - workspace->mru_list = NULL; - -@@ -276,9 +278,17 @@ meta_workspace_new (MetaWorkspaceManager *workspace_manager) - meta_workspace_add_window (workspace, l->data); - g_slist_free (windows); - -+ meta_workspace_manager_append_context_workspace (workspace_manager, workspace); -+ - return workspace; - } - -+MetaWorkspace * -+meta_workspace_new (MetaWorkspaceManager *workspace_manager) -+{ -+ return meta_workspace_new_with_context_id (workspace_manager, workspace_manager->active_context->id); -+} -+ - /** - * workspace_free_all_struts: - * @workspace: The workspace. -@@ -333,8 +343,7 @@ meta_workspace_remove (MetaWorkspace *workspace) - - assert_workspace_empty (workspace); - -- manager->workspaces = -- g_list_remove (manager->workspaces, workspace); -+ meta_workspace_manager_remove_context_workspace (manager, workspace); - - meta_workspace_clear_logical_monitor_data (workspace); - -@@ -403,8 +412,8 @@ meta_workspace_add_window (MetaWorkspace *workspace, - if (window->struts) - { - meta_topic (META_DEBUG_WORKAREA, -- "Invalidating work area of workspace %d since we're adding window %s to it", -- meta_workspace_index (workspace), window->desc); -+ "Invalidating work area of workspace %08x since we're adding window %s to it", -+ meta_workspace_get_id (workspace), window->desc); - meta_workspace_invalidate_work_area (workspace); - } - -@@ -432,8 +441,8 @@ meta_workspace_remove_window (MetaWorkspace *workspace, - if (window->struts) - { - meta_topic (META_DEBUG_WORKAREA, -- "Invalidating work area of workspace %d since we're removing window %s from it", -- meta_workspace_index (workspace), window->desc); -+ "Invalidating work area of workspace %08x since we're removing window %s from it", -+ meta_workspace_get_id (workspace), window->desc); - meta_workspace_invalidate_work_area (workspace); - } - -@@ -486,6 +495,11 @@ workspace_switch_sound(MetaWorkspace *from, - int i, nw, x, y, fi, ti; - const char *e; - -+ if (from->context_id != to->context_id) { -+ /* XXX: There is no sound for context switches, but there should be (?) */ -+ return; -+ } -+ - nw = meta_workspace_manager_get_n_workspaces (from->manager); - fi = meta_workspace_index(from); - ti = meta_workspace_index(to); -@@ -566,9 +580,15 @@ meta_workspace_activate_with_focus (MetaWorkspace *workspace, - MetaWorkspaceLayout layout1, layout2; - gint num_workspaces, current_space, new_space; - MetaMotionDirection direction; -+ MetaWorkspaceContext *context; -+ -+ meta_verbose ("Activating workspace %08x", -+ meta_workspace_get_id (workspace)); - -- meta_verbose ("Activating workspace %d", -- meta_workspace_index (workspace)); -+ context = meta_workspace_manager_lookup_context (workspace->manager, workspace->context_id); -+ if(context && !meta_workspace_context_is_current (context)) { -+ meta_workspace_context_make_active (context); -+ } - - if (workspace->manager->active_workspace == workspace) - { -@@ -631,52 +651,59 @@ meta_workspace_activate_with_focus (MetaWorkspace *workspace, - comp = meta_display_get_compositor (workspace->display); - direction = 0; - -- current_space = meta_workspace_index (old); -- new_space = meta_workspace_index (workspace); -- num_workspaces = meta_workspace_manager_get_n_workspaces (workspace->manager); -- meta_workspace_manager_calc_workspace_layout (workspace->manager, num_workspaces, -- current_space, &layout1); - -- meta_workspace_manager_calc_workspace_layout (workspace->manager, num_workspaces, -+ if (workspace->context_id != old->context_id) { -+ direction = META_MOTION_CONTEXT_SWITCH; -+ current_space = meta_workspace_get_id (old); -+ new_space = meta_workspace_get_id (workspace); -+ } else { -+ current_space = meta_workspace_index (old); -+ new_space = meta_workspace_index (workspace); -+ num_workspaces = meta_workspace_manager_get_n_workspaces (workspace->manager); -+ meta_workspace_manager_calc_workspace_layout (workspace->manager, num_workspaces, -+ current_space, &layout1); -+ -+ meta_workspace_manager_calc_workspace_layout (workspace->manager, num_workspaces, - new_space, &layout2); - -- if (meta_get_locale_direction () == META_LOCALE_DIRECTION_RTL) -- { -- if (layout1.current_col > layout2.current_col) -- direction = META_MOTION_RIGHT; -- else if (layout1.current_col < layout2.current_col) -- direction = META_MOTION_LEFT; -- } -- else -- { -- if (layout1.current_col < layout2.current_col) -- direction = META_MOTION_RIGHT; -- else if (layout1.current_col > layout2.current_col) -- direction = META_MOTION_LEFT; -- } -+ if (meta_get_locale_direction () == META_LOCALE_DIRECTION_RTL) -+ { -+ if (layout1.current_col > layout2.current_col) -+ direction = META_MOTION_RIGHT; -+ else if (layout1.current_col < layout2.current_col) -+ direction = META_MOTION_LEFT; -+ } -+ else -+ { -+ if (layout1.current_col < layout2.current_col) -+ direction = META_MOTION_RIGHT; -+ else if (layout1.current_col > layout2.current_col) -+ direction = META_MOTION_LEFT; -+ } - -- if (layout1.current_row < layout2.current_row) -- { -- if (!direction) -- direction = META_MOTION_DOWN; -- else if (direction == META_MOTION_RIGHT) -- direction = META_MOTION_DOWN_RIGHT; -- else -- direction = META_MOTION_DOWN_LEFT; -- } -- -- if (layout1.current_row > layout2.current_row) -- { -- if (!direction) -- direction = META_MOTION_UP; -- else if (direction == META_MOTION_RIGHT) -- direction = META_MOTION_UP_RIGHT; -- else -- direction = META_MOTION_UP_LEFT; -- } -- -- meta_workspace_manager_free_workspace_layout (&layout1); -- meta_workspace_manager_free_workspace_layout (&layout2); -+ if (layout1.current_row < layout2.current_row) -+ { -+ if (!direction) -+ direction = META_MOTION_DOWN; -+ else if (direction == META_MOTION_RIGHT) -+ direction = META_MOTION_DOWN_RIGHT; -+ else -+ direction = META_MOTION_DOWN_LEFT; -+ } -+ -+ if (layout1.current_row > layout2.current_row) -+ { -+ if (!direction) -+ direction = META_MOTION_UP; -+ else if (direction == META_MOTION_RIGHT) -+ direction = META_MOTION_UP_RIGHT; -+ else -+ direction = META_MOTION_UP_LEFT; -+ } -+ -+ meta_workspace_manager_free_workspace_layout (&layout1); -+ meta_workspace_manager_free_workspace_layout (&layout2); -+ } - - meta_compositor_switch_workspace (comp, old, workspace, direction); - -@@ -714,14 +741,19 @@ meta_workspace_activate (MetaWorkspace *workspace, - int - meta_workspace_index (MetaWorkspace *workspace) - { -- int ret; -- -- ret = g_list_index (workspace->manager->workspaces, workspace); -+ return meta_workspace_manager_context_workspace_index (workspace->manager, workspace); -+} - -- if (ret < 0) -- meta_bug ("Workspace does not exist to index!"); -+int -+meta_workspace_get_id (MetaWorkspace *workspace) -+{ -+ return meta_workspace_manager_get_workspace_id (workspace->manager, workspace); -+} - -- return ret; -+guint -+meta_workspace_get_context_id (MetaWorkspace *workspace) -+{ -+ return workspace->context_id; - } - - void -@@ -778,14 +810,14 @@ meta_workspace_invalidate_work_area (MetaWorkspace *workspace) - if (workspace->work_areas_invalid) - { - meta_topic (META_DEBUG_WORKAREA, -- "Work area for workspace %d is already invalid", -- meta_workspace_index (workspace)); -+ "Work area for workspace %08x is already invalid", -+ meta_workspace_get_id (workspace)); - return; - } - - meta_topic (META_DEBUG_WORKAREA, -- "Invalidating work area for workspace %d", -- meta_workspace_index (workspace)); -+ "Invalidating work area for workspace %08x", -+ meta_workspace_get_id (workspace)); - - /* If we are in the middle of a resize or move operation, we - * might have cached pointers to the workspace's edges */ -@@ -956,8 +988,8 @@ ensure_work_areas_validated (MetaWorkspace *workspace) - } - workspace->work_area_screen = work_area; - meta_topic (META_DEBUG_WORKAREA, -- "Computed work area for workspace %d: %d,%d %d x %d", -- meta_workspace_index (workspace), -+ "Computed work area for workspace %08x: %d,%d %d x %d", -+ meta_workspace_get_id (workspace), - workspace->work_area_screen.x, - workspace->work_area_screen.y, - workspace->work_area_screen.width, -@@ -987,8 +1019,8 @@ ensure_work_areas_validated (MetaWorkspace *workspace) - - meta_topic (META_DEBUG_WORKAREA, - "Computed work area for workspace %d " -- "monitor %d: %d,%d %d x %d", -- meta_workspace_index (workspace), -+ "monitor %08x: %d,%d %d x %d", -+ meta_workspace_get_id (workspace), - logical_monitor->number, - data->logical_monitor_work_area.x, - data->logical_monitor_work_area.y, -@@ -1230,6 +1262,8 @@ meta_motion_direction_to_string (MetaMotionDirection direction) - return "Up-Left"; - case META_MOTION_DOWN_LEFT: - return "Down-Left"; -+ case META_MOTION_CONTEXT_SWITCH: -+ return "Switch-Context"; - } - - return "Unknown"; -diff --git a/src/meta/common.h b/src/meta/common.h -index ab93080..32b6912 100644 ---- a/src/meta/common.h -+++ b/src/meta/common.h -@@ -328,6 +328,7 @@ typedef enum - * @META_MOTION_UP_RIGHT: Motion up and to the right - * @META_MOTION_DOWN_LEFT: Motion down and to the left - * @META_MOTION_DOWN_RIGHT: Motion down and to the right -+ * @META_MOTION_REALM_SWITCH: Workspace switch is a change of current realm - */ - - /* Negative to avoid conflicting with real workspace -@@ -343,7 +344,8 @@ typedef enum - META_MOTION_UP_LEFT = -5, - META_MOTION_UP_RIGHT = -6, - META_MOTION_DOWN_LEFT = -7, -- META_MOTION_DOWN_RIGHT = -8 -+ META_MOTION_DOWN_RIGHT = -8, -+ META_MOTION_CONTEXT_SWITCH = -9 - } MetaMotionDirection; - - /** -diff --git a/src/meta/meta-workspace-manager.h b/src/meta/meta-workspace-manager.h -index 92cd681..d00948d 100644 ---- a/src/meta/meta-workspace-manager.h -+++ b/src/meta/meta-workspace-manager.h -@@ -30,6 +30,14 @@ - #include - #include - -+#define META_TYPE_WORKSPACE_CONTEXT (meta_workspace_context_get_type ()) -+ -+META_EXPORT -+G_DECLARE_FINAL_TYPE (MetaWorkspaceContext, -+ meta_workspace_context, -+ META, WORKSPACE_CONTEXT, -+ GObject) -+ - #define META_TYPE_WORKSPACE_MANAGER (meta_workspace_manager_get_type ()) - - META_EXPORT -@@ -66,6 +74,9 @@ void meta_workspace_manager_reorder_workspace (MetaWorkspaceManager *workspace_m - META_EXPORT - int meta_workspace_manager_get_active_workspace_index (MetaWorkspaceManager *workspace_manager); - -+META_EXPORT -+int meta_workspace_manager_get_active_workspace_id (MetaWorkspaceManager *workspace_manager); -+ - META_EXPORT - MetaWorkspace *meta_workspace_manager_get_active_workspace (MetaWorkspaceManager *workspace_manager); - -@@ -75,4 +86,37 @@ void meta_workspace_manager_override_workspace_layout (MetaWorkspaceManager *wor - gboolean vertical_layout, - int n_rows, - int n_columns); -+META_EXPORT -+guint meta_workspace_manager_active_context_id (MetaWorkspaceManager *workspace_manager); -+ -+META_EXPORT -+void meta_workspace_manager_set_builtin_struts_all(MetaWorkspaceManager *workspace_manager, -+ GSList *struts); -+ -+META_EXPORT -+MetaWorkspaceContext *meta_workspace_manager_context_for_namespace (MetaWorkspaceManager *workspace_manager, -+ const gchar *namespace); -+ -+META_EXPORT -+const char *meta_workspace_manager_mutter_namespace (MetaWorkspaceManager *workspace_manager); -+ -+META_EXPORT -+void meta_workspace_context_activate (MetaWorkspaceContext *context); -+ -+META_EXPORT -+void meta_workspace_context_remove (MetaWorkspaceContext *context); -+ -+META_EXPORT -+gboolean meta_workspace_context_is_current (MetaWorkspaceContext *context); -+ -+META_EXPORT -+MetaWorkspace * -+meta_workspace_context_get_active_workspace (MetaWorkspaceContext *context); -+ -+META_EXPORT -+guint meta_workspace_context_id (MetaWorkspaceContext *context); -+ -+META_EXPORT -+void meta_workspace_context_move_window_to_context (MetaWorkspaceContext *context, MetaWindow *window); -+ - #endif /* META_WORKSPACE_MANAGER_H */ -diff --git a/src/meta/types.h b/src/meta/types.h -index 403e62b..4e2f042 100644 ---- a/src/meta/types.h -+++ b/src/meta/types.h -@@ -43,6 +43,7 @@ typedef struct _MetaCursorTracker MetaCursorTracker; - typedef struct _MetaDnd MetaDnd; - typedef struct _MetaSettings MetaSettings; - -+typedef struct _MetaWorkspaceContext MetaWorkspaceContext; - typedef struct _MetaWorkspaceManager MetaWorkspaceManager; - typedef struct _MetaSelection MetaSelection; - -diff --git a/src/meta/window.h b/src/meta/window.h -index 2b69269..f4aebf0 100644 ---- a/src/meta/window.h -+++ b/src/meta/window.h -@@ -450,4 +450,10 @@ uint64_t meta_window_get_id (MetaWindow *window); - META_EXPORT - MetaWindowClientType meta_window_get_client_type (MetaWindow *window); - -+META_EXPORT -+const char *meta_window_namespace (MetaWindow *window); -+ -+META_EXPORT -+gboolean meta_window_is_on_foreign_workspace_context (MetaWindow *window); -+ - #endif -diff --git a/src/meta/workspace.h b/src/meta/workspace.h -index 99aebee..4b94cff 100644 ---- a/src/meta/workspace.h -+++ b/src/meta/workspace.h -@@ -39,6 +39,12 @@ GType meta_workspace_get_type (void); - META_EXPORT - int meta_workspace_index (MetaWorkspace *workspace); - -+META_EXPORT -+int meta_workspace_get_id (MetaWorkspace *workspace); -+ -+META_EXPORT -+guint meta_workspace_get_context_id (MetaWorkspace *workspace); -+ - META_EXPORT - MetaDisplay *meta_workspace_get_display (MetaWorkspace *workspace); - diff --git a/meta-gnome/recipes-gnome/mutter/mutter/0001-Fix-libmutter_dep-in-meson.build-for-Citadel-builds.patch b/meta-gnome/recipes-gnome/mutter/mutter/0001-Fix-libmutter_dep-in-meson.build-for-Citadel-builds.patch deleted file mode 100644 index 20b0c88..0000000 --- a/meta-gnome/recipes-gnome/mutter/mutter/0001-Fix-libmutter_dep-in-meson.build-for-Citadel-builds.patch +++ /dev/null @@ -1,21 +0,0 @@ -From fa052377263464109035564b17cfa2aa76e35016 Mon Sep 17 00:00:00 2001 -From: David McKinney -Date: Fri, 12 Feb 2021 13:53:31 +0000 -Subject: [PATCH] Fix libmutter_dep in meson.build for Citadel builds - ---- - src/meson.build | 1 + - 1 file changed, 1 insertion(+) - -diff --git a/src/meson.build b/src/meson.build -index e7c99ca..8fe484e 100644 ---- a/src/meson.build -+++ b/src/meson.build -@@ -955,6 +955,7 @@ libmutter = shared_library(libmutter_name, - libmutter_dep = declare_dependency( - link_with: libmutter, - include_directories: mutter_includes, -+ sources: mutter_built_sources, - dependencies: [ - libmutter_cogl_dep, - libmutter_clutter_dep, diff --git a/meta-gnome/recipes-gnome/mutter/mutter_41.1.bb b/meta-gnome/recipes-gnome/mutter/mutter_41.1.bb index 94b36ab..d02d4c0 100644 --- a/meta-gnome/recipes-gnome/mutter/mutter_41.1.bb +++ b/meta-gnome/recipes-gnome/mutter/mutter_41.1.bb @@ -37,19 +37,14 @@ DEPENDS = " \ RDEPENDS_${PN} += " xserver-xorg-xwayland zenity" +SRCREV = "7cab502754edef977215bdfc9f8dfa0849a426f3" + +EXTERNAL_TREE_VAR="CITADEL_MUTTER_PATH" +GIT_URI = "git://github.com/brl/mutter.git;branch=citadel;protocol=https" + GNOMEBASEBUILDCLASS = "meson" -inherit gnomebase gsettings gobject-introspection gettext upstream-version-is-even features_check - -def gnome_verdir(v): - return oe.utils.trim_version(v, 1) - -SRC_URI = "${GNOME_MIRROR}/${GNOMEBN}/${@gnome_verdir("${PV}")}/${GNOMEBN}-${PV}.tar.${GNOME_COMPRESS_TYPE};name=archive \ - file://0001-Fix-libmutter_dep-in-meson.build-for-Citadel-builds.patch \ - file://0001-Citadel-changes-to-Mutter.patch \ - " - -SRC_URI[archive.sha256sum] = "58e63fd0bc43f35134f21313aff4aebf92c829d6df4dc99a044a1e376691e3f3" +inherit gnomebase gsettings gobject-introspection gettext upstream-version-is-even features_check external-tree # x11 is still mandatory - see meson.build REQUIRED_DISTRO_FEATURES = "x11 systemd"