diff --git a/data/theme/gnome-shell-sass/widgets/_realms.scss b/data/theme/gnome-shell-sass/widgets/_realms.scss index 49a824c38..815a977ea 100644 --- a/data/theme/gnome-shell-sass/widgets/_realms.scss +++ b/data/theme/gnome-shell-sass/widgets/_realms.scss @@ -7,10 +7,22 @@ padding: .5em; } -.realm-frame-label { +.realm-window-label { + color: rgb(40, 40, 40); + padding: 6px 1em; + border-radius: 999px; + text-align: center; font-size: 12pt; font-weight: bold; } +.realm-app-icon-label { + border-width: 3px; + border-style: solid; + border-color: rgb(40, 40, 40); + border-radius: 8px; + padding: 2px 1em; + margin-bottom: 8px; +} .realm-config-icon { color: #8e8e80; diff --git a/js/js-resources.gresource.xml b/js/js-resources.gresource.xml index abf72a7b9..b20402d7c 100644 --- a/js/js-resources.gresource.xml +++ b/js/js-resources.gresource.xml @@ -160,7 +160,7 @@ ui/realms/realmManager.js ui/realms/realmSearchProvider.js ui/realms/realmSwitcher.js - ui/realms/realmWindowFrame.js + ui/realms/realmLabels.js ui/realms/realmWindowMenu.js diff --git a/js/ui/appDisplay.js b/js/ui/appDisplay.js index 3385a60f9..cd692bf43 100644 --- a/js/ui/appDisplay.js +++ b/js/ui/appDisplay.js @@ -3009,6 +3009,14 @@ export const AppIcon = GObject.registerClass({ iconParams['createIcon'] = this._createIcon.bind(this); iconParams['setSizeManually'] = true; this.icon = new IconGrid.BaseIcon(app.get_name(), iconParams); + + if (Main.realmManager.appIconLabelsEnabled()) { + const realmLabel = Main.realmManager.createRealmLabelForApp(app); + if (realmLabel) { + this.icon._box.insert_child_at_index(realmLabel, 0); + } + } + this._iconContainer.add_child(this.icon); this._dot = new St.Widget({ diff --git a/js/ui/realms/realmLabels.js b/js/ui/realms/realmLabels.js new file mode 100644 index 000000000..787be93bb --- /dev/null +++ b/js/ui/realms/realmLabels.js @@ -0,0 +1,431 @@ +import Clutter from 'gi://Clutter'; +import GObject from 'gi://GObject'; +import Meta from 'gi://Meta'; +import Shell from 'gi://Shell'; +import GLib from 'gi://GLib'; +import Gio from 'gi://Gio'; +import St from 'gi://St'; + +const LABEL_FADE_TIMEOUT = 1500; +const LABEL_FADEIN_TIME = 250; + +const CITADEL_SETTINGS_SCHEMA = 'com.subgraph.citadel'; +const LABEL_COLOR_LIST_KEY = 'label-color-list'; +const REALM_LABEL_COLORS_KEY = 'realm-label-colors'; +const REALM_LABEL_SHOW_CITADEL_KEY = 'realm-label-show-citadel'; +const REALM_LABEL_SHOW_ALL_KEY = 'realm-label-show-all'; +const REALM_LABEL_SHOW_APP_ICONS = 'realm-label-show-app-icons'; + +const CITADEL_LABEL_COLOR = 'rgb(200,0,0)'; +const CITADEL_REALM_NAME = 'Citadel'; + +const WindowLabelColors = class WindowLabelColors { + + constructor(settings) { + this._realm_label_colors = new Map(); + this._defaultColors = []; + const [_ok, color] = Clutter.Color.from_string(CITADEL_LABEL_COLOR); + this.citadelColor = color; + this._citadelSettings = settings; + this.reloadColors(); + } + + reloadColors() { + this._loadDefaultColors(); + this._loadRealmColors(); + } + + colorForRealmName(name) { + if (name === CITADEL_REALM_NAME) { + return this.citadelColor; + } else if (this._realm_label_colors.has(name)) { + return this._realm_label_colors.get(name); + } else { + let color = this._allocateColor(); + this._realm_label_colors.set(name, color); + this._storeColors(); + return color; + } + } + + + _storeColors() { + let entries = []; + for(let [name,color] of this._realm_label_colors) { + entries.push(`${name}:${color.to_string()}`); + } + entries.sort(); + this._citadelSettings.set_strv(REALM_LABEL_COLORS_KEY, entries); + } + + _loadDefaultColors() { + this._defaultColors = []; + let entries = this._citadelSettings.get_strv(LABEL_COLOR_LIST_KEY); + entries.forEach(entry => { + let [ok,color] = Clutter.Color.from_string(entry); + if (ok) { + this._defaultColors.push(color); + } else { + log(`RealmLabels: failed to parse default color entry: ${entry}`) + } + }); + } + + _loadRealmColors() { + this._realm_label_colors.clear(); + let entries = this._citadelSettings.get_strv(REALM_LABEL_COLORS_KEY); + + entries.forEach(entry => { + let parts = entry.split(":"); + if (parts.length === 2) { + let [ok,color] = Clutter.Color.from_string(parts[1]); + if (ok) { + this._realm_label_colors.set(parts[0], color); + } else { + log(`RealmLabels: Failed to parse color from realm color entry: ${entry}`); + } + } else { + log(`RealmLabels: Invalid realm color entry: ${entry}`); + } + }); + } + + _allocateColor() { + // 1) No default colors? return a built in color + if (this._defaultColors.length == 0) { + return Clutter.Color.new(153, 193, 241, 255); + } + + // 2) No default colors? Find first color on default color list that isn't used already + let used_colors = Array.from(this._realm_label_colors.values()); + for (const color of this._defaultColors) { + if (!used_colors.some(c => c.equal(color))) { + return color; + } + } + + // 3) Choose a random element of the default list + let index = Math.floor(Math.random() * this._defaultColors.length); + return this._defaultColors[index]; + } +} + +export const RealmLabelManager = class RealmLabelManager { + constructor() { + + this._realms = Shell.Realms.get_default(); + this._citadelSettings = new Gio.Settings({ schema_id: CITADEL_SETTINGS_SCHEMA }); + this._colors = new WindowLabelColors(this._citadelSettings); + this._showCitadelLabels = this._citadelSettings.get_boolean(REALM_LABEL_SHOW_CITADEL_KEY); + this._showAllLabels = this._citadelSettings.get_boolean(REALM_LABEL_SHOW_ALL_KEY); + this._showAppIconLabels = this._citadelSettings.get_boolean(REALM_LABEL_SHOW_APP_ICONS); + this._citadelSettings.connect('changed', this._syncSettings.bind(this)); + + this._window_labels = new Map(); + + 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)); + + this._syncAllWindows(); + } + + _syncSettings() { + this._colors.reloadColors(); + for (const label of this._window_labels.values()) { + let color = this.colorForWindow(label.window); + label.setColor(color); + } + this._showCitadelLabels = this._citadelSettings.get_boolean(REALM_LABEL_SHOW_CITADEL_KEY); + this._showAllLabels = this._citadelSettings.get_boolean(REALM_LABEL_SHOW_ALL_KEY); + this._showAppIconLabels = this._citadelSettings.get_boolean(REALM_LABEL_SHOW_APP_ICONS); + this._syncAllWindows(); + } + + _onContextWindowMoved(workspaceManager, window) { + const actor = window.get_compositor_private(); + if (actor) { + this._syncWindow(actor); + } + return Clutter.EVENT_PROPAGATE; + } + + _handleWindowMap(shellwm, actor) { + this._syncWindow(actor); + return Clutter.EVENT_PROPAGATE; + } + + _onContextRemoved(workspaceManager, id) { + this._syncAllWindows(); + } + + _syncAllWindows() { + const actors = global.get_window_actors(); + actors.forEach(a => this._syncWindow(a)); + } + + createAppIconLabelForRealm(realmName) { + if (!realmName) { + return null; + } + const color = this._colors.colorForRealmName(realmName); + return new RealmAppIconLabel(realmName, color); + } + + appIconLabelsEnabled() { + return this._showAppIconLabels; + } + + addLabelToWindow(actor, window) { + const win_id = window.get_stable_sequence(); + const name = this.realmNameForWindow(window); + const color = this.colorForWindow(window); + if (name && color) { + this._window_labels.set(win_id, new RealmWindowLabel(actor, color, name)); + window.connectObject('unmanaged', window => this.removeLabelFromWindow(window), this); + } else { + log(`RealmLabels: failed to add label to window`); + } + } + + removeLabelFromWindow(window) { + const win_id = window.get_stable_sequence(); + const label = this._window_labels.get(win_id); + if (label) { + label.destroy(); + this._window_labels.delete(win_id); + } + } + + _getWindowLabel(window) { + const win_id = window.get_stable_sequence(); + return this._window_labels.get(win_id); + } + + _syncWindow(actor) { + const window = actor.metaWindow; + const label = this._getWindowLabel(window); + const needsLabel = this.windowNeedsLabel(window); + + if (label && !needsLabel) { + this.removeLabelFromWindow(window); + } else if (!label && needsLabel) { + this.addLabelToWindow(actor, window); + } + } + + // Return 'true' if 'window' should have a label. + windowNeedsLabel(window) { + // Only show labels on window type 'NORMAL' + if (window.get_window_type() !== Meta.WindowType.NORMAL) { + return false; + } + // Only show labels on citadel windows if showCitadelLabels is enabled. + if (this._realms.is_citadel_window(window)) { + return this._showCitadelLabels; + } + // Unless showAllLabels is enabled only show label on 'foreign' windows + return this._showAllLabels || this._realms.is_foreign_window(window); + } + + realmNameForWindow(window) { + if (this._realms.is_citadel_window(window)) { + return "Citadel"; + } + const realm = this._realms.realm_by_window(window); + if (realm) { + return realm.get_realm_name(); + } else { + log(`RealmLabels: No realm found for window`); + return null; + } + } + + colorForWindow(window) { + if (this._realms.is_citadel_window(window)) { + return this._colors.citadelColor; + } + const realmName = this.realmNameForWindow(window); + if (!realmName) { + return null; + } + const color = this._colors.colorForRealmName(realmName); + return color; + } + + getWindowLabelEnabled(window) { + const label = this._getWindowLabel(window); + return label && label.visible + } + + setWindowLabelEnabled(window, enabled) { + const label = this._getWindowLabel(window); + if (!label) { + return; + } + + if (enabled) { + label.show(); + } else { + label.hide(); + } + } +} + +const RealmAppIconLabel = GObject.registerClass( + class RealmAppIconLabel extends St.Label { + constructor(realmName, color) { + super({ + style_class: 'realm-app-icon-label', + x_align: Clutter.ActorAlign.CENTER, + }); + this.set_text(realmName); + this._color = null; + this.setColor(color); + } + + setColor(color) { + if (this._color && this._color.equal(color)) { + return; + } + if(color) { + this._color = color; + let c = color.to_string().substring(0, 7); + this.set_style(`border-color: ${c};`); + } + } + } +) + +const RealmWindowLabel = GObject.registerClass( + class RealmWindowLabel extends St.Label { + constructor(actor, color, realmName) { + super({ + style_class: 'realm-window-label', + }); + + this.set_text(realmName); + this._color = null; + this.setColor(color); + + this.set_reactive(true); + + this._changedId = actor.metaWindow.connect('size-changed', window => { + this._update(window); + }); + + actor.add_child(this); + this._actor = actor; + + this._update(actor.metaWindow); + } + + setColor(color) { + if (this._color && this._color.equal(color)) { + return; + } + if(color) { + this._color = color; + let c = color.to_string().substring(0, 7); + this.set_style(`background-color: ${c};`); + } + } + + get window() { + return this._actor.metaWindow; + } + + destroy() { + if (this._timeoutId) { + GLib.source_remove(this._timeoutId); + this._timeoutId = 0; + } + this._actor.metaWindow.disconnect(this._changedId); + this._actor.remove_child(this); + } + + _easeOpacity(target) { + this.ease({ + opacity: target, + duration: LABEL_FADEIN_TIME, + mode: Clutter.AnimationMode.EASE_OUT_QUAD, + }) + } + _setDimmedLabel() { + this.opacity = 50; + this._resetTimer(); + } + + _restoreDimmedLabel() { + this._easeOpacity(255); + } + + _updatePosition(window) { + 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; + this.set_position(offsetX + 4, offsetY + 4); + } + + _update(window) { + if (window.is_fullscreen()) { + this.hide(); + } else { + this.show(); + this._updatePosition(window); + } + } + + _resendEvent(event) { + // Window actors receive events on a different path (via an event filter) + // than other actors on the stage. By setting (reactive = false) on the + // RealmWindowLabel and resending the event, it will be processed by the + // window actor. Afterward we set back (reactive = true). + this.set_reactive(false); + event.put(); + this.set_reactive(true); + } + + _resetTimer() { + if (this._timeoutId) { + GLib.source_remove(this._timeoutId); + this._timeoutId = 0; + } + this._timeoutId = GLib.timeout_add(GLib.PRIORITY_DEFAULT, LABEL_FADE_TIMEOUT, () => { + this._timeoutId = 0; + this._restoreDimmedLabel(); + return GLib.SOURCE_REMOVE; + }); + } + + vfunc_enter_event(event) { + this._resendEvent(event); + super.vfunc_enter_event(event); + this._setDimmedLabel(); + return Clutter.EVENT_PROPAGATE; + } + + vfunc_leave_event(event) { + super.vfunc_leave_event(event); + this._restoreDimmedLabel(); + return Clutter.EVENT_PROPAGATE; + } + + vfunc_motion_event(event) { + this._resendEvent(event); + this._setDimmedLabel(); + return Clutter.EVENT_PROPAGATE; + } + + vfunc_button_press_event(event) { + this._resendEvent(event); + return Clutter.EVENT_PROPAGATE; + } + + vfunc_button_release_event(event) { + this._resendEvent(event); + return Clutter.EVENT_PROPAGATE; + } + }) diff --git a/js/ui/realms/realmManager.js b/js/ui/realms/realmManager.js index 03775d666..3c1e6e952 100644 --- a/js/ui/realms/realmManager.js +++ b/js/ui/realms/realmManager.js @@ -1,15 +1,11 @@ -import Clutter from 'gi://Clutter' import Gio from 'gi://Gio' import Meta from 'gi://Meta' import Shell from 'gi://Shell' -import St from 'gi://St' import * as Main from '../main.js'; import * as RealmIndicator from './realmIndicator.js'; import * as RealmSwitcher from './realmSwitcher.js'; -import * as Lightbox from '../lightbox.js'; -import * as RealmSearchProvider from './realmSearchProvider.js'; -import * as RealmWindowFrame from './realmWindowFrame.js'; +import * as RealmLabels from './realmLabels.js'; export const RealmManager = class { constructor() { @@ -39,17 +35,20 @@ export const RealmManager = class { }); this._switchAnimation = new RealmSwitcher.ContextSwitchAnimationController(this._realmIndicator); + this.labelManager = new RealmLabels.RealmLabelManager(); + } - if (Main.overview._overview) { - this._searchResults = Main.overview._overview.controls._searchController._searchResults; - this._searchProvider = new RealmSearchProvider.RealmSearchProvider(); - this._searchProvider.createResultDisplay(this._searchResults); - this._searchResults._registerProvider(this._searchProvider); + createRealmLabelForApp(app) { + let realmName = app.get_realm_name(); + if (this.labelManager.appIconLabelsEnabled() && realmName) { + return this.labelManager.createAppIconLabelForRealm(realmName); } else { - log("Not creating search provider because Main.overview._overview does not exist"); + return null; } + } - this._frameManager = new RealmWindowFrame.WindowFrameManager(); + appIconLabelsEnabled() { + return this.labelManager.appIconLabelsEnabled(); } animateSwitch(from, to, onComplete) { @@ -61,5 +60,4 @@ export const RealmManager = class { if (!popup.show(binding.is_reversed(), binding.get_name(), binding.get_mask())) popup.fadeAndDestroy(); } - }; diff --git a/js/ui/realms/realmWindowFrame.js b/js/ui/realms/realmWindowFrame.js deleted file mode 100644 index 0e2294b23..000000000 --- a/js/ui/realms/realmWindowFrame.js +++ /dev/null @@ -1,332 +0,0 @@ -import Clutter from 'gi://Clutter'; -import Cogl from 'gi://Cogl'; -import GObject from 'gi://GObject'; -import Meta from 'gi://Meta'; -import Shell from 'gi://Shell'; -import St from 'gi://St'; - -export const 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; - } - } -} - -const 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; - this._label_text = label_text; - - if (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_text) { - return; - } - - if (window.is_fullscreen()) { - if (this._label) { - let actor = window.get_compositor_private(); - actor.remove_child(this._label); - this._label = null; - } - } else if (!this._label) { - let actor = window.get_compositor_private(); - this._label = this._createLabel(actor, this._label_text); - } - - 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_4f( - this._color.red / 255.0, - this._color.green / 255.0, - this._color.blue / 255.0, - 0xc4 / 255.0); - 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 = Cogl.Pipeline.new(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 index fe6b2dca3..52fe779a1 100644 --- a/js/ui/realms/realmWindowMenu.js +++ b/js/ui/realms/realmWindowMenu.js @@ -1,7 +1,7 @@ -import GObject from 'gi://GObject'; import Shell from 'gi://Shell'; import * as PopupMenu from '../popupMenu.js'; +import * as Main from '../main.js'; function _windowAppId(window) { const tracker = Shell.WindowTracker.get_default(); @@ -79,22 +79,20 @@ function windowContextRealmName(window) { } } -export function enableFrameItem(window) { - const realms = Shell.Realms.get_default(); - const frames = realms.window_frames(); - if (!frames.has_frame(window)) { +export function enableLabelItem(window) { + const labelManager = Main.realmManager.labelManager; + + if (!labelManager.windowNeedsLabel(window)) { return null; } - let enabled = frames.is_frame_enabled(window); - let item = new PopupMenu.PopupMenuItem("Display colored window frame"); + let enabled = labelManager.getWindowLabelEnabled(window); + let item = new PopupMenu.PopupMenuItem("Display realm label on window"); 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); + Main.realmManager.labelManager.setWindowLabelEnabled(window, !enabled); }); return item; diff --git a/js/ui/windowMenu.js b/js/ui/windowMenu.js index 723b3d769..f65f667bc 100644 --- a/js/ui/windowMenu.js +++ b/js/ui/windowMenu.js @@ -31,7 +31,7 @@ export class WindowMenu extends PopupMenu.PopupMenu { this.addMenuItem(new PopupMenu.PopupSeparatorMenuItem(s)); - item = RealmWindowMenu.enableFrameItem(window); + item = RealmWindowMenu.enableLabelItem(window); if (item) { this.addMenuItem(item); } diff --git a/src/meson.build b/src/meson.build index 56ab7ca69..d9debd4cf 100644 --- a/src/meson.build +++ b/src/meson.build @@ -160,8 +160,6 @@ libshell_sources = [ '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', diff --git a/src/shell-app.c b/src/shell-app.c index 3b5df0aab..14d148f5a 100644 --- a/src/shell-app.c +++ b/src/shell-app.c @@ -83,6 +83,8 @@ struct _ShellApp ShellAppRunningState *running_state; + char *realm_name; + char *window_id_string; char *name_collation_key; }; @@ -177,6 +179,43 @@ shell_app_get_id (ShellApp *app) return app->window_id_string; } + + +static char * +shell_app_realm_name_from_app (ShellApp *app) +{ + + if (!app->info) { + return g_strdup("??"); + } + const char *app_id = g_app_info_get_id (G_APP_INFO (app->info)); + + if (!app_id) { + return g_strdup("??"); + } + + char *realm_name = NULL; + char **split = g_strsplit(app_id, ".", 2); + if (g_str_has_prefix(split[0], "realm-")) { + realm_name = g_strdup(split[0] + 6); + } else { + realm_name = g_strdup("Citadel"); + } + g_strfreev(split); + return realm_name; +} + +const char * +shell_app_get_realm_name (ShellApp *app) +{ + + if(!app->realm_name) { + app->realm_name = shell_app_realm_name_from_app(app); + } + + return app->realm_name; +} + static MetaWindow * window_backed_app_get_window (ShellApp *app) { @@ -894,6 +933,7 @@ _shell_app_set_app_info (ShellApp *app, g_set_object (&app->info, info); g_clear_pointer (&app->name_collation_key, g_free); + g_clear_pointer (&app->realm_name, g_free); if (app->info) app->name_collation_key = g_utf8_collate_key (shell_app_get_name (app), -1); } @@ -1781,6 +1821,8 @@ shell_app_finalize (GObject *object) { ShellApp *app = SHELL_APP (object); + g_free(app->realm_name); + g_free (app->window_id_string); g_free (app->name_collation_key); diff --git a/src/shell-app.h b/src/shell-app.h index a3e087eae..de20f110b 100644 --- a/src/shell-app.h +++ b/src/shell-app.h @@ -26,6 +26,8 @@ typedef enum { const char *shell_app_get_id (ShellApp *app); +const char *shell_app_get_realm_name (ShellApp *app); + GDesktopAppInfo *shell_app_get_app_info (ShellApp *app); ClutterActor *shell_app_create_icon_texture (ShellApp *app, int size); diff --git a/src/shell-realms-private.h b/src/shell-realms-private.h index 466a82267..168ee8e15 100644 --- a/src/shell-realms-private.h +++ b/src/shell-realms-private.h @@ -2,7 +2,6 @@ #define __SHELL_REALMS_PRIVATE_H__ #include #include "shell-realms.h" -#include "shell-realms-window-frames.h" #define CITADEL_REALM_TAG "[CITADEL]" diff --git a/src/shell-realms-window-frames.c b/src/shell-realms-window-frames.c deleted file mode 100644 index d2463e6ae..000000000 --- a/src/shell-realms-window-frames.c +++ /dev/null @@ -1,390 +0,0 @@ -#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; - ShellRealms *realms; - 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 }; - -ShellRealmsWindowFrames * -shell_realms_window_frames_new (ShellRealms *realms) -{ - ShellRealmsWindowFrames *frames; - - frames = g_object_new (SHELL_TYPE_REALMS_WINDOW_FRAMES, NULL); - - frames->realms = realms; - return frames; -} - -static void -shell_realms_window_frames_process_color (ShellRealmsWindowFrames *frames, const char *entry) -{ - ClutterColor *rgba = clutter_color_alloc(); - - gchar **split = g_strsplit (entry, ":", -1); - - if (g_strv_length (split) != 2) { - g_warning ("ShellRealmsWindowFrames: Unable to parse realm-frame-colors entry: %s", entry); - clutter_color_free(rgba); - g_strfreev (split); - return; - } - - if (!clutter_color_from_string(rgba, split[1])) { - g_warning ("ShellRealmsWindowFrames: Failed to parse RGBA component of realm frame color entry: %s", entry); - clutter_color_free(rgba); - } else { - g_hash_table_insert (frames->realm_frame_colors, g_strdup (split[0]), 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; - ClutterColor *rgba = clutter_color_alloc(); - - entries = g_settings_get_strv (frames->settings, FRAME_COLOR_LIST_KEY); - n_entries = g_strv_length (entries); - - g_clear_list (&frames->default_colors, (GDestroyNotify) clutter_color_free); - - for (i = 0; i < n_entries; i++) { - if (clutter_color_from_string(rgba, entries[i])) { - frames->default_colors = g_list_append (frames->default_colors, clutter_color_copy(rgba)); - } - } - clutter_color_free(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) clutter_color_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) clutter_color_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 clutter_color_equal(c1, c2) ? 0 : 1; -} - -static ClutterColor * -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) { - return clutter_color_new(153, 193, 241, REALM_FRAME_ALPHA); - } - - // 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) { - ClutterColor *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; - ClutterColor *rgba = value; - char *rgba_str = clutter_color_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, MetaWindow *window) -{ - - ShellRealmItem *realm = shell_realms_realm_by_window (frames->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) && shell_realms_is_foreign_window (frames->realms, 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); - } -} - -/** - * 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; - } - - const gchar *name = shell_realms_window_frames_realm_name_for_window (frames, window); - - if (name == NULL) { - return NULL; - } - - ClutterColor *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); - } - ClutterColor *copy = clutter_color_copy(rgba); - copy->alpha = REALM_FRAME_ALPHA; - return copy; -} - -/** - * 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; - } - - if (shell_realms_is_citadel_window (frames->realms, window)) { - return "Citadel"; - } else { - return shell_realms_window_frames_realm_name_for_window (frames, window); - } -} diff --git a/src/shell-realms-window-frames.h b/src/shell-realms-window-frames.h deleted file mode 100644 index 4a36eebc4..000000000 --- a/src/shell-realms-window-frames.h +++ /dev/null @@ -1,21 +0,0 @@ -#ifndef __SHELL_REALMS_WINDOW_FRAMES_H__ -#define __SHELL_REALMS_WINDOW_FRAMES_H__ - -#include -#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) - -typedef struct _ShellRealms ShellRealms; - -ShellRealmsWindowFrames *shell_realms_window_frames_new (ShellRealms *realms); -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 index 4561fe340..b17c84ae5 100644 --- a/src/shell-realms.c +++ b/src/shell-realms.c @@ -11,7 +11,6 @@ struct _ShellRealms { GHashTable *realms; GList *running_realms; ShellRealmItem *current_realm; - ShellRealmsWindowFrames *frames; MetaWorkspaceContext *detached_context; }; @@ -185,20 +184,6 @@ 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) { @@ -597,8 +582,6 @@ shell_realms_finalize (GObject *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); } diff --git a/src/shell-realms.h b/src/shell-realms.h index 7e4759629..ef7fd97ff 100644 --- a/src/shell-realms.h +++ b/src/shell-realms.h @@ -4,7 +4,6 @@ #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) @@ -19,8 +18,6 @@ ShellRealmItem *shell_realms_realm_by_window (ShellRealms *realms, MetaWindow *w gboolean shell_realms_is_citadel_window (ShellRealms *realms, MetaWindow *window); gboolean shell_realms_is_foreign_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);