e6414d8afc
Re-apply rebased and updated version of the RHEL 9 downstream patches, with some exceptions: Branding is still TBD, so has been left out for now. The desktop-icons extension will be replaced by an upstreamed version of desktop-icons-ng, which is still work-in-progress. Both dash-to-dock and dash-to-panel will be moved to separate packages, based on the existing Fedora package. It was decided to drop the panel-favorites and updates-dialog extensions. Resolves: RHEL-34255
943 lines
31 KiB
Diff
943 lines
31 KiB
Diff
From 1f0681875eefd09df28630a74aabd1bf47f90dab Mon Sep 17 00:00:00 2001
|
|
From: =?UTF-8?q?Florian=20M=C3=BCllner?= <fmuellner@gnome.org>
|
|
Date: Wed, 21 Feb 2024 12:38:33 +0100
|
|
Subject: [PATCH 01/28] workspace-indicator: Move indicator code into separate
|
|
file
|
|
|
|
Shortly after the window-list extension was added, it gained a
|
|
workspace switcher based on the workspace indicator extension.
|
|
|
|
Duplicating the code wasn't a big issue while the switcher was
|
|
a simple menu, but since it gained previews with a fair bit of
|
|
custom styling, syncing changes between the two extensions has
|
|
become tedious, in particular as the two copies have slightly
|
|
diverged over time.
|
|
|
|
In order to allow the two copies to converge again, the indicator
|
|
code needs to be separate from the extension boilerplate, so
|
|
split out the code into a separate module.
|
|
|
|
Part-of: <https://gitlab.gnome.org/GNOME/gnome-shell-extensions/-/merge_requests/307>
|
|
---
|
|
extensions/workspace-indicator/extension.js | 432 +----------------
|
|
extensions/workspace-indicator/meson.build | 2 +-
|
|
.../workspace-indicator/workspaceIndicator.js | 438 ++++++++++++++++++
|
|
po/POTFILES.in | 2 +-
|
|
4 files changed, 442 insertions(+), 432 deletions(-)
|
|
create mode 100644 extensions/workspace-indicator/workspaceIndicator.js
|
|
|
|
diff --git a/extensions/workspace-indicator/extension.js b/extensions/workspace-indicator/extension.js
|
|
index f9c7dd9a..b383c919 100644
|
|
--- a/extensions/workspace-indicator/extension.js
|
|
+++ b/extensions/workspace-indicator/extension.js
|
|
@@ -4,439 +4,11 @@
|
|
//
|
|
// SPDX-License-Identifier: GPL-2.0-or-later
|
|
|
|
-// -*- mode: js2; indent-tabs-mode: nil; js2-basic-offset: 4 -*-
|
|
-import Clutter from 'gi://Clutter';
|
|
-import Gio from 'gi://Gio';
|
|
-import GObject from 'gi://GObject';
|
|
-import Meta from 'gi://Meta';
|
|
-import St from 'gi://St';
|
|
+import {Extension} from 'resource:///org/gnome/shell/extensions/extension.js';
|
|
|
|
-import {Extension, gettext as _} from 'resource:///org/gnome/shell/extensions/extension.js';
|
|
-
|
|
-import * as DND from 'resource:///org/gnome/shell/ui/dnd.js';
|
|
import * as Main from 'resource:///org/gnome/shell/ui/main.js';
|
|
-import * as PanelMenu from 'resource:///org/gnome/shell/ui/panelMenu.js';
|
|
-import * as PopupMenu from 'resource:///org/gnome/shell/ui/popupMenu.js';
|
|
-
|
|
-const WORKSPACE_SCHEMA = 'org.gnome.desktop.wm.preferences';
|
|
-const WORKSPACE_KEY = 'workspace-names';
|
|
-
|
|
-const TOOLTIP_OFFSET = 6;
|
|
-const TOOLTIP_ANIMATION_TIME = 150;
|
|
-
|
|
-const MAX_THUMBNAILS = 6;
|
|
-
|
|
-class WindowPreview extends St.Button {
|
|
- static {
|
|
- GObject.registerClass(this);
|
|
- }
|
|
-
|
|
- constructor(window) {
|
|
- super({
|
|
- style_class: 'workspace-indicator-window-preview',
|
|
- });
|
|
-
|
|
- this._delegate = this;
|
|
- DND.makeDraggable(this, {restoreOnSuccess: true});
|
|
-
|
|
- this._window = window;
|
|
-
|
|
- this._window.connectObject(
|
|
- 'size-changed', () => this._checkRelayout(),
|
|
- 'position-changed', () => this._checkRelayout(),
|
|
- 'notify::minimized', this._updateVisible.bind(this),
|
|
- 'notify::window-type', this._updateVisible.bind(this),
|
|
- this);
|
|
- this._updateVisible();
|
|
-
|
|
- global.display.connectObject('notify::focus-window',
|
|
- this._onFocusChanged.bind(this), this);
|
|
- this._onFocusChanged();
|
|
- }
|
|
-
|
|
- // needed for DND
|
|
- get metaWindow() {
|
|
- return this._window;
|
|
- }
|
|
-
|
|
- _onFocusChanged() {
|
|
- if (global.display.focus_window === this._window)
|
|
- this.add_style_class_name('active');
|
|
- else
|
|
- this.remove_style_class_name('active');
|
|
- }
|
|
-
|
|
- _checkRelayout() {
|
|
- const monitor = Main.layoutManager.findIndexForActor(this);
|
|
- const workArea = Main.layoutManager.getWorkAreaForMonitor(monitor);
|
|
- if (this._window.get_frame_rect().overlap(workArea))
|
|
- this.queue_relayout();
|
|
- }
|
|
-
|
|
- _updateVisible() {
|
|
- this.visible = this._window.window_type !== Meta.WindowType.DESKTOP &&
|
|
- this._window.showing_on_its_workspace();
|
|
- }
|
|
-}
|
|
-
|
|
-class WorkspaceLayout extends Clutter.LayoutManager {
|
|
- static {
|
|
- GObject.registerClass(this);
|
|
- }
|
|
-
|
|
- vfunc_get_preferred_width() {
|
|
- return [0, 0];
|
|
- }
|
|
-
|
|
- vfunc_get_preferred_height() {
|
|
- return [0, 0];
|
|
- }
|
|
-
|
|
- vfunc_allocate(container, box) {
|
|
- const monitor = Main.layoutManager.findIndexForActor(container);
|
|
- const workArea = Main.layoutManager.getWorkAreaForMonitor(monitor);
|
|
- const hscale = box.get_width() / workArea.width;
|
|
- const vscale = box.get_height() / workArea.height;
|
|
-
|
|
- for (const child of container) {
|
|
- const childBox = new Clutter.ActorBox();
|
|
- const frameRect = child.metaWindow.get_frame_rect();
|
|
- childBox.set_size(
|
|
- Math.round(Math.min(frameRect.width, workArea.width) * hscale),
|
|
- Math.round(Math.min(frameRect.height, workArea.height) * vscale));
|
|
- childBox.set_origin(
|
|
- Math.round((frameRect.x - workArea.x) * hscale),
|
|
- Math.round((frameRect.y - workArea.y) * vscale));
|
|
- child.allocate(childBox);
|
|
- }
|
|
- }
|
|
-}
|
|
-
|
|
-class WorkspaceThumbnail extends St.Button {
|
|
- static {
|
|
- GObject.registerClass(this);
|
|
- }
|
|
-
|
|
- constructor(index) {
|
|
- super({
|
|
- style_class: 'workspace',
|
|
- child: new Clutter.Actor({
|
|
- layout_manager: new WorkspaceLayout(),
|
|
- clip_to_allocation: true,
|
|
- x_expand: true,
|
|
- y_expand: true,
|
|
- }),
|
|
- });
|
|
-
|
|
- this._tooltip = new St.Label({
|
|
- style_class: 'dash-label',
|
|
- visible: false,
|
|
- });
|
|
- Main.uiGroup.add_child(this._tooltip);
|
|
-
|
|
- this.connect('destroy', this._onDestroy.bind(this));
|
|
- this.connect('notify::hover', this._syncTooltip.bind(this));
|
|
-
|
|
- this._index = index;
|
|
- this._delegate = this; // needed for DND
|
|
-
|
|
- this._windowPreviews = new Map();
|
|
-
|
|
- let workspaceManager = global.workspace_manager;
|
|
- this._workspace = workspaceManager.get_workspace_by_index(index);
|
|
-
|
|
- this._workspace.connectObject(
|
|
- 'window-added', (ws, window) => this._addWindow(window),
|
|
- 'window-removed', (ws, window) => this._removeWindow(window),
|
|
- this);
|
|
-
|
|
- global.display.connectObject('restacked',
|
|
- this._onRestacked.bind(this), this);
|
|
-
|
|
- this._workspace.list_windows().forEach(w => this._addWindow(w));
|
|
- this._onRestacked();
|
|
- }
|
|
-
|
|
- acceptDrop(source) {
|
|
- if (!source.metaWindow)
|
|
- return false;
|
|
-
|
|
- this._moveWindow(source.metaWindow);
|
|
- return true;
|
|
- }
|
|
-
|
|
- handleDragOver(source) {
|
|
- if (source.metaWindow)
|
|
- return DND.DragMotionResult.MOVE_DROP;
|
|
- else
|
|
- return DND.DragMotionResult.CONTINUE;
|
|
- }
|
|
-
|
|
- _addWindow(window) {
|
|
- if (this._windowPreviews.has(window))
|
|
- return;
|
|
-
|
|
- let preview = new WindowPreview(window);
|
|
- preview.connect('clicked', (a, btn) => this.emit('clicked', btn));
|
|
- this._windowPreviews.set(window, preview);
|
|
- this.child.add_child(preview);
|
|
- }
|
|
-
|
|
- _removeWindow(window) {
|
|
- let preview = this._windowPreviews.get(window);
|
|
- if (!preview)
|
|
- return;
|
|
-
|
|
- this._windowPreviews.delete(window);
|
|
- preview.destroy();
|
|
- }
|
|
-
|
|
- _onRestacked() {
|
|
- let lastPreview = null;
|
|
- let windows = global.get_window_actors().map(a => a.meta_window);
|
|
- for (let i = 0; i < windows.length; i++) {
|
|
- let preview = this._windowPreviews.get(windows[i]);
|
|
- if (!preview)
|
|
- continue;
|
|
-
|
|
- this.child.set_child_above_sibling(preview, lastPreview);
|
|
- lastPreview = preview;
|
|
- }
|
|
- }
|
|
-
|
|
- _moveWindow(window) {
|
|
- let monitorIndex = Main.layoutManager.findIndexForActor(this);
|
|
- if (monitorIndex !== window.get_monitor())
|
|
- window.move_to_monitor(monitorIndex);
|
|
- window.change_workspace_by_index(this._index, false);
|
|
- }
|
|
-
|
|
- on_clicked() {
|
|
- let ws = global.workspace_manager.get_workspace_by_index(this._index);
|
|
- if (ws)
|
|
- ws.activate(global.get_current_time());
|
|
- }
|
|
-
|
|
- _syncTooltip() {
|
|
- if (this.hover) {
|
|
- this._tooltip.set({
|
|
- text: Meta.prefs_get_workspace_name(this._index),
|
|
- visible: true,
|
|
- opacity: 0,
|
|
- });
|
|
-
|
|
- const [stageX, stageY] = this.get_transformed_position();
|
|
- const thumbWidth = this.allocation.get_width();
|
|
- const thumbHeight = this.allocation.get_height();
|
|
- const tipWidth = this._tooltip.width;
|
|
- const xOffset = Math.floor((thumbWidth - tipWidth) / 2);
|
|
- const monitor = Main.layoutManager.findMonitorForActor(this);
|
|
- const x = Math.clamp(
|
|
- stageX + xOffset,
|
|
- monitor.x,
|
|
- monitor.x + monitor.width - tipWidth);
|
|
- const y = stageY + thumbHeight + TOOLTIP_OFFSET;
|
|
- this._tooltip.set_position(x, y);
|
|
- }
|
|
-
|
|
- this._tooltip.ease({
|
|
- opacity: this.hover ? 255 : 0,
|
|
- duration: TOOLTIP_ANIMATION_TIME,
|
|
- mode: Clutter.AnimationMode.EASE_OUT_QUAD,
|
|
- onComplete: () => (this._tooltip.visible = this.hover),
|
|
- });
|
|
- }
|
|
-
|
|
- _onDestroy() {
|
|
- this._tooltip.destroy();
|
|
- }
|
|
-}
|
|
-
|
|
-class WorkspaceIndicator extends PanelMenu.Button {
|
|
- static {
|
|
- GObject.registerClass(this);
|
|
- }
|
|
-
|
|
- constructor() {
|
|
- super(0.5, _('Workspace Indicator'));
|
|
-
|
|
- let container = new St.Widget({
|
|
- layout_manager: new Clutter.BinLayout(),
|
|
- x_expand: true,
|
|
- y_expand: true,
|
|
- });
|
|
- this.add_child(container);
|
|
-
|
|
- let workspaceManager = global.workspace_manager;
|
|
-
|
|
- this._currentWorkspace = workspaceManager.get_active_workspace_index();
|
|
- this._statusLabel = new St.Label({
|
|
- style_class: 'panel-workspace-indicator',
|
|
- y_align: Clutter.ActorAlign.CENTER,
|
|
- text: this._labelText(),
|
|
- });
|
|
-
|
|
- container.add_child(this._statusLabel);
|
|
-
|
|
- this._thumbnailsBox = new St.BoxLayout({
|
|
- style_class: 'panel-workspace-indicator-box',
|
|
- y_expand: true,
|
|
- reactive: true,
|
|
- });
|
|
-
|
|
- container.add_child(this._thumbnailsBox);
|
|
-
|
|
- this._workspacesItems = [];
|
|
- this._workspaceSection = new PopupMenu.PopupMenuSection();
|
|
- this.menu.addMenuItem(this._workspaceSection);
|
|
-
|
|
- workspaceManager.connectObject(
|
|
- 'notify::n-workspaces', this._nWorkspacesChanged.bind(this), GObject.ConnectFlags.AFTER,
|
|
- 'workspace-switched', this._onWorkspaceSwitched.bind(this), GObject.ConnectFlags.AFTER,
|
|
- 'notify::layout-rows', this._updateThumbnailVisibility.bind(this),
|
|
- this);
|
|
-
|
|
- this.connect('scroll-event', this._onScrollEvent.bind(this));
|
|
- this._thumbnailsBox.connect('scroll-event', this._onScrollEvent.bind(this));
|
|
- this._createWorkspacesSection();
|
|
- this._updateThumbnails();
|
|
- this._updateThumbnailVisibility();
|
|
-
|
|
- this._settings = new Gio.Settings({schema_id: WORKSPACE_SCHEMA});
|
|
- this._settings.connectObject(`changed::${WORKSPACE_KEY}`,
|
|
- this._updateMenuLabels.bind(this), this);
|
|
- }
|
|
-
|
|
- _onDestroy() {
|
|
- Main.panel.set_offscreen_redirect(Clutter.OffscreenRedirect.ALWAYS);
|
|
-
|
|
- super._onDestroy();
|
|
- }
|
|
-
|
|
- _updateThumbnailVisibility() {
|
|
- const {workspaceManager} = global;
|
|
- const vertical = workspaceManager.layout_rows === -1;
|
|
- const useMenu =
|
|
- vertical || workspaceManager.n_workspaces > MAX_THUMBNAILS;
|
|
- this.reactive = useMenu;
|
|
-
|
|
- this._statusLabel.visible = useMenu;
|
|
- this._thumbnailsBox.visible = !useMenu;
|
|
-
|
|
- // Disable offscreen-redirect when showing the workspace switcher
|
|
- // so that clip-to-allocation works
|
|
- Main.panel.set_offscreen_redirect(useMenu
|
|
- ? Clutter.OffscreenRedirect.ALWAYS
|
|
- : Clutter.OffscreenRedirect.AUTOMATIC_FOR_OPACITY);
|
|
- }
|
|
-
|
|
- _onWorkspaceSwitched() {
|
|
- this._currentWorkspace = global.workspace_manager.get_active_workspace_index();
|
|
|
|
- this._updateMenuOrnament();
|
|
- this._updateActiveThumbnail();
|
|
-
|
|
- this._statusLabel.set_text(this._labelText());
|
|
- }
|
|
-
|
|
- _nWorkspacesChanged() {
|
|
- this._createWorkspacesSection();
|
|
- this._updateThumbnails();
|
|
- this._updateThumbnailVisibility();
|
|
- }
|
|
-
|
|
- _updateMenuOrnament() {
|
|
- for (let i = 0; i < this._workspacesItems.length; i++) {
|
|
- this._workspacesItems[i].setOrnament(i === this._currentWorkspace
|
|
- ? PopupMenu.Ornament.DOT
|
|
- : PopupMenu.Ornament.NO_DOT);
|
|
- }
|
|
- }
|
|
-
|
|
- _updateActiveThumbnail() {
|
|
- let thumbs = this._thumbnailsBox.get_children();
|
|
- for (let i = 0; i < thumbs.length; i++) {
|
|
- if (i === this._currentWorkspace)
|
|
- thumbs[i].add_style_class_name('active');
|
|
- else
|
|
- thumbs[i].remove_style_class_name('active');
|
|
- }
|
|
- }
|
|
-
|
|
- _labelText(workspaceIndex) {
|
|
- if (workspaceIndex === undefined) {
|
|
- workspaceIndex = this._currentWorkspace;
|
|
- return (workspaceIndex + 1).toString();
|
|
- }
|
|
- return Meta.prefs_get_workspace_name(workspaceIndex);
|
|
- }
|
|
-
|
|
- _updateMenuLabels() {
|
|
- for (let i = 0; i < this._workspacesItems.length; i++)
|
|
- this._workspacesItems[i].label.text = this._labelText(i);
|
|
- }
|
|
-
|
|
- _createWorkspacesSection() {
|
|
- let workspaceManager = global.workspace_manager;
|
|
-
|
|
- this._workspaceSection.removeAll();
|
|
- this._workspacesItems = [];
|
|
- this._currentWorkspace = workspaceManager.get_active_workspace_index();
|
|
-
|
|
- let i = 0;
|
|
- for (; i < workspaceManager.n_workspaces; i++) {
|
|
- this._workspacesItems[i] = new PopupMenu.PopupMenuItem(this._labelText(i));
|
|
- this._workspaceSection.addMenuItem(this._workspacesItems[i]);
|
|
- this._workspacesItems[i].workspaceId = i;
|
|
- this._workspacesItems[i].label_actor = this._statusLabel;
|
|
- this._workspacesItems[i].connect('activate', (actor, _event) => {
|
|
- this._activate(actor.workspaceId);
|
|
- });
|
|
-
|
|
- this._workspacesItems[i].setOrnament(i === this._currentWorkspace
|
|
- ? PopupMenu.Ornament.DOT
|
|
- : PopupMenu.Ornament.NO_DOT);
|
|
- }
|
|
-
|
|
- this._statusLabel.set_text(this._labelText());
|
|
- }
|
|
-
|
|
- _updateThumbnails() {
|
|
- let workspaceManager = global.workspace_manager;
|
|
-
|
|
- this._thumbnailsBox.destroy_all_children();
|
|
-
|
|
- for (let i = 0; i < workspaceManager.n_workspaces; i++) {
|
|
- let thumb = new WorkspaceThumbnail(i);
|
|
- this._thumbnailsBox.add_child(thumb);
|
|
- }
|
|
- this._updateActiveThumbnail();
|
|
- }
|
|
-
|
|
- _activate(index) {
|
|
- let workspaceManager = global.workspace_manager;
|
|
-
|
|
- if (index >= 0 && index < workspaceManager.n_workspaces) {
|
|
- let metaWorkspace = workspaceManager.get_workspace_by_index(index);
|
|
- metaWorkspace.activate(global.get_current_time());
|
|
- }
|
|
- }
|
|
-
|
|
- _onScrollEvent(actor, event) {
|
|
- let direction = event.get_scroll_direction();
|
|
- let diff = 0;
|
|
- if (direction === Clutter.ScrollDirection.DOWN)
|
|
- diff = 1;
|
|
- else if (direction === Clutter.ScrollDirection.UP)
|
|
- diff = -1;
|
|
- else
|
|
- return;
|
|
-
|
|
-
|
|
- let newIndex = global.workspace_manager.get_active_workspace_index() + diff;
|
|
- this._activate(newIndex);
|
|
- }
|
|
-}
|
|
+import {WorkspaceIndicator} from './workspaceIndicator.js';
|
|
|
|
export default class WorkspaceIndicatorExtension extends Extension {
|
|
enable() {
|
|
diff --git a/extensions/workspace-indicator/meson.build b/extensions/workspace-indicator/meson.build
|
|
index 36daa535..6dd08dae 100644
|
|
--- a/extensions/workspace-indicator/meson.build
|
|
+++ b/extensions/workspace-indicator/meson.build
|
|
@@ -9,4 +9,4 @@ extension_data += configure_file(
|
|
)
|
|
extension_data += files('stylesheet.css')
|
|
|
|
-extension_sources += files('prefs.js')
|
|
+extension_sources += files('prefs.js', 'workspaceIndicator.js')
|
|
diff --git a/extensions/workspace-indicator/workspaceIndicator.js b/extensions/workspace-indicator/workspaceIndicator.js
|
|
new file mode 100644
|
|
index 00000000..6b0903d5
|
|
--- /dev/null
|
|
+++ b/extensions/workspace-indicator/workspaceIndicator.js
|
|
@@ -0,0 +1,438 @@
|
|
+// SPDX-FileCopyrightText: 2011 Erick Pérez Castellanos <erick.red@gmail.com>
|
|
+// SPDX-FileCopyrightText: 2011 Giovanni Campagna <gcampagna@src.gnome.org>
|
|
+// SPDX-FileCopyrightText: 2017 Florian Müllner <fmuellner@gnome.org>
|
|
+//
|
|
+// SPDX-License-Identifier: GPL-2.0-or-later
|
|
+
|
|
+import Clutter from 'gi://Clutter';
|
|
+import Gio from 'gi://Gio';
|
|
+import GObject from 'gi://GObject';
|
|
+import Meta from 'gi://Meta';
|
|
+import St from 'gi://St';
|
|
+
|
|
+import {gettext as _} from 'resource:///org/gnome/shell/extensions/extension.js';
|
|
+
|
|
+import * as DND from 'resource:///org/gnome/shell/ui/dnd.js';
|
|
+import * as Main from 'resource:///org/gnome/shell/ui/main.js';
|
|
+import * as PanelMenu from 'resource:///org/gnome/shell/ui/panelMenu.js';
|
|
+import * as PopupMenu from 'resource:///org/gnome/shell/ui/popupMenu.js';
|
|
+
|
|
+const WORKSPACE_SCHEMA = 'org.gnome.desktop.wm.preferences';
|
|
+const WORKSPACE_KEY = 'workspace-names';
|
|
+
|
|
+const TOOLTIP_OFFSET = 6;
|
|
+const TOOLTIP_ANIMATION_TIME = 150;
|
|
+
|
|
+const MAX_THUMBNAILS = 6;
|
|
+
|
|
+class WindowPreview extends St.Button {
|
|
+ static {
|
|
+ GObject.registerClass(this);
|
|
+ }
|
|
+
|
|
+ constructor(window) {
|
|
+ super({
|
|
+ style_class: 'workspace-indicator-window-preview',
|
|
+ });
|
|
+
|
|
+ this._delegate = this;
|
|
+ DND.makeDraggable(this, {restoreOnSuccess: true});
|
|
+
|
|
+ this._window = window;
|
|
+
|
|
+ this._window.connectObject(
|
|
+ 'size-changed', () => this._checkRelayout(),
|
|
+ 'position-changed', () => this._checkRelayout(),
|
|
+ 'notify::minimized', this._updateVisible.bind(this),
|
|
+ 'notify::window-type', this._updateVisible.bind(this),
|
|
+ this);
|
|
+ this._updateVisible();
|
|
+
|
|
+ global.display.connectObject('notify::focus-window',
|
|
+ this._onFocusChanged.bind(this), this);
|
|
+ this._onFocusChanged();
|
|
+ }
|
|
+
|
|
+ // needed for DND
|
|
+ get metaWindow() {
|
|
+ return this._window;
|
|
+ }
|
|
+
|
|
+ _onFocusChanged() {
|
|
+ if (global.display.focus_window === this._window)
|
|
+ this.add_style_class_name('active');
|
|
+ else
|
|
+ this.remove_style_class_name('active');
|
|
+ }
|
|
+
|
|
+ _checkRelayout() {
|
|
+ const monitor = Main.layoutManager.findIndexForActor(this);
|
|
+ const workArea = Main.layoutManager.getWorkAreaForMonitor(monitor);
|
|
+ if (this._window.get_frame_rect().overlap(workArea))
|
|
+ this.queue_relayout();
|
|
+ }
|
|
+
|
|
+ _updateVisible() {
|
|
+ this.visible = this._window.window_type !== Meta.WindowType.DESKTOP &&
|
|
+ this._window.showing_on_its_workspace();
|
|
+ }
|
|
+}
|
|
+
|
|
+class WorkspaceLayout extends Clutter.LayoutManager {
|
|
+ static {
|
|
+ GObject.registerClass(this);
|
|
+ }
|
|
+
|
|
+ vfunc_get_preferred_width() {
|
|
+ return [0, 0];
|
|
+ }
|
|
+
|
|
+ vfunc_get_preferred_height() {
|
|
+ return [0, 0];
|
|
+ }
|
|
+
|
|
+ vfunc_allocate(container, box) {
|
|
+ const monitor = Main.layoutManager.findIndexForActor(container);
|
|
+ const workArea = Main.layoutManager.getWorkAreaForMonitor(monitor);
|
|
+ const hscale = box.get_width() / workArea.width;
|
|
+ const vscale = box.get_height() / workArea.height;
|
|
+
|
|
+ for (const child of container) {
|
|
+ const childBox = new Clutter.ActorBox();
|
|
+ const frameRect = child.metaWindow.get_frame_rect();
|
|
+ childBox.set_size(
|
|
+ Math.round(Math.min(frameRect.width, workArea.width) * hscale),
|
|
+ Math.round(Math.min(frameRect.height, workArea.height) * vscale));
|
|
+ childBox.set_origin(
|
|
+ Math.round((frameRect.x - workArea.x) * hscale),
|
|
+ Math.round((frameRect.y - workArea.y) * vscale));
|
|
+ child.allocate(childBox);
|
|
+ }
|
|
+ }
|
|
+}
|
|
+
|
|
+class WorkspaceThumbnail extends St.Button {
|
|
+ static {
|
|
+ GObject.registerClass(this);
|
|
+ }
|
|
+
|
|
+ constructor(index) {
|
|
+ super({
|
|
+ style_class: 'workspace',
|
|
+ child: new Clutter.Actor({
|
|
+ layout_manager: new WorkspaceLayout(),
|
|
+ clip_to_allocation: true,
|
|
+ x_expand: true,
|
|
+ y_expand: true,
|
|
+ }),
|
|
+ });
|
|
+
|
|
+ this._tooltip = new St.Label({
|
|
+ style_class: 'dash-label',
|
|
+ visible: false,
|
|
+ });
|
|
+ Main.uiGroup.add_child(this._tooltip);
|
|
+
|
|
+ this.connect('destroy', this._onDestroy.bind(this));
|
|
+ this.connect('notify::hover', this._syncTooltip.bind(this));
|
|
+
|
|
+ this._index = index;
|
|
+ this._delegate = this; // needed for DND
|
|
+
|
|
+ this._windowPreviews = new Map();
|
|
+
|
|
+ let workspaceManager = global.workspace_manager;
|
|
+ this._workspace = workspaceManager.get_workspace_by_index(index);
|
|
+
|
|
+ this._workspace.connectObject(
|
|
+ 'window-added', (ws, window) => this._addWindow(window),
|
|
+ 'window-removed', (ws, window) => this._removeWindow(window),
|
|
+ this);
|
|
+
|
|
+ global.display.connectObject('restacked',
|
|
+ this._onRestacked.bind(this), this);
|
|
+
|
|
+ this._workspace.list_windows().forEach(w => this._addWindow(w));
|
|
+ this._onRestacked();
|
|
+ }
|
|
+
|
|
+ acceptDrop(source) {
|
|
+ if (!source.metaWindow)
|
|
+ return false;
|
|
+
|
|
+ this._moveWindow(source.metaWindow);
|
|
+ return true;
|
|
+ }
|
|
+
|
|
+ handleDragOver(source) {
|
|
+ if (source.metaWindow)
|
|
+ return DND.DragMotionResult.MOVE_DROP;
|
|
+ else
|
|
+ return DND.DragMotionResult.CONTINUE;
|
|
+ }
|
|
+
|
|
+ _addWindow(window) {
|
|
+ if (this._windowPreviews.has(window))
|
|
+ return;
|
|
+
|
|
+ let preview = new WindowPreview(window);
|
|
+ preview.connect('clicked', (a, btn) => this.emit('clicked', btn));
|
|
+ this._windowPreviews.set(window, preview);
|
|
+ this.child.add_child(preview);
|
|
+ }
|
|
+
|
|
+ _removeWindow(window) {
|
|
+ let preview = this._windowPreviews.get(window);
|
|
+ if (!preview)
|
|
+ return;
|
|
+
|
|
+ this._windowPreviews.delete(window);
|
|
+ preview.destroy();
|
|
+ }
|
|
+
|
|
+ _onRestacked() {
|
|
+ let lastPreview = null;
|
|
+ let windows = global.get_window_actors().map(a => a.meta_window);
|
|
+ for (let i = 0; i < windows.length; i++) {
|
|
+ let preview = this._windowPreviews.get(windows[i]);
|
|
+ if (!preview)
|
|
+ continue;
|
|
+
|
|
+ this.child.set_child_above_sibling(preview, lastPreview);
|
|
+ lastPreview = preview;
|
|
+ }
|
|
+ }
|
|
+
|
|
+ _moveWindow(window) {
|
|
+ let monitorIndex = Main.layoutManager.findIndexForActor(this);
|
|
+ if (monitorIndex !== window.get_monitor())
|
|
+ window.move_to_monitor(monitorIndex);
|
|
+ window.change_workspace_by_index(this._index, false);
|
|
+ }
|
|
+
|
|
+ on_clicked() {
|
|
+ let ws = global.workspace_manager.get_workspace_by_index(this._index);
|
|
+ if (ws)
|
|
+ ws.activate(global.get_current_time());
|
|
+ }
|
|
+
|
|
+ _syncTooltip() {
|
|
+ if (this.hover) {
|
|
+ this._tooltip.set({
|
|
+ text: Meta.prefs_get_workspace_name(this._index),
|
|
+ visible: true,
|
|
+ opacity: 0,
|
|
+ });
|
|
+
|
|
+ const [stageX, stageY] = this.get_transformed_position();
|
|
+ const thumbWidth = this.allocation.get_width();
|
|
+ const thumbHeight = this.allocation.get_height();
|
|
+ const tipWidth = this._tooltip.width;
|
|
+ const xOffset = Math.floor((thumbWidth - tipWidth) / 2);
|
|
+ const monitor = Main.layoutManager.findMonitorForActor(this);
|
|
+ const x = Math.clamp(
|
|
+ stageX + xOffset,
|
|
+ monitor.x,
|
|
+ monitor.x + monitor.width - tipWidth);
|
|
+ const y = stageY + thumbHeight + TOOLTIP_OFFSET;
|
|
+ this._tooltip.set_position(x, y);
|
|
+ }
|
|
+
|
|
+ this._tooltip.ease({
|
|
+ opacity: this.hover ? 255 : 0,
|
|
+ duration: TOOLTIP_ANIMATION_TIME,
|
|
+ mode: Clutter.AnimationMode.EASE_OUT_QUAD,
|
|
+ onComplete: () => (this._tooltip.visible = this.hover),
|
|
+ });
|
|
+ }
|
|
+
|
|
+ _onDestroy() {
|
|
+ this._tooltip.destroy();
|
|
+ }
|
|
+}
|
|
+
|
|
+export class WorkspaceIndicator extends PanelMenu.Button {
|
|
+ static {
|
|
+ GObject.registerClass(this);
|
|
+ }
|
|
+
|
|
+ constructor() {
|
|
+ super(0.5, _('Workspace Indicator'));
|
|
+
|
|
+ let container = new St.Widget({
|
|
+ layout_manager: new Clutter.BinLayout(),
|
|
+ x_expand: true,
|
|
+ y_expand: true,
|
|
+ });
|
|
+ this.add_child(container);
|
|
+
|
|
+ let workspaceManager = global.workspace_manager;
|
|
+
|
|
+ this._currentWorkspace = workspaceManager.get_active_workspace_index();
|
|
+ this._statusLabel = new St.Label({
|
|
+ style_class: 'panel-workspace-indicator',
|
|
+ y_align: Clutter.ActorAlign.CENTER,
|
|
+ text: this._labelText(),
|
|
+ });
|
|
+
|
|
+ container.add_child(this._statusLabel);
|
|
+
|
|
+ this._thumbnailsBox = new St.BoxLayout({
|
|
+ style_class: 'panel-workspace-indicator-box',
|
|
+ y_expand: true,
|
|
+ reactive: true,
|
|
+ });
|
|
+
|
|
+ container.add_child(this._thumbnailsBox);
|
|
+
|
|
+ this._workspacesItems = [];
|
|
+ this._workspaceSection = new PopupMenu.PopupMenuSection();
|
|
+ this.menu.addMenuItem(this._workspaceSection);
|
|
+
|
|
+ workspaceManager.connectObject(
|
|
+ 'notify::n-workspaces', this._nWorkspacesChanged.bind(this), GObject.ConnectFlags.AFTER,
|
|
+ 'workspace-switched', this._onWorkspaceSwitched.bind(this), GObject.ConnectFlags.AFTER,
|
|
+ 'notify::layout-rows', this._updateThumbnailVisibility.bind(this),
|
|
+ this);
|
|
+
|
|
+ this.connect('scroll-event', this._onScrollEvent.bind(this));
|
|
+ this._thumbnailsBox.connect('scroll-event', this._onScrollEvent.bind(this));
|
|
+ this._createWorkspacesSection();
|
|
+ this._updateThumbnails();
|
|
+ this._updateThumbnailVisibility();
|
|
+
|
|
+ this._settings = new Gio.Settings({schema_id: WORKSPACE_SCHEMA});
|
|
+ this._settings.connectObject(`changed::${WORKSPACE_KEY}`,
|
|
+ this._updateMenuLabels.bind(this), this);
|
|
+ }
|
|
+
|
|
+ _onDestroy() {
|
|
+ Main.panel.set_offscreen_redirect(Clutter.OffscreenRedirect.ALWAYS);
|
|
+
|
|
+ super._onDestroy();
|
|
+ }
|
|
+
|
|
+ _updateThumbnailVisibility() {
|
|
+ const {workspaceManager} = global;
|
|
+ const vertical = workspaceManager.layout_rows === -1;
|
|
+ const useMenu =
|
|
+ vertical || workspaceManager.n_workspaces > MAX_THUMBNAILS;
|
|
+ this.reactive = useMenu;
|
|
+
|
|
+ this._statusLabel.visible = useMenu;
|
|
+ this._thumbnailsBox.visible = !useMenu;
|
|
+
|
|
+ // Disable offscreen-redirect when showing the workspace switcher
|
|
+ // so that clip-to-allocation works
|
|
+ Main.panel.set_offscreen_redirect(useMenu
|
|
+ ? Clutter.OffscreenRedirect.ALWAYS
|
|
+ : Clutter.OffscreenRedirect.AUTOMATIC_FOR_OPACITY);
|
|
+ }
|
|
+
|
|
+ _onWorkspaceSwitched() {
|
|
+ this._currentWorkspace = global.workspace_manager.get_active_workspace_index();
|
|
+
|
|
+ this._updateMenuOrnament();
|
|
+ this._updateActiveThumbnail();
|
|
+
|
|
+ this._statusLabel.set_text(this._labelText());
|
|
+ }
|
|
+
|
|
+ _nWorkspacesChanged() {
|
|
+ this._createWorkspacesSection();
|
|
+ this._updateThumbnails();
|
|
+ this._updateThumbnailVisibility();
|
|
+ }
|
|
+
|
|
+ _updateMenuOrnament() {
|
|
+ for (let i = 0; i < this._workspacesItems.length; i++) {
|
|
+ this._workspacesItems[i].setOrnament(i === this._currentWorkspace
|
|
+ ? PopupMenu.Ornament.DOT
|
|
+ : PopupMenu.Ornament.NO_DOT);
|
|
+ }
|
|
+ }
|
|
+
|
|
+ _updateActiveThumbnail() {
|
|
+ let thumbs = this._thumbnailsBox.get_children();
|
|
+ for (let i = 0; i < thumbs.length; i++) {
|
|
+ if (i === this._currentWorkspace)
|
|
+ thumbs[i].add_style_class_name('active');
|
|
+ else
|
|
+ thumbs[i].remove_style_class_name('active');
|
|
+ }
|
|
+ }
|
|
+
|
|
+ _labelText(workspaceIndex) {
|
|
+ if (workspaceIndex === undefined) {
|
|
+ workspaceIndex = this._currentWorkspace;
|
|
+ return (workspaceIndex + 1).toString();
|
|
+ }
|
|
+ return Meta.prefs_get_workspace_name(workspaceIndex);
|
|
+ }
|
|
+
|
|
+ _updateMenuLabels() {
|
|
+ for (let i = 0; i < this._workspacesItems.length; i++)
|
|
+ this._workspacesItems[i].label.text = this._labelText(i);
|
|
+ }
|
|
+
|
|
+ _createWorkspacesSection() {
|
|
+ let workspaceManager = global.workspace_manager;
|
|
+
|
|
+ this._workspaceSection.removeAll();
|
|
+ this._workspacesItems = [];
|
|
+ this._currentWorkspace = workspaceManager.get_active_workspace_index();
|
|
+
|
|
+ let i = 0;
|
|
+ for (; i < workspaceManager.n_workspaces; i++) {
|
|
+ this._workspacesItems[i] = new PopupMenu.PopupMenuItem(this._labelText(i));
|
|
+ this._workspaceSection.addMenuItem(this._workspacesItems[i]);
|
|
+ this._workspacesItems[i].workspaceId = i;
|
|
+ this._workspacesItems[i].label_actor = this._statusLabel;
|
|
+ this._workspacesItems[i].connect('activate', (actor, _event) => {
|
|
+ this._activate(actor.workspaceId);
|
|
+ });
|
|
+
|
|
+ this._workspacesItems[i].setOrnament(i === this._currentWorkspace
|
|
+ ? PopupMenu.Ornament.DOT
|
|
+ : PopupMenu.Ornament.NO_DOT);
|
|
+ }
|
|
+
|
|
+ this._statusLabel.set_text(this._labelText());
|
|
+ }
|
|
+
|
|
+ _updateThumbnails() {
|
|
+ let workspaceManager = global.workspace_manager;
|
|
+
|
|
+ this._thumbnailsBox.destroy_all_children();
|
|
+
|
|
+ for (let i = 0; i < workspaceManager.n_workspaces; i++) {
|
|
+ let thumb = new WorkspaceThumbnail(i);
|
|
+ this._thumbnailsBox.add_child(thumb);
|
|
+ }
|
|
+ this._updateActiveThumbnail();
|
|
+ }
|
|
+
|
|
+ _activate(index) {
|
|
+ let workspaceManager = global.workspace_manager;
|
|
+
|
|
+ if (index >= 0 && index < workspaceManager.n_workspaces) {
|
|
+ let metaWorkspace = workspaceManager.get_workspace_by_index(index);
|
|
+ metaWorkspace.activate(global.get_current_time());
|
|
+ }
|
|
+ }
|
|
+
|
|
+ _onScrollEvent(actor, event) {
|
|
+ let direction = event.get_scroll_direction();
|
|
+ let diff = 0;
|
|
+ if (direction === Clutter.ScrollDirection.DOWN)
|
|
+ diff = 1;
|
|
+ else if (direction === Clutter.ScrollDirection.UP)
|
|
+ diff = -1;
|
|
+ else
|
|
+ return;
|
|
+
|
|
+
|
|
+ let newIndex = global.workspace_manager.get_active_workspace_index() + diff;
|
|
+ this._activate(newIndex);
|
|
+ }
|
|
+}
|
|
diff --git a/po/POTFILES.in b/po/POTFILES.in
|
|
index b77f6c21..182b2be0 100644
|
|
--- a/po/POTFILES.in
|
|
+++ b/po/POTFILES.in
|
|
@@ -20,5 +20,5 @@ extensions/window-list/org.gnome.shell.extensions.window-list.gschema.xml
|
|
extensions/window-list/prefs.js
|
|
extensions/window-list/workspaceIndicator.js
|
|
extensions/windowsNavigator/extension.js
|
|
-extensions/workspace-indicator/extension.js
|
|
extensions/workspace-indicator/prefs.js
|
|
+extensions/workspace-indicator/workspaceIndicator.js
|
|
--
|
|
2.44.0
|
|
|