From 41a29becb785765aa965460518916a5bb9dd20d1 Mon Sep 17 00:00:00 2001 From: MSVSphere Packaging Team Date: Fri, 25 Oct 2024 14:55:11 +0300 Subject: [PATCH] import gnome-shell-extensions-47.0-2.el10 --- .gitignore | 1 + .gnome-shell-extensions.metadata | 1 + ...lude-status-icons-in-classic-session.patch | 32 + ...0001-Add-gesture-inhibitor-extension.patch | 181 ++ ...sions-0002-Add-classification-banner.patch | 479 +++++ ...extensions-0003-Add-heads-up-display.patch | 878 +++++++++ ...sions-0004-Add-custom-menu-extension.patch | 719 ++++++++ ...ons-0005-Add-desktop-icons-extension.patch | 72 + SOURCES/window-list-reordering.patch | 1622 +++++++++++++++++ SPECS/gnome-shell-extensions.spec | 1212 ++++++++++++ 10 files changed, 5197 insertions(+) create mode 100644 .gitignore create mode 100644 .gnome-shell-extensions.metadata create mode 100644 SOURCES/0001-Include-status-icons-in-classic-session.patch create mode 100644 SOURCES/extra-extensions-0001-Add-gesture-inhibitor-extension.patch create mode 100644 SOURCES/extra-extensions-0002-Add-classification-banner.patch create mode 100644 SOURCES/extra-extensions-0003-Add-heads-up-display.patch create mode 100644 SOURCES/extra-extensions-0004-Add-custom-menu-extension.patch create mode 100644 SOURCES/extra-extensions-0005-Add-desktop-icons-extension.patch create mode 100644 SOURCES/window-list-reordering.patch create mode 100644 SPECS/gnome-shell-extensions.spec diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..86d5b13 --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +SOURCES/gnome-shell-extensions-47.0.tar.xz diff --git a/.gnome-shell-extensions.metadata b/.gnome-shell-extensions.metadata new file mode 100644 index 0000000..8bb9f42 --- /dev/null +++ b/.gnome-shell-extensions.metadata @@ -0,0 +1 @@ +98847e816858f90394ec0b016a86f2e481f0d3b7 SOURCES/gnome-shell-extensions-47.0.tar.xz diff --git a/SOURCES/0001-Include-status-icons-in-classic-session.patch b/SOURCES/0001-Include-status-icons-in-classic-session.patch new file mode 100644 index 0000000..b803e90 --- /dev/null +++ b/SOURCES/0001-Include-status-icons-in-classic-session.patch @@ -0,0 +1,32 @@ +From 09d8a56d61abd5aab39a54312f180ad431605a3f Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Florian=20M=C3=BCllner?= +Date: Fri, 23 Feb 2018 16:56:46 +0100 +Subject: [PATCH] Include status-icons in classic session + +--- + meson.build | 2 +- + 1 file changed, 1 insertion(+), 1 deletion(-) + +diff --git a/meson.build b/meson.build +index 9ecd0923..e759012d 100644 +--- a/meson.build ++++ b/meson.build +@@ -34,6 +34,7 @@ classic_extensions = [ + 'apps-menu', + 'places-menu', + 'launch-new-instance', ++ 'status-icons', + 'window-list' + ] + +@@ -44,7 +45,6 @@ default_extensions += [ + 'heads-up-display', + 'light-style', + 'screenshot-window-sizer', +- 'status-icons', + 'system-monitor', + 'windowsNavigator', + 'workspace-indicator' +-- +2.46.0 + diff --git a/SOURCES/extra-extensions-0001-Add-gesture-inhibitor-extension.patch b/SOURCES/extra-extensions-0001-Add-gesture-inhibitor-extension.patch new file mode 100644 index 0000000..ff54b6c --- /dev/null +++ b/SOURCES/extra-extensions-0001-Add-gesture-inhibitor-extension.patch @@ -0,0 +1,181 @@ +From cfa4c600830902bbf4027f1bbee7c6e394a84432 Mon Sep 17 00:00:00 2001 +From: Carlos Garnacho +Date: Thu, 28 Jan 2021 00:06:12 +0100 +Subject: [PATCH 1/5] Add gesture-inhibitor extension + +This extension may disable default GNOME Shell gestures. +--- + extensions/gesture-inhibitor/extension.js | 79 +++++++++++++++++++ + extensions/gesture-inhibitor/meson.build | 8 ++ + extensions/gesture-inhibitor/metadata.json.in | 12 +++ + ...l.extensions.gesture-inhibitor.gschema.xml | 25 ++++++ + meson.build | 1 + + 5 files changed, 125 insertions(+) + create mode 100644 extensions/gesture-inhibitor/extension.js + create mode 100644 extensions/gesture-inhibitor/meson.build + create mode 100644 extensions/gesture-inhibitor/metadata.json.in + create mode 100644 extensions/gesture-inhibitor/org.gnome.shell.extensions.gesture-inhibitor.gschema.xml + +diff --git a/extensions/gesture-inhibitor/extension.js b/extensions/gesture-inhibitor/extension.js +new file mode 100644 +index 00000000..872020ba +--- /dev/null ++++ b/extensions/gesture-inhibitor/extension.js +@@ -0,0 +1,79 @@ ++// SPDX-FileCopyrightText: 2021 Carlos Garnacho ++// ++// SPDX-License-Identifier: GPL-2.0-or-later ++// ++ ++import Clutter from 'gi://Clutter'; ++import Gio from 'gi://Gio'; ++import St from 'gi://St'; ++ ++import * as Main from 'resource:///org/gnome/shell/ui/main.js'; ++ ++import {AppSwitchAction} from 'resource:///org/gnome/shell/ui/windowManager.js'; ++import {EdgeDragAction} from 'resource:///org/gnome/shell/ui/edgeDragAction.js'; ++ ++import {Extension} from 'resource:///org/gnome/shell/extensions/extension.js'; ++ ++export default class GestureInhibitorExtension extends Extension { ++ constructor(metadata) { ++ super(metadata); ++ ++ let actions = global.stage.get_actions(); ++ ++ actions.forEach(a => { ++ if (a instanceof AppSwitchAction) ++ this._appSwitch = a; ++ else if (a instanceof EdgeDragAction && ++ a._side === St.Side.BOTTOM) ++ this._showOsk = a; ++ else if (a instanceof EdgeDragAction && ++ a._side === St.Side.TOP) ++ this._unfullscreen = a; ++ }); ++ ++ this._map = [ ++ {setting: 'overview', action: Main.overview._swipeTracker}, ++ {setting: 'app-switch', action: this._appSwitch}, ++ {setting: 'show-osk', action: this._showOsk}, ++ {setting: 'unfullscreen', action: this._unfullscreen}, ++ {setting: 'workspace-switch', action: Main.wm._workspaceAnimation._swipeTracker}, ++ ]; ++ ++ this._enabledDesc = Object.getOwnPropertyDescriptor( ++ Clutter.ActorMeta.prototype, 'enabled'); ++ } ++ ++ _overrideEnabledSetter(obj, set) { ++ if (!(obj instanceof Clutter.ActorMeta)) ++ return; ++ ++ const desc = set ++ ? {...this._enabledDesc, set} ++ : {...this._enabledDesc}; ++ Object.defineProperty(obj, 'enabled', desc); ++ } ++ ++ enable() { ++ const settings = this.getSettings(); ++ ++ this._map.forEach(m => { ++ settings.bind(m.setting, m.action, 'enabled', ++ Gio.SettingsBindFlags.DEFAULT); ++ ++ this._overrideEnabledSetter(m.action, function (value) { ++ if (settings.get_boolean(m.setting)) { ++ // eslint-disable-next-line no-invalid-this ++ this.set_enabled(value); ++ } ++ }); ++ }); ++ } ++ ++ disable() { ++ this._map.forEach(m => { ++ Gio.Settings.unbind(m.action, 'enabled'); ++ this._overrideEnabledSetter(m.action); ++ m.action.enabled = true; ++ }); ++ } ++} +diff --git a/extensions/gesture-inhibitor/meson.build b/extensions/gesture-inhibitor/meson.build +new file mode 100644 +index 00000000..fdad5cc8 +--- /dev/null ++++ b/extensions/gesture-inhibitor/meson.build +@@ -0,0 +1,8 @@ ++extension_data += configure_file( ++ input: metadata_name + '.in', ++ output: metadata_name, ++ configuration: metadata_conf ++) ++ ++# extension_sources += files('prefs.js') ++extension_schemas += files(metadata_conf.get('gschemaname') + '.gschema.xml') +diff --git a/extensions/gesture-inhibitor/metadata.json.in b/extensions/gesture-inhibitor/metadata.json.in +new file mode 100644 +index 00000000..37d6a117 +--- /dev/null ++++ b/extensions/gesture-inhibitor/metadata.json.in +@@ -0,0 +1,12 @@ ++{ ++ "uuid": "@uuid@", ++ "extension-id": "@extension_id@", ++ "settings-schema": "@gschemaname@", ++ "gettext-domain": "@gettext_domain@", ++ "name": "Gesture Inhibitor", ++ "description": "Makes touchscreen gestures optional.", ++ "shell-version": [ "@shell_current@" ], ++ "original-authors": [ "cgarnach@redhat.com" ], ++ "url": "@url@" ++} ++ +diff --git a/extensions/gesture-inhibitor/org.gnome.shell.extensions.gesture-inhibitor.gschema.xml b/extensions/gesture-inhibitor/org.gnome.shell.extensions.gesture-inhibitor.gschema.xml +new file mode 100644 +index 00000000..b06d027a +--- /dev/null ++++ b/extensions/gesture-inhibitor/org.gnome.shell.extensions.gesture-inhibitor.gschema.xml +@@ -0,0 +1,25 @@ ++ ++ ++ ++ true ++ Show OSK gesture ++ ++ ++ true ++ Show Overview gesture ++ ++ ++ true ++ Application switch gesture ++ ++ ++ true ++ Workspace switch gesture ++ ++ ++ true ++ Unfullscreen gesture ++ ++ ++ ++ +diff --git a/meson.build b/meson.build +index 0d458a00..49d36c46 100644 +--- a/meson.build ++++ b/meson.build +@@ -51,6 +51,7 @@ default_extensions += [ + all_extensions = default_extensions + all_extensions += [ + 'auto-move-windows', ++ 'gesture-inhibitor', + 'native-window-placement', + 'user-theme' + ] +-- +2.46.0 + diff --git a/SOURCES/extra-extensions-0002-Add-classification-banner.patch b/SOURCES/extra-extensions-0002-Add-classification-banner.patch new file mode 100644 index 0000000..90c66f6 --- /dev/null +++ b/SOURCES/extra-extensions-0002-Add-classification-banner.patch @@ -0,0 +1,479 @@ +From 83dbd799013fc52ba79ec449286434a78c04c1f8 Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Florian=20M=C3=BCllner?= +Date: Thu, 2 Dec 2021 19:39:50 +0100 +Subject: [PATCH 2/5] Add classification-banner + +--- + extensions/classification-banner/extension.js | 163 +++++++++++++++ + extensions/classification-banner/meson.build | 9 + + .../classification-banner/metadata.json.in | 11 + + ...tensions.classification-banner.gschema.xml | 29 +++ + extensions/classification-banner/prefs.js | 192 ++++++++++++++++++ + .../classification-banner/stylesheet.css | 3 + + meson.build | 1 + + 7 files changed, 408 insertions(+) + create mode 100644 extensions/classification-banner/extension.js + create mode 100644 extensions/classification-banner/meson.build + create mode 100644 extensions/classification-banner/metadata.json.in + create mode 100644 extensions/classification-banner/org.gnome.shell.extensions.classification-banner.gschema.xml + create mode 100644 extensions/classification-banner/prefs.js + create mode 100644 extensions/classification-banner/stylesheet.css + +diff --git a/extensions/classification-banner/extension.js b/extensions/classification-banner/extension.js +new file mode 100644 +index 00000000..32c7d794 +--- /dev/null ++++ b/extensions/classification-banner/extension.js +@@ -0,0 +1,163 @@ ++// SPDX-FileCopyrightText: 2021 Florian Müllner ++// SPDX-License-Identifier: GPL-2.0-or-later ++ ++import Clutter from 'gi://Clutter'; ++import Cogl from 'gi://Cogl'; ++import Gio from 'gi://Gio'; ++import GLib from 'gi://GLib'; ++import GObject from 'gi://GObject'; ++import Shell from 'gi://Shell'; ++import St from 'gi://St'; ++ ++import {Extension} from 'resource:///org/gnome/shell/extensions/extension.js'; ++ ++import * as Main from 'resource:///org/gnome/shell/ui/main.js'; ++import {MonitorConstraint} from 'resource:///org/gnome/shell/ui/layout.js'; ++ ++class ClassificationBanner extends Clutter.Actor { ++ static { ++ GObject.registerClass(this); ++ } ++ ++ #topBanner; ++ #bottomBanner; ++ #monitorConstraint; ++ #settings; ++ ++ constructor(index, settings) { ++ const constraint = new MonitorConstraint({index}); ++ super({ ++ layout_manager: new Clutter.BinLayout(), ++ constraints: constraint, ++ }); ++ this.#monitorConstraint = constraint; ++ ++ Shell.util_set_hidden_from_pick(this, true); ++ ++ this.#settings = settings; ++ ++ this.#topBanner = new St.BoxLayout({ ++ style_class: 'classification-banner', ++ x_expand: true, ++ y_expand: true, ++ y_align: Clutter.ActorAlign.START, ++ }); ++ this.add_child(this.#topBanner); ++ this.#settings.bind('top-banner', ++ this.#topBanner, 'visible', ++ Gio.SettingsBindFlags.GET); ++ ++ this.#bottomBanner = new St.BoxLayout({ ++ style_class: 'classification-banner', ++ x_expand: true, ++ y_expand: true, ++ y_align: Clutter.ActorAlign.END, ++ }); ++ this.add_child(this.#bottomBanner); ++ this.#settings.bind('bottom-banner', ++ this.#bottomBanner, 'visible', ++ Gio.SettingsBindFlags.GET); ++ ++ for (const banner of [this.#topBanner, this.#bottomBanner]) { ++ const label = new St.Label({ ++ style_class: 'classification-message', ++ x_align: Clutter.ActorAlign.CENTER, ++ x_expand: true, ++ }); ++ banner.add_child(label); ++ ++ this.#settings.bind('message', ++ label, 'text', ++ Gio.SettingsBindFlags.GET); ++ } ++ ++ const hostLabel = new St.Label({ ++ style_class: 'classification-system-info', ++ text: GLib.get_host_name(), ++ }); ++ this.#topBanner.insert_child_at_index(hostLabel, 0); ++ this.#settings.bind('system-info', ++ hostLabel, 'visible', ++ Gio.SettingsBindFlags.GET); ++ ++ const userLabel = new St.Label({ ++ style_class: 'classification-system-info', ++ text: GLib.get_user_name(), ++ }); ++ this.#topBanner.add_child(userLabel); ++ this.#settings.bind('system-info', ++ userLabel, 'visible', ++ Gio.SettingsBindFlags.GET); ++ ++ global.display.connectObject('in-fullscreen-changed', ++ () => this.#updateMonitorConstraint(), this); ++ this.#updateMonitorConstraint(); ++ ++ this.#settings.connectObject( ++ 'changed::color', () => this.#updateStyles(), ++ 'changed::background-color', () => this.#updateStyles(), ++ this); ++ this.#updateStyles(); ++ } ++ ++ #getColorSetting(key) { ++ const str = this.#settings.get_string(key); ++ const [valid, color] = Cogl.Color.from_string(str); ++ if (!valid) ++ return ''; ++ const {red, green, blue, alpha} = color; ++ return `${key}: rgba(${red},${green},${blue},${alpha / 255});`; ++ } ++ ++ #updateMonitorConstraint() { ++ const {index} = this.#monitorConstraint; ++ this.#monitorConstraint.work_area = ++ !global.display.get_monitor_in_fullscreen(index); ++ } ++ ++ #updateStyles() { ++ const bgStyle = this.#getColorSetting('background-color'); ++ const fgStyle = this.#getColorSetting('color'); ++ const style = `${bgStyle}${fgStyle}`; ++ this.#topBanner.set({style}); ++ this.#bottomBanner.set({style}); ++ } ++} ++ ++export default class ClassificationBannerExtension extends Extension { ++ #banners = []; ++ ++ #updateMonitors() { ++ const {monitors, panelBox, primaryIndex} = Main.layoutManager; ++ if (monitors.length !== this.#banners.length) { ++ this.#clearBanners(); ++ ++ const settings = this.getSettings(); ++ for (let i = 0; i < monitors.length; i++) { ++ const banner = new ClassificationBanner(i, settings); ++ Main.uiGroup.add_child(banner); ++ this.#banners.push(banner); ++ } ++ } ++ ++ const primaryBanner = this.#banners[primaryIndex]; ++ if (primaryBanner) ++ Main.uiGroup.set_child_below_sibling(primaryBanner, panelBox); ++ } ++ ++ #clearBanners() { ++ this.#banners.forEach(b => b.destroy()); ++ this.#banners = []; ++ } ++ ++ enable() { ++ Main.layoutManager.connectObject('monitors-changed', ++ () => this.#updateMonitors(), this); ++ this.#updateMonitors(); ++ } ++ ++ disable() { ++ Main.layoutManager.disconnectObject(this); ++ this.#clearBanners(); ++ } ++} +diff --git a/extensions/classification-banner/meson.build b/extensions/classification-banner/meson.build +new file mode 100644 +index 00000000..aa943741 +--- /dev/null ++++ b/extensions/classification-banner/meson.build +@@ -0,0 +1,9 @@ ++extension_data += configure_file( ++ input: metadata_name + '.in', ++ output: metadata_name, ++ configuration: metadata_conf ++) ++extension_data += files('stylesheet.css') ++ ++extension_sources += files('prefs.js') ++extension_schemas += files(metadata_conf.get('gschemaname') + '.gschema.xml') +diff --git a/extensions/classification-banner/metadata.json.in b/extensions/classification-banner/metadata.json.in +new file mode 100644 +index 00000000..f93b1a2d +--- /dev/null ++++ b/extensions/classification-banner/metadata.json.in +@@ -0,0 +1,11 @@ ++{ ++"extension-id": "@extension_id@", ++"uuid": "@uuid@", ++"settings-schema": "@gschemaname@", ++"gettext-domain": "@gettext_domain@", ++"name": "Classification Banner", ++"description": "Display classification level banner", ++"shell-version": [ "@shell_current@" ], ++"session-modes": [ "gdm", "unlock-dialog", "user" ], ++"url": "@url@" ++} +diff --git a/extensions/classification-banner/org.gnome.shell.extensions.classification-banner.gschema.xml b/extensions/classification-banner/org.gnome.shell.extensions.classification-banner.gschema.xml +new file mode 100644 +index 00000000..0314ef60 +--- /dev/null ++++ b/extensions/classification-banner/org.gnome.shell.extensions.classification-banner.gschema.xml +@@ -0,0 +1,29 @@ ++ ++ ++ ++ true ++ Show a banner at the top ++ ++ ++ true ++ Show a banner at the bottom ++ ++ ++ "UNCLASSIFIED" ++ classification message ++ ++ ++ "#fff" ++ text color ++ ++ ++ "rgba(0,122,51,0.75)" ++ background color ++ ++ ++ false ++ Include system info in top banner ++ ++ ++ +diff --git a/extensions/classification-banner/prefs.js b/extensions/classification-banner/prefs.js +new file mode 100644 +index 00000000..dc73ddae +--- /dev/null ++++ b/extensions/classification-banner/prefs.js +@@ -0,0 +1,192 @@ ++import Adw from 'gi://Adw'; ++import Gdk from 'gi://Gdk'; ++import Gio from 'gi://Gio'; ++import GObject from 'gi://GObject'; ++import Gtk from 'gi://Gtk'; ++ ++import {ExtensionPreferences, gettext as _} from 'resource:///org/gnome/Shell/Extensions/js/extensions/prefs.js'; ++ ++class GenericPrefs extends Adw.PreferencesGroup { ++ static { ++ GObject.registerClass(this); ++ } ++ ++ #actionGroup = new Gio.SimpleActionGroup(); ++ #settings; ++ ++ constructor(settings) { ++ super(); ++ ++ this.#settings = settings; ++ this.insert_action_group('options', this.#actionGroup); ++ ++ this.#actionGroup.add_action(settings.create_action('top-banner')); ++ this.#actionGroup.add_action(settings.create_action('bottom-banner')); ++ this.#actionGroup.add_action(settings.create_action('system-info')); ++ ++ this.add(new Adw.SwitchRow({ ++ title: _('Top Banner'), ++ action_name: 'options.top-banner', ++ })); ++ ++ this.add(new Adw.SwitchRow({ ++ title: _('Bottom Banner'), ++ action_name: 'options.bottom-banner', ++ })); ++ ++ this.add(new Adw.SwitchRow({ ++ title: _('System Info'), ++ action_name: 'options.system-info', ++ })); ++ } ++} ++ ++class BannerPreset extends GObject.Object { ++ static [GObject.properties] = { ++ 'message': GObject.ParamSpec.string( ++ 'message', 'message', 'message', ++ GObject.ParamFlags.READWRITE, ++ null), ++ 'color': GObject.ParamSpec.string( ++ 'color', 'color', 'color', ++ GObject.ParamFlags.READWRITE, ++ null), ++ 'background-color': GObject.ParamSpec.string( ++ 'background-color', 'background-color', 'background-color', ++ GObject.ParamFlags.READWRITE, ++ null), ++ }; ++ ++ static { ++ GObject.registerClass(this); ++ } ++} ++ ++class AppearancePrefs extends Adw.PreferencesGroup { ++ static { ++ GObject.registerClass(this); ++ } ++ ++ #settings; ++ ++ constructor(settings) { ++ super(); ++ ++ this.#settings = settings; ++ ++ const model = new Gio.ListStore({item_type: BannerPreset.$gtype}); ++ model.append(new BannerPreset({ ++ message: 'UNCLASSIFIED', ++ color: '#fff', ++ background_color: 'rgba(0, 122, 51, 0.75)', ++ })); ++ model.append(new BannerPreset({ ++ message: 'CONFIDENTIAL', ++ color: '#fff', ++ background_color: 'rgba(0, 51, 160, 0.75)', ++ })); ++ model.append(new BannerPreset({ ++ message: 'SECRET', ++ color: '#fff', ++ background_color: 'rgba(200, 16, 46, 0.75)', ++ })); ++ model.append(new BannerPreset({ ++ message: 'TOP SECRET', ++ color: '#fff', ++ background_color: 'rgba(255, 103, 31, 0.75)', ++ })); ++ model.append(new BannerPreset({ ++ message: 'TOP SECRET//SCI', ++ color: '#000', ++ background_color: 'rgba(247, 234, 72, 0.75)', ++ })); ++ ++ let row, activatableWidget; ++ row = this.#createPresetsRow(model); ++ row.connect('notify::selected-item', comboRow => { ++ const {message, color, backgroundColor} = comboRow.selected_item; ++ this.#settings.set_string('message', message); ++ this.#settings.set_string('color', color); ++ this.#settings.set_string('background-color', backgroundColor); ++ }); ++ this.add(row); ++ ++ activatableWidget = new Gtk.Entry({ ++ valign: Gtk.Align.CENTER, ++ }); ++ this.#settings.bind('message', ++ activatableWidget, 'text', ++ Gio.SettingsBindFlags.DEFAULT); ++ row = new Adw.ActionRow({title: _('Message'), activatableWidget}); ++ row.add_suffix(activatableWidget); ++ this.add(row); ++ ++ activatableWidget = this.#createColorButton('background-color', { ++ use_alpha: true, ++ }); ++ row = new Adw.ActionRow({title: _('Background color'), activatableWidget}); ++ row.add_suffix(activatableWidget); ++ this.add(row); ++ ++ activatableWidget = this.#createColorButton('color'); ++ row = new Adw.ActionRow({title: _('Text color'), activatableWidget}); ++ row.add_suffix(activatableWidget); ++ this.add(row); ++ } ++ ++ #createPresetsRow(model) { ++ const listFactory = new Gtk.SignalListItemFactory(); ++ listFactory.connect('setup', ++ (f, item) => item.set_child(new Gtk.Label())); ++ listFactory.connect('bind', (f, listItem) => { ++ const {child, item} = listItem; ++ ++ const provider = new Gtk.CssProvider(); ++ provider.load_from_data(`* { ++ border-radius: 99px; ++ padding: 6px; ++ color: ${item.color}; ++ background-color: ${item.background_color}; ++ }`, -1); ++ child.get_style_context().add_provider(provider, ++ Gtk.STYLE_PROVIDER_PRIORITY_APPLICATION); ++ child.label = item.message; ++ }); ++ ++ return new Adw.ComboRow({ ++ title: _('Presets'), ++ model, ++ listFactory, ++ expression: Gtk.ConstantExpression.new_for_value(''), ++ }); ++ } ++ ++ #createColorButton(key, params = {}) { ++ const rgba = new Gdk.RGBA(); ++ rgba.parse(this.#settings.get_string(key)); ++ ++ const button = new Gtk.ColorButton({ ++ ...params, ++ rgba, ++ valign: Gtk.Align.CENTER, ++ }); ++ this.#settings.connect(`changed::${key}`, () => { ++ const newRgba = new Gdk.RGBA(); ++ newRgba.parse(this.#settings.get_string(key)); ++ if (!newRgba.equal(button.rgba)) ++ button.set({rgba: newRgba}); ++ }); ++ button.connect('notify::rgba', ++ () => this.#settings.set_string(key, button.rgba.to_string())); ++ return button; ++ } ++} ++ ++export default class ClassificationPrefs extends ExtensionPreferences { ++ getPreferencesWidget() { ++ const page = new Adw.PreferencesPage(); ++ page.add(new AppearancePrefs(this.getSettings())); ++ page.add(new GenericPrefs(this.getSettings())); ++ return page; ++ } ++} +diff --git a/extensions/classification-banner/stylesheet.css b/extensions/classification-banner/stylesheet.css +new file mode 100644 +index 00000000..fb6a697e +--- /dev/null ++++ b/extensions/classification-banner/stylesheet.css +@@ -0,0 +1,3 @@ ++.classification-system-info { padding: 0 24px; } ++.classification-message { font-weight: bold; } ++.classification-banner { font-size: 0.9em; } +diff --git a/meson.build b/meson.build +index 49d36c46..2e49d72f 100644 +--- a/meson.build ++++ b/meson.build +@@ -51,6 +51,7 @@ default_extensions += [ + all_extensions = default_extensions + all_extensions += [ + 'auto-move-windows', ++ 'classification-banner', + 'gesture-inhibitor', + 'native-window-placement', + 'user-theme' +-- +2.46.0 + diff --git a/SOURCES/extra-extensions-0003-Add-heads-up-display.patch b/SOURCES/extra-extensions-0003-Add-heads-up-display.patch new file mode 100644 index 0000000..fd8ef57 --- /dev/null +++ b/SOURCES/extra-extensions-0003-Add-heads-up-display.patch @@ -0,0 +1,878 @@ +From c2864f8358f3c5c317f5f5e209c9a099fbbf9b23 Mon Sep 17 00:00:00 2001 +From: Ray Strode +Date: Tue, 24 Aug 2021 15:03:57 -0400 +Subject: [PATCH 3/5] Add heads-up-display + +--- + extensions/heads-up-display/extension.js | 404 ++++++++++++++++++ + extensions/heads-up-display/headsUpMessage.js | 166 +++++++ + extensions/heads-up-display/meson.build | 13 + + extensions/heads-up-display/metadata.json.in | 12 + + ...ll.extensions.heads-up-display.gschema.xml | 60 +++ + extensions/heads-up-display/prefs.js | 92 ++++ + extensions/heads-up-display/stylesheet.css | 38 ++ + meson.build | 1 + + po/POTFILES.in | 1 + + 9 files changed, 787 insertions(+) + create mode 100644 extensions/heads-up-display/extension.js + create mode 100644 extensions/heads-up-display/headsUpMessage.js + create mode 100644 extensions/heads-up-display/meson.build + create mode 100644 extensions/heads-up-display/metadata.json.in + create mode 100644 extensions/heads-up-display/org.gnome.shell.extensions.heads-up-display.gschema.xml + create mode 100644 extensions/heads-up-display/prefs.js + create mode 100644 extensions/heads-up-display/stylesheet.css + +diff --git a/extensions/heads-up-display/extension.js b/extensions/heads-up-display/extension.js +new file mode 100644 +index 00000000..a71b5925 +--- /dev/null ++++ b/extensions/heads-up-display/extension.js +@@ -0,0 +1,404 @@ ++// SPDX-FileCopyrightText: 2021 Ray Strode ++// ++// SPDX-License-Identifier: GPL-2.0-or-later ++ ++import GObject from 'gi://GObject'; ++import Meta from 'gi://Meta'; ++import Mtk from 'gi://Mtk'; ++ ++import {Extension, gettext as _} from 'resource:///org/gnome/shell/extensions/extension.js'; ++ ++import * as Main from 'resource:///org/gnome/shell/ui/main.js'; ++import {MonitorConstraint} from 'resource:///org/gnome/shell/ui/layout.js'; ++ ++import {HeadsUpMessage} from './headsUpMessage.js'; ++ ++var HeadsUpConstraint = GObject.registerClass({ ++ Properties: { ++ 'offset': GObject.ParamSpec.int( ++ 'offset', 'Offset', 'offset', ++ GObject.ParamFlags.READABLE | GObject.ParamFlags.WRITABLE, ++ -1, 0, -1), ++ 'active': GObject.ParamSpec.boolean( ++ 'active', 'Active', 'active', ++ GObject.ParamFlags.READABLE | GObject.ParamFlags.WRITABLE, ++ true), ++ }, ++}, class HeadsUpConstraint extends MonitorConstraint { ++ constructor(props) { ++ super(props); ++ this._offset = 0; ++ this._active = true; ++ } ++ ++ get offset() { ++ return this._offset; ++ } ++ ++ set offset(o) { ++ this._offset = o; ++ } ++ ++ get active() { ++ return this._active; ++ } ++ ++ set active(a) { ++ this._active = a; ++ } ++ ++ vfunc_update_allocation(actor, actorBox) { ++ if (!Main.layoutManager.primaryMonitor) ++ return; ++ ++ if (!this.active) ++ return; ++ ++ if (actor.has_allocation()) ++ return; ++ ++ const workArea = Main.layoutManager.getWorkAreaForMonitor(Main.layoutManager.primaryIndex); ++ actorBox.init_rect(workArea.x, workArea.y + this.offset, workArea.width, workArea.height - this.offset); ++ } ++}); ++ ++export default class HeadsUpDisplayExtension extends Extension { ++ enable() { ++ this._settings = this.getSettings('org.gnome.shell.extensions.heads-up-display'); ++ this._settings.connectObject('changed', ++ () => this._updateMessage(), this); ++ ++ this._idleMonitor = global.backend.get_core_idle_monitor(); ++ this._messageInhibitedUntilIdle = false; ++ global.window_manager.connectObject('map', ++ this._onWindowMap.bind(this), this); ++ ++ if (Main.layoutManager._startingUp) ++ Main.layoutManager.connectObject('startup-complete', () => this._onStartupComplete(), this); ++ else ++ this._onStartupComplete(); ++ } ++ ++ disable() { ++ this._dismissMessage(); ++ ++ this._stopWatchingForIdle(); ++ ++ Main.sessionMode.disconnectObject(this); ++ Main.overview.disconnectObject(this); ++ Main.layoutManager.panelBox.disconnectObject(this); ++ Main.layoutManager.disconnectObject(this); ++ global.window_manager.disconnectObject(this); ++ ++ if (this._screenShieldVisibleId) { ++ Main.screenShield._dialog._clock.disconnect(this._screenShieldVisibleId); ++ this._screenShieldVisibleId = 0; ++ } ++ ++ this._settings.disconnectObject(this); ++ delete this._settings; ++ } ++ ++ _onWindowMap(shellwm, actor) { ++ const windowObject = actor.meta_window; ++ const windowType = windowObject.get_window_type(); ++ ++ if (windowType !== Meta.WindowType.NORMAL) ++ return; ++ ++ if (!this._message || !this._message.visible) ++ return; ++ ++ const messageRect = new Mtk.Rectangle({ ++ x: this._message.x, ++ y: this._message.y, ++ width: this._message.width, ++ height: this._message.height, ++ }); ++ const windowRect = windowObject.get_frame_rect(); ++ ++ if (windowRect.intersect(messageRect)) ++ windowObject.move_frame(false, windowRect.x, this._message.y + this._message.height); ++ } ++ ++ _onStartupComplete() { ++ Main.overview.connectObject( ++ 'showing', () => this._updateMessage(), ++ 'hidden', () => this._updateMessage(), ++ this); ++ Main.layoutManager.panelBox.connectObject('notify::visible', ++ () => this._updateMessage(), this); ++ Main.sessionMode.connectObject('updated', ++ () => this._onSessionModeUpdated(), this); ++ ++ this._updateMessage(); ++ } ++ ++ _onSessionModeUpdated() { ++ if (!Main.sessionMode.hasWindows) ++ this._messageInhibitedUntilIdle = false; ++ ++ const dialog = Main.screenShield._dialog; ++ if (!Main.sessionMode.isGreeter && dialog && !this._screenShieldVisibleId) { ++ this._screenShieldVisibleId = dialog._clock.connect('notify::visible', this._updateMessage.bind(this)); ++ this._screenShieldDestroyId = dialog._clock.connect('destroy', () => { ++ this._screenShieldVisibleId = 0; ++ this._screenShieldDestroyId = 0; ++ }); ++ } ++ this._updateMessage(); ++ } ++ ++ _stopWatchingForIdle() { ++ if (this._idleWatchId) { ++ this._idleMonitor.remove_watch(this._idleWatchId); ++ this._idleWatchId = 0; ++ } ++ ++ if (this._idleTimeoutChangedId) { ++ this._settings.disconnect(this._idleTimeoutChangedId); ++ this._idleTimeoutChangedId = 0; ++ } ++ } ++ ++ _onIdleTimeoutChanged() { ++ this._stopWatchingForIdle(); ++ this._messageInhibitedUntilIdle = false; ++ } ++ ++ _onUserIdle() { ++ this._messageInhibitedUntilIdle = false; ++ this._updateMessage(); ++ } ++ ++ _watchForIdle() { ++ this._stopWatchingForIdle(); ++ ++ const idleTimeout = this._settings.get_uint('idle-timeout'); ++ ++ this._idleTimeoutChangedId = ++ this._settings.connect('changed::idle-timeout', ++ this._onIdleTimeoutChanged.bind(this)); ++ this._idleWatchId = this._idleMonitor.add_idle_watch(idleTimeout * 1000, ++ this._onUserIdle.bind(this)); ++ } ++ ++ _updateMessage() { ++ if (this._messageInhibitedUntilIdle) { ++ if (this._message) ++ this._dismissMessage(); ++ return; ++ } ++ ++ this._stopWatchingForIdle(); ++ ++ if (Main.sessionMode.hasOverview && Main.overview.visible) { ++ this._dismissMessage(); ++ return; ++ } ++ ++ if (!Main.layoutManager.panelBox.visible) { ++ this._dismissMessage(); ++ return; ++ } ++ ++ let supportedModes = []; ++ ++ if (this._settings.get_boolean('show-when-unlocked')) ++ supportedModes.push('user'); ++ ++ if (this._settings.get_boolean('show-when-unlocking') || ++ this._settings.get_boolean('show-when-locked')) ++ supportedModes.push('unlock-dialog'); ++ ++ if (this._settings.get_boolean('show-on-login-screen')) ++ supportedModes.push('gdm'); ++ ++ if (!supportedModes.includes(Main.sessionMode.currentMode) && ++ !supportedModes.includes(Main.sessionMode.parentMode)) { ++ this._dismissMessage(); ++ return; ++ } ++ ++ if (Main.sessionMode.currentMode === 'unlock-dialog') { ++ const dialog = Main.screenShield._dialog; ++ if (!this._settings.get_boolean('show-when-locked')) { ++ if (dialog._clock.visible) { ++ this._dismissMessage(); ++ return; ++ } ++ } ++ ++ if (!this._settings.get_boolean('show-when-unlocking')) { ++ if (!dialog._clock.visible) { ++ this._dismissMessage(); ++ return; ++ } ++ } ++ } ++ ++ const heading = this._settings.get_string('message-heading'); ++ const body = this._settings.get_string('message-body'); ++ ++ if (!heading && !body) { ++ this._dismissMessage(); ++ return; ++ } ++ ++ if (!this._message) { ++ this._message = new HeadsUpMessage(heading, body); ++ ++ this._message.connect('notify::allocation', this._adaptSessionForMessage.bind(this)); ++ this._message.connect('clicked', this._onMessageClicked.bind(this)); ++ } ++ ++ this._message.reactive = true; ++ this._message.track_hover = true; ++ ++ this._message.setHeading(heading); ++ this._message.setBody(body); ++ ++ if (!Main.sessionMode.hasWindows) { ++ this._message.track_hover = false; ++ this._message.reactive = false; ++ } ++ } ++ ++ _onMessageClicked() { ++ if (!Main.sessionMode.hasWindows) ++ return; ++ ++ this._watchForIdle(); ++ this._messageInhibitedUntilIdle = true; ++ this._updateMessage(); ++ } ++ ++ _dismissMessage() { ++ if (!this._message) ++ return; ++ ++ this._message.visible = false; ++ this._message.destroy(); ++ this._message = null; ++ this._resetMessageTray(); ++ this._resetLoginDialog(); ++ } ++ ++ _resetMessageTray() { ++ if (!Main.messageTray) ++ return; ++ ++ if (this._updateMessageTrayId) { ++ global.stage.disconnect(this._updateMessageTrayId); ++ this._updateMessageTrayId = 0; ++ } ++ ++ if (this._messageTrayConstraint) { ++ Main.messageTray.remove_constraint(this._messageTrayConstraint); ++ this._messageTrayConstraint = null; ++ } ++ } ++ ++ _alignMessageTray() { ++ if (!Main.messageTray) ++ return; ++ ++ if (!this._message || !this._message.visible) { ++ this._resetMessageTray(); ++ return; ++ } ++ ++ if (this._updateMessageTrayId) ++ return; ++ ++ this._updateMessageTrayId = global.stage.connect('before-update', () => { ++ if (!this._messageTrayConstraint) { ++ this._messageTrayConstraint = new HeadsUpConstraint({primary: true}); ++ ++ Main.layoutManager.panelBox.bind_property('visible', ++ this._messageTrayConstraint, 'active', ++ GObject.BindingFlags.SYNC_CREATE); ++ ++ Main.messageTray.add_constraint(this._messageTrayConstraint); ++ } ++ ++ const panelBottom = Main.layoutManager.panelBox.y + Main.layoutManager.panelBox.height; ++ const messageBottom = this._message.y + this._message.height; ++ ++ this._messageTrayConstraint.offset = messageBottom - panelBottom; ++ global.stage.disconnect(this._updateMessageTrayId); ++ this._updateMessageTrayId = 0; ++ }); ++ } ++ ++ _resetLoginDialog() { ++ if (!Main.sessionMode.isGreeter) ++ return; ++ ++ if (!Main.screenShield || !Main.screenShield._dialog) ++ return; ++ ++ const dialog = Main.screenShield._dialog; ++ ++ if (this._authPromptAllocatedId) { ++ dialog.disconnect(this._authPromptAllocatedId); ++ this._authPromptAllocatedId = 0; ++ } ++ ++ if (this._updateLoginDialogId) { ++ global.stage.disconnect(this._updateLoginDialogId); ++ this._updateLoginDialogId = 0; ++ } ++ ++ if (this._loginDialogConstraint) { ++ dialog.remove_constraint(this._loginDialogConstraint); ++ this._loginDialogConstraint = null; ++ } ++ } ++ ++ _adaptLoginDialogForMessage() { ++ if (!Main.sessionMode.isGreeter) ++ return; ++ ++ if (!Main.screenShield || !Main.screenShield._dialog) ++ return; ++ ++ if (!this._message || !this._message.visible) { ++ this._resetLoginDialog(); ++ return; ++ } ++ ++ const dialog = Main.screenShield._dialog; ++ ++ if (this._updateLoginDialogId) ++ return; ++ ++ this._updateLoginDialogId = global.stage.connect('before-update', () => { ++ let messageHeight = this._message.y + this._message.height; ++ if (dialog._logoBin.visible) ++ messageHeight -= dialog._logoBin.height; ++ ++ if (!this._logindDialogConstraint) { ++ this._loginDialogConstraint = new HeadsUpConstraint({primary: true}); ++ dialog.add_constraint(this._loginDialogConstraint); ++ } ++ ++ this._loginDialogConstraint.offset = messageHeight; ++ ++ global.stage.disconnect(this._updateLoginDialogId); ++ this._updateLoginDialogId = 0; ++ }); ++ } ++ ++ _adaptSessionForMessage() { ++ this._alignMessageTray(); ++ ++ if (Main.sessionMode.isGreeter) { ++ this._adaptLoginDialogForMessage(); ++ if (!this._authPromptAllocatedId) { ++ const dialog = Main.screenShield._dialog; ++ this._authPromptAllocatedId = dialog._authPrompt.connect('notify::allocation', this._adaptLoginDialogForMessage.bind(this)); ++ } ++ } ++ } ++} +diff --git a/extensions/heads-up-display/headsUpMessage.js b/extensions/heads-up-display/headsUpMessage.js +new file mode 100644 +index 00000000..30298847 +--- /dev/null ++++ b/extensions/heads-up-display/headsUpMessage.js +@@ -0,0 +1,166 @@ ++// SPDX-FileCopyrightText: 2021 Ray Strode ++// ++// SPDX-License-Identifier: GPL-2.0-or-later ++ ++import Atk from 'gi://Atk'; ++import Clutter from 'gi://Clutter'; ++import GObject from 'gi://GObject'; ++import St from 'gi://St'; ++ ++import * as Main from 'resource:///org/gnome/shell/ui/main.js'; ++ ++const HeadsUpMessageBodyLabel = GObject.registerClass({ ++}, class HeadsUpMessageBodyLabel extends St.Label { ++ constructor(params) { ++ super(params); ++ ++ this._widthCoverage = 0.75; ++ this._heightCoverage = 0.25; ++ ++ global.display.connectObject('workareas-changed', ++ () => this._getWorkAreaAndMeasureLineHeight()); ++ } ++ ++ _getWorkAreaAndMeasureLineHeight() { ++ if (!this.get_parent()) ++ return; ++ ++ this._workArea = Main.layoutManager.getWorkAreaForMonitor(Main.layoutManager.primaryIndex); ++ ++ this.clutter_text.single_line_mode = true; ++ this.clutter_text.line_wrap = false; ++ ++ this._lineHeight = super.vfunc_get_preferred_height(-1)[0]; ++ ++ this.clutter_text.single_line_mode = false; ++ this.clutter_text.line_wrap = true; ++ } ++ ++ vfunc_parent_set() { ++ this._getWorkAreaAndMeasureLineHeight(); ++ } ++ ++ vfunc_get_preferred_width(forHeight) { ++ const maxWidth = this._widthCoverage * this._workArea.width; ++ ++ let [labelMinimumWidth, labelNaturalWidth] = super.vfunc_get_preferred_width(forHeight); ++ ++ labelMinimumWidth = Math.min(labelMinimumWidth, maxWidth); ++ labelNaturalWidth = Math.min(labelNaturalWidth, maxWidth); ++ ++ return [labelMinimumWidth, labelNaturalWidth]; ++ } ++ ++ vfunc_get_preferred_height(forWidth) { ++ const labelHeightUpperBound = this._heightCoverage * this._workArea.height; ++ const numberOfLines = Math.floor(labelHeightUpperBound / this._lineHeight); ++ this._numberOfLines = Math.max(numberOfLines, 1); ++ ++ const maxHeight = this._lineHeight * this._numberOfLines; ++ ++ let [labelMinimumHeight, labelNaturalHeight] = super.vfunc_get_preferred_height(forWidth); ++ ++ labelMinimumHeight = Math.min(labelMinimumHeight, maxHeight); ++ labelNaturalHeight = Math.min(labelNaturalHeight, maxHeight); ++ ++ return [labelMinimumHeight, labelNaturalHeight]; ++ } ++}); ++ ++export const HeadsUpMessage = GObject.registerClass({ ++}, class HeadsUpMessage extends St.Button { ++ constructor(heading, body) { ++ super({ ++ style_class: 'message', ++ accessible_role: Atk.Role.NOTIFICATION, ++ can_focus: false, ++ opacity: 0, ++ }); ++ ++ Main.layoutManager.addChrome(this, {affectsInputRegion: true}); ++ ++ this.add_style_class_name('heads-up-display-message'); ++ ++ this.connect('destroy', () => this._onDestroy()); ++ ++ Main.layoutManager.panelBox.connectObject('notify::allocation', ++ () => this._alignWithPanel()); ++ this.connect('notify::allocation', ++ () => this._alignWithPanel()); ++ ++ const contentsBox = new St.BoxLayout({ ++ style_class: 'heads-up-message-content', ++ vertical: true, ++ x_align: Clutter.ActorAlign.CENTER, ++ }); ++ this.add_child(contentsBox); ++ ++ this._headingLabel = new St.Label({ ++ style_class: 'heads-up-message-heading', ++ x_expand: true, ++ x_align: Clutter.ActorAlign.CENTER, ++ }); ++ ++ this.setHeading(heading); ++ contentsBox.add_child(this._headingLabel); ++ ++ this._bodyLabel = new HeadsUpMessageBodyLabel({ ++ style_class: 'heads-up-message-body', ++ x_expand: true, ++ y_expand: true, ++ }); ++ contentsBox.add_child(this._bodyLabel); ++ ++ this.setBody(body); ++ } ++ ++ vfunc_parent_set() { ++ this._alignWithPanel(); ++ } ++ ++ _alignWithPanel() { ++ if (this._beforeUpdateId) ++ return; ++ ++ this._beforeUpdateId = global.stage.connect('before-update', () => { ++ let x = Main.panel.x; ++ let y = Main.panel.y + Main.panel.height; ++ ++ x += Main.panel.width / 2; ++ x -= this.width / 2; ++ x = Math.floor(x); ++ this.set_position(x, y); ++ this.opacity = 255; ++ ++ global.stage.disconnect(this._beforeUpdateId); ++ this._beforeUpdateId = 0; ++ }); ++ } ++ ++ setHeading(text) { ++ if (text) { ++ const heading = text ? text.replace(/\n/g, ' ') : ''; ++ this._headingLabel.text = heading; ++ this._headingLabel.visible = true; ++ } else { ++ this._headingLabel.text = text; ++ this._headingLabel.visible = false; ++ } ++ } ++ ++ setBody(text) { ++ this._bodyLabel.text = text; ++ ++ if (text) ++ this._bodyLabel.visible = true; ++ else ++ this._bodyLabel.visible = false; ++ } ++ ++ _onDestroy() { ++ if (this._beforeUpdateId) { ++ global.stage.disconnect(this._beforeUpdateId); ++ this._beforeUpdateId = 0; ++ } ++ } ++}); +diff --git a/extensions/heads-up-display/meson.build b/extensions/heads-up-display/meson.build +new file mode 100644 +index 00000000..42ce222c +--- /dev/null ++++ b/extensions/heads-up-display/meson.build +@@ -0,0 +1,13 @@ ++# SPDX-FileCopyrightText: 2021 Ray Strode ++# ++# SPDX-License-Identifier: GPL-2.0-or-later ++ ++extension_data += configure_file( ++ input: metadata_name + '.in', ++ output: metadata_name, ++ configuration: metadata_conf ++) ++ ++extension_data += files('stylesheet.css') ++extension_sources += files('headsUpMessage.js', 'prefs.js') ++extension_schemas += files(metadata_conf.get('gschemaname') + '.gschema.xml') +diff --git a/extensions/heads-up-display/metadata.json.in b/extensions/heads-up-display/metadata.json.in +new file mode 100644 +index 00000000..01bcd4df +--- /dev/null ++++ b/extensions/heads-up-display/metadata.json.in +@@ -0,0 +1,12 @@ ++{ ++"extension-id": "@extension_id@", ++"uuid": "@uuid@", ++"settings-schema": "@gschemaname@", ++"gettext-domain": "@gettext_domain@", ++"name": "Heads-up Display Message", ++"description": "Add a message to be displayed on screen always above all windows and chrome.", ++"original-authors": [ "rstrode@redhat.com" ], ++"shell-version": [ "@shell_current@" ], ++"url": "@url@", ++"session-modes": [ "gdm", "lock-screen", "unlock-dialog", "user" ] ++} +diff --git a/extensions/heads-up-display/org.gnome.shell.extensions.heads-up-display.gschema.xml b/extensions/heads-up-display/org.gnome.shell.extensions.heads-up-display.gschema.xml +new file mode 100644 +index 00000000..1e2119c8 +--- /dev/null ++++ b/extensions/heads-up-display/org.gnome.shell.extensions.heads-up-display.gschema.xml +@@ -0,0 +1,60 @@ ++ ++ ++ ++ ++ ++ 30 ++ Idle Timeout ++ ++ Number of seconds until message is reshown after user goes idle. ++ ++ ++ ++ "" ++ Message to show at top of display ++ ++ The top line of the heads up display message. ++ ++ ++ ++ "" ++ Banner message ++ ++ A message to always show at the top of the screen. ++ ++ ++ ++ true ++ Show on login screen ++ ++ Whether or not the message should display on the login screen ++ ++ ++ ++ false ++ Show on screen shield ++ ++ Whether or not the message should display when the screen is locked ++ ++ ++ ++ false ++ Show on unlock screen ++ ++ Whether or not the message should display on the unlock screen. ++ ++ ++ ++ false ++ Show in user session ++ ++ Whether or not the message should display when the screen is unlocked. ++ ++ ++ ++ +diff --git a/extensions/heads-up-display/prefs.js b/extensions/heads-up-display/prefs.js +new file mode 100644 +index 00000000..304c8813 +--- /dev/null ++++ b/extensions/heads-up-display/prefs.js +@@ -0,0 +1,92 @@ ++// SPDX-FileCopyrightText: 2021 Ray Strode ++// ++// SPDX-License-Identifier: GPL-2.0-or-later ++ ++import Adw from 'gi://Adw'; ++import Gio from 'gi://Gio'; ++import GObject from 'gi://GObject'; ++import Gtk from 'gi://Gtk'; ++ ++import {ExtensionPreferences, gettext as _} from 'resource:///org/gnome/Shell/Extensions/js/extensions/prefs.js'; ++ ++class GeneralGroup extends Adw.PreferencesGroup { ++ static { ++ GObject.registerClass(this); ++ } ++ ++ constructor(settings) { ++ super(); ++ ++ const actionGroup = new Gio.SimpleActionGroup(); ++ this.insert_action_group('options', actionGroup); ++ ++ actionGroup.add_action(settings.create_action('show-when-locked')); ++ actionGroup.add_action(settings.create_action('show-when-unlocking')); ++ actionGroup.add_action(settings.create_action('show-when-unlocked')); ++ ++ this.add(new Adw.SwitchRow({ ++ title: _('Show message when screen is locked'), ++ action_name: 'options.show-when-locked', ++ })); ++ this.add(new Adw.SwitchRow({ ++ title: _('Show message on unlock screen'), ++ action_name: 'options.show-when-unlocking', ++ })); ++ this.add(new Adw.SwitchRow({ ++ title: _('Show message when screen is unlocked'), ++ action_name: 'options.show-when-unlocked', ++ })); ++ ++ const spinRow = new Adw.SpinRow({ ++ title: _('Seconds after user goes idle before reshowing message'), ++ adjustment: new Gtk.Adjustment({ ++ lower: 0, ++ upper: 2147483647, ++ step_increment: 1, ++ page_increment: 60, ++ page_size: 60, ++ }), ++ }); ++ settings.bind('idle-timeout', ++ spinRow, 'value', ++ Gio.SettingsBindFlags.DEFAULT); ++ this.add(spinRow); ++ } ++} ++ ++class MessageGroup extends Adw.PreferencesGroup { ++ static { ++ GObject.registerClass(this); ++ } ++ ++ constructor(settings) { ++ super({ ++ title: _('Message'), ++ }); ++ ++ const textView = new Gtk.TextView({ ++ accepts_tab: false, ++ wrap_mode: Gtk.WrapMode.WORD, ++ top_margin: 6, ++ bottom_margin: 6, ++ left_margin: 6, ++ right_margin: 6, ++ vexpand: true, ++ }); ++ textView.add_css_class('card'); ++ ++ settings.bind('message-body', ++ textView.get_buffer(), 'text', ++ Gio.SettingsBindFlags.DEFAULT); ++ this.add(textView); ++ } ++} ++ ++export default class HeadsUpDisplayPrefs extends ExtensionPreferences { ++ getPreferencesWidget() { ++ const page = new Adw.PreferencesPage(); ++ page.add(new GeneralGroup(this.getSettings())); ++ page.add(new MessageGroup(this.getSettings())); ++ return page; ++ } ++} +diff --git a/extensions/heads-up-display/stylesheet.css b/extensions/heads-up-display/stylesheet.css +new file mode 100644 +index 00000000..a1a34e3f +--- /dev/null ++++ b/extensions/heads-up-display/stylesheet.css +@@ -0,0 +1,38 @@ ++/* ++ * SPDX-FileCopyrightText: 2021 Ray Strode ++ * ++ * SPDX-License-Identifier: GPL-2.0-or-later ++ */ ++ ++.heads-up-display-message { ++ background-color: rgba(0.24, 0.24, 0.24, 0.80); ++ border: 1px solid black; ++ border-radius: 6px; ++ color: #eeeeec; ++ font-size: 11pt; ++ margin-top: 0.5em; ++ margin-bottom: 0.5em; ++ padding: 0.9em; ++} ++ ++.heads-up-display-message:insensitive { ++ background-color: rgba(0.24, 0.24, 0.24, 0.33); ++} ++ ++.heads-up-display-message:hover { ++ background-color: rgba(0.24, 0.24, 0.24, 0.2); ++ border: 1px solid rgba(0.0, 0.0, 0.0, 0.5); ++ color: #4d4d4d; ++ transition-duration: 250ms; ++} ++ ++.heads-up-message-heading { ++ height: 1.75em; ++ font-size: 1.25em; ++ font-weight: bold; ++ text-align: center; ++} ++ ++.heads-up-message-body { ++ text-align: center; ++} +diff --git a/meson.build b/meson.build +index 2e49d72f..ab30c7a3 100644 +--- a/meson.build ++++ b/meson.build +@@ -40,6 +40,7 @@ classic_extensions = [ + default_extensions = classic_extensions + default_extensions += [ + 'drive-menu', ++ 'heads-up-display', + 'light-style', + 'screenshot-window-sizer', + 'status-icons', +diff --git a/po/POTFILES.in b/po/POTFILES.in +index 447465a1..b7cb8a7c 100644 +--- a/po/POTFILES.in ++++ b/po/POTFILES.in +@@ -6,6 +6,7 @@ extensions/auto-move-windows/extension.js + extensions/auto-move-windows/org.gnome.shell.extensions.auto-move-windows.gschema.xml + extensions/auto-move-windows/prefs.js + extensions/drive-menu/extension.js ++extensions/heads-up-display/prefs.js + extensions/native-window-placement/extension.js + extensions/native-window-placement/org.gnome.shell.extensions.native-window-placement.gschema.xml + extensions/places-menu/extension.js +-- +2.46.0 + diff --git a/SOURCES/extra-extensions-0004-Add-custom-menu-extension.patch b/SOURCES/extra-extensions-0004-Add-custom-menu-extension.patch new file mode 100644 index 0000000..11aff1b --- /dev/null +++ b/SOURCES/extra-extensions-0004-Add-custom-menu-extension.patch @@ -0,0 +1,719 @@ +From f8d1ecf33fa58de64023f7431cd4d3e66e1abfe8 Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Florian=20M=C3=BCllner?= +Date: Thu, 12 Jan 2023 19:43:52 +0100 +Subject: [PATCH 4/5] Add custom-menu extension + +--- + extensions/custom-menu/config.js | 445 ++++++++++++++++++++++++ + extensions/custom-menu/extension.js | 201 +++++++++++ + extensions/custom-menu/meson.build | 7 + + extensions/custom-menu/metadata.json.in | 10 + + meson.build | 1 + + 5 files changed, 664 insertions(+) + create mode 100644 extensions/custom-menu/config.js + create mode 100644 extensions/custom-menu/extension.js + create mode 100644 extensions/custom-menu/meson.build + create mode 100644 extensions/custom-menu/metadata.json.in + +diff --git a/extensions/custom-menu/config.js b/extensions/custom-menu/config.js +new file mode 100644 +index 00000000..d08e3201 +--- /dev/null ++++ b/extensions/custom-menu/config.js +@@ -0,0 +1,445 @@ ++import Gio from 'gi://Gio'; ++import GLib from 'gi://GLib'; ++import Json from 'gi://Json'; ++ ++import * as Main from 'resource:///org/gnome/shell/ui/main.js'; ++import * as PopupMenu from 'resource:///org/gnome/shell/ui/popupMenu.js'; ++ ++import {getLogger} from './extension.js'; ++ ++class Entry { ++ constructor(prop) { ++ this.type = prop.type; ++ this.title = prop.title || ""; ++ this.__vars = prop.__vars || []; ++ this.updateEnv(prop); ++ } ++ ++ setTitle(text) { ++ this.item.label.get_clutter_text().set_text(text); ++ } ++ ++ updateEnv(prop) { ++ this.__env = {} ++ if (!this.__vars) return; ++ for (let i in this.__vars) { ++ let v = this.__vars[i]; ++ this.__env[v] = prop[v] ? String(prop[v]) : ""; ++ } ++ } ++ ++ // the pulse function should be read as "a pulse arrives" ++ pulse() { ++ } ++ ++ _try_destroy() { ++ try { ++ if (this.item && this.item.destroy) { ++ this.item.destroy(); ++ } ++ } catch(e) { /* Ignore all errors during destory*/ } ++ } ++} ++ ++class DerivedEntry { ++ constructor(prop) { ++ if (!prop.base) { ++ throw new Error("Base entry not specified in type definition."); ++ } ++ this.base = prop.base; ++ this.vars = prop.vars || []; ++ delete prop.base; ++ delete prop.vars; ++ this.prop = prop; ++ } ++ ++ createInstance(addit_prop) { ++ let cls = type_map[this.base]; ++ if (!cls) { ++ throw new Error("Bad base class."); ++ } ++ if (cls.createInstance) { ++ throw new Error("Not allowed to derive from dervied types"); ++ } ++ for (let rp in this.prop) { ++ addit_prop[rp] = this.prop[rp]; ++ } ++ addit_prop.__vars = this.vars; ++ let instance = new cls(addit_prop); ++ return instance; ++ } ++} ++ ++let __pipeOpenQueue = []; ++ ++/* callback: function (stdout, stderr, exit_status) { } */ ++function pipeOpen(cmdline, env, callback) { ++ if (cmdline === undefined || callback === undefined) { ++ return false; ++ } ++ realPipeOpen(cmdline, env, callback); ++ return true; ++} /**/ ++ ++function realPipeOpen(cmdline, env, callback) { ++ let user_cb = callback; ++ let proc; ++ ++ function wait_cb(_, _res) { ++ let stdout_pipe = proc.get_stdout_pipe(); ++ let stderr_pipe = proc.get_stderr_pipe(); ++ let stdout_content; ++ let stderr_content; ++ ++ // Only the first GLib.MAXINT16 characters are fetched for optimization. ++ stdout_pipe.read_bytes_async(GLib.MAXINT16, 0, null, function(osrc, ores) { ++ const decoder = new TextDecoder(); ++ stdout_content = decoder.decode(stdout_pipe.read_bytes_finish(ores).get_data()); ++ stdout_pipe.close(null); ++ stderr_pipe.read_bytes_async(GLib.MAXINT16, 0, null, function(esrc, eres) { ++ stderr_content = decoder.decode(stderr_pipe.read_bytes_finish(eres).get_data()); ++ stderr_pipe.close(null); ++ user_cb(stdout_content, stderr_content, proc.get_exit_status()); ++ }); ++ }); ++ } ++ ++ if (user_cb) { ++ let _pipedLauncher = new Gio.SubprocessLauncher({ ++ flags: ++ Gio.SubprocessFlags.STDERR_PIPE | ++ Gio.SubprocessFlags.STDOUT_PIPE ++ }); ++ for (let key in env) { ++ _pipedLauncher.setenv(key, env[key], true); ++ } ++ proc = _pipedLauncher.spawnv(['bash', '-c', cmdline]); ++ proc.wait_async(null, wait_cb); ++ } else { ++ // Detached launcher is used to spawn commands that we are not concerned about its result. ++ let _detacLauncher = new Gio.SubprocessLauncher(); ++ for (let key in env) { ++ _detacLauncher.setenv(key, env[key], true); ++ } ++ proc = _detacLauncher.spawnv(['bash', '-c', cmdline]); ++ } ++ getLogger().info("Spawned " + cmdline); ++ return proc.get_identifier(); ++} ++ ++function _generalSpawn(command, env, title) { ++ title = title || "Process"; ++ pipeOpen(command, env, function(stdout, stderr, exit_status) { ++ if (exit_status != 0) { ++ log ++ getLogger().warning(stderr); ++ getLogger().notify("proc", title + " exited with status " + exit_status, stderr); ++ } ++ }); ++} ++ ++// Detect menu toggle on startup ++function _toggleDetect(command, env, object) { ++ pipeOpen(command, env, function(stdout, stderr, exit_status) { ++ if (exit_status == 0) { ++ object.item.setToggleState(true); ++ } ++ }); ++} /**/ ++ ++function quoteShellArg(arg) { ++ arg = arg.replace(/'/g, "'\"'\"'"); ++ return "'" + arg + "'"; ++} ++ ++/** ++ * This cache is used to reduce detector cost. ++ * Each time creating an item, it check if the result of this detector is cached, ++ * which prevent the togglers from running detector on each creation. ++ * This is useful especially in search mode. ++ */ ++let _toggler_state_cache = { }; ++ ++class TogglerEntry extends Entry { ++ constructor(prop) { ++ super(prop); ++ this.command_on = prop.command_on || ""; ++ this.command_off = prop.command_off || ""; ++ this.detector = prop.detector || ""; ++ this.auto_on = prop.auto_on || false; ++ this.notify_when = prop.notify_when || []; ++ // if the switch is manually turned off, auto_on is disabled. ++ this._manually_switched_off = false; ++ this.pulse(); // load initial state ++ } ++ ++ createItem() { ++ this._try_destroy(); ++ this.item = new PopupMenu.PopupSwitchMenuItem(this.title, false); ++ this.item.label.get_clutter_text().set_use_markup(true); ++ this.item.connect('toggled', this._onManuallyToggled.bind(this)); ++ this._loadState(); ++ _toggleDetect(this.detector, this.__env, this); ++ return this.item; ++ } ++ ++ _onManuallyToggled(_, state) { ++ // when switched on again, this flag will get cleared. ++ this._manually_switched_off = !state; ++ this._storeState(state); ++ this._onToggled(state); ++ } ++ ++ _onToggled(state) { ++ if (state) { ++ _generalSpawn(this.command_on, this.__env, this.title); ++ } else { ++ _generalSpawn(this.command_off, this.__env, this.title); ++ } ++ } ++ ++ _detect(callback) { ++ // abort detecting if detector is an empty string ++ if (!this.detector) { ++ return; ++ } ++ pipeOpen(this.detector, this.__env, function(out) { ++ out = String(out); ++ callback(!Boolean(out.match(/^\s*$/))); ++ }); ++ } ++ ++ // compare the new state with cached state notify when state is different ++ compareState(new_state) { ++ let old_state = _toggler_state_cache[this.detector]; ++ if (old_state === undefined) return; ++ if (old_state == new_state) return; ++ ++ if (this.notify_when.indexOf(new_state ? "on" : "off") >= 0) { ++ let not_str = this.title + (new_state ? " started." : " stopped."); ++ if (!new_state && this.auto_on) { ++ not_str += " Attempt to restart it now."; ++ } ++ getLogger().notify("state", not_str); ++ } ++ } ++ ++ _storeState(state) { ++ let hash = JSON.stringify({ env: this.__env, detector: this.detector }); ++ _toggler_state_cache[hash] = state; ++ } ++ ++ _loadState() { ++ let hash = JSON.stringify({ env: this.__env, detector: this.detector }); ++ let state = _toggler_state_cache[hash]; ++ if (state !== undefined) { ++ this.item.setToggleState(state); // doesn't emit 'toggled' ++ } ++ } ++ ++ pulse() { ++ this._detect(state => { ++ this.compareState(state); ++ this._storeState(state); ++ this._loadState(); ++ if (!state && !this._manually_switched_off && this.auto_on) { ++ // do not call setToggleState here, because command_on may fail ++ this._onToggled(this.item, true); ++ } ++ }); ++ } ++ ++ perform() { ++ this.item.toggle(); ++ } ++} ++ ++class LauncherEntry extends Entry { ++ constructor(prop) { ++ super(prop); ++ this.command = prop.command || ""; ++ } ++ ++ createItem() { ++ this._try_destroy(); ++ this.item = new PopupMenu.PopupMenuItem(this.title); ++ this.item.label.get_clutter_text().set_use_markup(true); ++ this.item.connect('activate', this._onClicked.bind(this)); ++ return this.item; ++ } ++ ++ _onClicked(_) { ++ _generalSpawn(this.command, this.__env, this.title); ++ } ++ ++ perform() { ++ this.item.emit('activate'); ++ } ++} ++ ++class SubMenuEntry extends Entry { ++ constructor(prop) { ++ super(prop); ++ ++ if (prop.entries == undefined) { ++ throw new Error("Expected entries provided in submenu entry."); ++ } ++ this.entries = []; ++ for (let i in prop.entries) { ++ let entry_prop = prop.entries[i]; ++ let entry = createEntry(entry_prop); ++ this.entries.push(entry); ++ } ++ } ++ ++ createItem() { ++ this._try_destroy(); ++ this.item = new PopupMenu.PopupSubMenuMenuItem(this.title); ++ this.item.label.get_clutter_text().set_use_markup(true); ++ for (let i in this.entries) { ++ let entry = this.entries[i]; ++ this.item.menu.addMenuItem(entry.createItem()); ++ } ++ return this.item; ++ } ++ ++ pulse() { ++ for (let i in this.entries) { ++ let entry = this.entries[i]; ++ entry.pulse(); ++ } ++ } ++} ++ ++class SeparatorEntry extends Entry { ++ createItem() { ++ this._try_destroy(); ++ this.item = new PopupMenu.PopupSeparatorMenuItem(this.title); ++ this.item.label.get_clutter_text().set_use_markup(true); ++ return this.item; ++ } ++} ++ ++let type_map = {}; ++ ++//////////////////////////////////////////////////////////////////////////////// ++// Config Loader loads config from JSON file. ++ ++// convert Json Nodes (GLib based) to native javascript value. ++function convertJson(node) { ++ if (node.get_node_type() == Json.NodeType.VALUE) { ++ return node.get_value(); ++ } ++ if (node.get_node_type() == Json.NodeType.OBJECT) { ++ let obj = {} ++ node.get_object().foreach_member(function(_, k, v_n) { ++ obj[k] = convertJson(v_n); ++ }); ++ return obj; ++ } ++ if (node.get_node_type() == Json.NodeType.ARRAY) { ++ let arr = [] ++ node.get_array().foreach_element(function(_, i, elem) { ++ arr.push(convertJson(elem)); ++ }); ++ return arr; ++ } ++ return null; ++} ++ ++// ++function createEntry(entry_prop) { ++ if (!entry_prop.type) { ++ throw new Error("No type specified in entry."); ++ } ++ let cls = type_map[entry_prop.type]; ++ if (!cls) { ++ throw new Error("Incorrect type '" + entry_prop.type + "'"); ++ } else if (cls.createInstance) { ++ return cls.createInstance(entry_prop); ++ } ++ return new cls(entry_prop); ++} ++ ++export class Loader { ++ constructor(filename) { ++ if (filename) { ++ this.loadConfig(filename); ++ } ++ } ++ ++ loadConfig(filename) { ++ // reset type_map everytime load the config ++ type_map = { ++ launcher: LauncherEntry, ++ toggler: TogglerEntry, ++ submenu: SubMenuEntry, ++ separator: SeparatorEntry ++ }; ++ ++ type_map.systemd = new DerivedEntry({ ++ base: 'toggler', ++ vars: ['unit'], ++ command_on: "pkexec systemctl start ${unit}", ++ command_off: "pkexec systemctl stop ${unit}", ++ detector: "systemctl status ${unit} | grep Active:\\\\s\\*activ[ei]", ++ }); ++ ++ type_map.tmux = new DerivedEntry({ ++ base: 'toggler', ++ vars: ['command', 'session'], ++ command_on: 'tmux new -d -s ${session} bash -c "${command}"', ++ command_off: 'tmux kill-session -t ${session}', ++ detector: 'tmux has -t "${session}" 2>/dev/null && echo yes', ++ }); ++ ++ /* ++ * Refer to README file for detailed config file format. ++ */ ++ this.entries = []; // CAUTION: remove all entries. ++ ++ let config_parser = new Json.Parser(); ++ config_parser.load_from_file(filename); ++ let conf = convertJson(config_parser.get_root()); ++ if (conf.entries == undefined) { ++ throw new Error("Key 'entries' not found."); ++ } ++ if (conf.deftype) { ++ for (let tname in conf.deftype) { ++ if (type_map[tname]) { ++ throw new Error("Type \""+tname+"\" duplicated."); ++ } ++ type_map[tname] = new DerivedEntry(conf.deftype[tname]); ++ } ++ } ++ ++ for (let conf_i in conf.entries) { ++ let entry_prop = conf.entries[conf_i]; ++ this.entries.push(createEntry(entry_prop)); ++ } ++ } ++ ++ saveDefaultConfig(filename) { ++ // Write default config ++ const PERMISSIONS_MODE = 0o640; ++ const jsonString = JSON.stringify({ ++ "_homepage_": "https://github.com/andreabenini/gnome-plugin.custom-menu-panel", ++ "_examples_": "https://github.com/andreabenini/gnome-plugin.custom-menu-panel/tree/main/examples", ++ "entries": [ { ++ "type": "launcher", ++ "title": "Edit menu", ++ "command": "gedit $HOME/.entries.json" ++ } ] ++ }, null, 4); ++ let fileConfig = Gio.File.new_for_path(filename); ++ if (GLib.mkdir_with_parents(fileConfig.get_parent().get_path(), PERMISSIONS_MODE) === 0) { ++ fileConfig.replace_contents(jsonString, null, false, Gio.FileCreateFlags.REPLACE_DESTINATION, null); ++ } ++ // Try to load newly saved file ++ try { ++ this.loadConfig(filename); ++ } catch(e) { ++ Main.notify(_('Cannot create and load file: '+filename)); ++ } ++ } ++} +diff --git a/extensions/custom-menu/extension.js b/extensions/custom-menu/extension.js +new file mode 100644 +index 00000000..9edbc548 +--- /dev/null ++++ b/extensions/custom-menu/extension.js +@@ -0,0 +1,201 @@ ++/* extension.js ++ * ++ * This program is free software: you can redistribute it and/or modify ++ * it under the terms of the GNU General Public License as published by ++ * the Free Software Foundation, either version 3 of the License, or ++ * (at your option) any later version. ++ * ++ * This program is distributed in the hope that it will be useful, ++ * but WITHOUT ANY WARRANTY; without even the implied warranty of ++ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ++ * GNU General Public License for more details. ++ * ++ * You should have received a copy of the GNU General Public License ++ * along with this program. If not, see . ++ * ++ * SPDX-License-Identifier: GPL-3.0-or-later ++ */ ++ ++/** ++ * @author Ben ++ * @see https://github.com/andreabenini/gnome-plugin.custom-menu-panel ++ */ ++ ++const CONFIGURATION_FILE = '/.entries.json'; ++ ++import Gio from 'gi://Gio'; ++import GLib from 'gi://GLib'; ++import GObject from 'gi://GObject'; ++import St from 'gi://St'; ++ ++import {Extension, gettext as _} from 'resource:///org/gnome/shell/extensions/extension.js'; ++ ++import * as Main from 'resource:///org/gnome/shell/ui/main.js'; ++import * as BackgroundMenu from 'resource:///org/gnome/shell/ui/backgroundMenu.js'; ++import * as PanelMenu from 'resource:///org/gnome/shell/ui/panelMenu.js'; ++import * as PopupMenu from 'resource:///org/gnome/shell/ui/popupMenu.js'; ++ ++import * as Config from './config.js'; ++ ++const LOGGER_INFO = 0; ++const LOGGER_WARNING = 1; ++const LOGGER_ERROR = 2; ++ ++const {BackgroundMenu: OriginalBackgroundMenu} = BackgroundMenu; ++ ++ ++class CustomMenu extends PopupMenu.PopupMenu { ++ constructor(sourceActor) { ++ super(sourceActor, 0.0, St.Side.TOP, 0); ++ ++ this._loadSetup(); ++ } ++ ++ /** ++ * LOAD Program settings from .entries.json file ++ */ ++ _loadSetup() { ++ this.removeAll(); ++ // Loading configuration from file ++ this.configLoader = new Config.Loader(); ++ try { ++ this.configLoader.loadConfig(GLib.get_home_dir() + CONFIGURATION_FILE); // $HOME/.entries.json ++ } catch(e) { ++ this.configLoader.saveDefaultConfig(GLib.get_home_dir() + CONFIGURATION_FILE); // create default entries ++ } ++ // Build the menu ++ let i = 0; ++ for (let i in this.configLoader.entries) { ++ let item = this.configLoader.entries[i].createItem(); ++ this.addMenuItem(item); ++ } ++ } /**/ ++} ++ ++class CustomBackgroundMenu extends CustomMenu { ++ constructor(layoutManager) { ++ super(layoutManager.dummyCursor); ++ ++ this.actor.add_style_class_name('background-menu'); ++ ++ layoutManager.uiGroup.add_actor(this.actor); ++ this.actor.hide(); ++ ++ this.connect('open-state-changed', (menu, open) => { ++ if (open) ++ this._updateMaxHeight(); ++ }); ++ } ++ ++ _updateMaxHeight() { ++ const monitor = Main.layoutManager.findMonitorForActor(this.actor); ++ const workArea = Main.layoutManager.getWorkAreaForMonitor(monitor.index); ++ const {scaleFactor} = St.ThemeContext.get_for_stage(global.stage); ++ const vMargins = this.actor.margin_top + this.actor.margin_bottom; ++ const {y: offsetY} = this.sourceActor; ++ ++ const maxHeight = Math.round((monitor.height - offsetY - vMargins) / scaleFactor); ++ this.actor.style = `max-height: ${maxHeight}px;`; ++ } ++} ++ ++const Indicator = GObject.registerClass( ++ class Indicator extends PanelMenu.Button { ++ _init() { ++ super._init(0.0, _('Custom Menu Panel Indicator'), true); ++ this.add_child(new St.Icon({ ++ icon_name: 'view-list-bullet-symbolic', ++ style_class: 'system-status-icon', ++ })); ++ ++ this.setMenu(new CustomMenu(this)); ++ } ++ } ++); /**/ ++ ++ ++class Logger { ++ constructor(log_file) { ++ this._log_file = log_file; ++ // initailize log_backend ++ if (!log_file) { ++ this._initEmptyLog(); ++ } else if(log_file == "gnome-shell") { ++ this._initGnomeLog(); ++ } else { ++ this._initFileLog(); ++ } ++ this.level = LOGGER_WARNING; ++ this.info = function(t) { ++ if (this.level <= LOGGER_INFO) { ++ this.log(t); ++ } ++ }; ++ this.warning = function(t) { ++ if (this.level <= LOGGER_WARNING) { ++ this.log(t); ++ } ++ }; ++ this.error = function(t) { ++ if (this.level <= LOGGER_ERROR) { ++ this.log(t); ++ } ++ }; ++ } ++ ++ _initEmptyLog() { ++ this.log = function(_) { }; ++ } ++ ++ _initGnomeLog() { ++ this.log = function(s) { ++ global.log("custom-menu-panel> " + s); ++ }; ++ } ++ ++ _initFileLog() { ++ this.log = function(s) { ++ // all operations are synchronous: any needs to optimize? ++ if (!this._output_file || !this._output_file.query_exists(null) || !this._fstream || this._fstream.is_closed()) { ++ this._output_file = Gio.File.new_for_path(this._log_file); ++ this._fstream = this._output_file.append_to(Gio.FileCreateFlags.NONE, null); ++ if (!this._fstream instanceof Gio.FileIOStream) { ++ this._initGnomeLog(); ++ this.log("IOError: Failed to append to " + this._log_file + " [Gio.IOErrorEnum:" + this._fstream + "]"); ++ return; ++ } ++ } ++ this._fstream.write(String(new Date())+" "+s+"\n", null); ++ this._fstream.flush(null); ++ } ++ } ++ ++ notify(t, str, details) { ++ this.ncond = this.ncond || ['proc', 'ext', 'state']; ++ if (this.ncond.indexOf(t) < 0) { ++ return; ++ } ++ Main.notify(str, details || ""); ++ } ++} ++ ++// lazy-evaluation ++let logger = null; ++export function getLogger() { ++ if (logger === null) { ++ logger = new Logger("gnome-shell"); ++ } ++ return logger; ++} /**/ ++ ++export default class CustomMenuExtension extends Extension { ++ enable() { ++ BackgroundMenu.BackgroundMenu = CustomBackgroundMenu; ++ Main.layoutManager._updateBackgrounds(); ++ } ++ ++ disable() { ++ BackgroundMenu.BackgroundMenu = OriginalBackgroundMenu; ++ Main.layoutManager._updateBackgrounds(); ++ } ++} /**/ +diff --git a/extensions/custom-menu/meson.build b/extensions/custom-menu/meson.build +new file mode 100644 +index 00000000..92450963 +--- /dev/null ++++ b/extensions/custom-menu/meson.build +@@ -0,0 +1,7 @@ ++extension_data += configure_file( ++ input: metadata_name + '.in', ++ output: metadata_name, ++ configuration: metadata_conf ++) ++ ++extension_sources += files('config.js') +diff --git a/extensions/custom-menu/metadata.json.in b/extensions/custom-menu/metadata.json.in +new file mode 100644 +index 00000000..054f639b +--- /dev/null ++++ b/extensions/custom-menu/metadata.json.in +@@ -0,0 +1,10 @@ ++{ ++"extension-id": "@extension_id@", ++"uuid": "@uuid@", ++"settings-schema": "@gschemaname@", ++"gettext-domain": "@gettext_domain@", ++"name": "Custom menu", ++"description": "Quick custom menu for launching your favorite applications", ++"shell-version": [ "@shell_current@" ], ++"url": "@url@" ++} +diff --git a/meson.build b/meson.build +index ab30c7a3..c698dc14 100644 +--- a/meson.build ++++ b/meson.build +@@ -53,6 +53,7 @@ all_extensions = default_extensions + all_extensions += [ + 'auto-move-windows', + 'classification-banner', ++ 'custom-menu', + 'gesture-inhibitor', + 'native-window-placement', + 'user-theme' +-- +2.46.0 + diff --git a/SOURCES/extra-extensions-0005-Add-desktop-icons-extension.patch b/SOURCES/extra-extensions-0005-Add-desktop-icons-extension.patch new file mode 100644 index 0000000..60639f8 --- /dev/null +++ b/SOURCES/extra-extensions-0005-Add-desktop-icons-extension.patch @@ -0,0 +1,72 @@ +From d0f2273765ab61e55c5cf10e7283a545fcafa947 Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Florian=20M=C3=BCllner?= +Date: Thu, 22 Aug 2024 13:24:20 +0200 +Subject: [PATCH] Add stub desktop-icons extension + +--- + extensions/desktop-icons/extension.js | 5 +++++ + extensions/desktop-icons/meson.build | 9 +++++++++ + extensions/desktop-icons/metadata.json.in | 10 ++++++++++ + meson.build | 1 + + 4 files changed, 25 insertions(+) + create mode 100644 extensions/desktop-icons/extension.js + create mode 100644 extensions/desktop-icons/meson.build + create mode 100644 extensions/desktop-icons/metadata.json.in + +diff --git a/extensions/desktop-icons/extension.js b/extensions/desktop-icons/extension.js +new file mode 100644 +index 00000000..bbc96ef2 +--- /dev/null ++++ b/extensions/desktop-icons/extension.js +@@ -0,0 +1,5 @@ ++// SPDX-FileCopyrightText: 2024 Florian Müllner ++// ++// SPDX-License-Identifier: GPL-2.0-or-later ++ ++export {Extension as default} from 'resource:///org/gnome/shell/extensions/extension.js'; +diff --git a/extensions/desktop-icons/meson.build b/extensions/desktop-icons/meson.build +new file mode 100644 +index 00000000..7b28a2ef +--- /dev/null ++++ b/extensions/desktop-icons/meson.build +@@ -0,0 +1,9 @@ ++# SPDX-FileCopyrightText: 2017 Florian Müllner ++# ++# SPDX-License-Identifier: GPL-2.0-or-later ++ ++extension_data += configure_file( ++ input: metadata_name + '.in', ++ output: metadata_name, ++ configuration: metadata_conf ++) +diff --git a/extensions/desktop-icons/metadata.json.in b/extensions/desktop-icons/metadata.json.in +new file mode 100644 +index 00000000..78a55abb +--- /dev/null ++++ b/extensions/desktop-icons/metadata.json.in +@@ -0,0 +1,10 @@ ++{ ++"extension-id": "@extension_id@", ++"uuid": "@uuid@", ++"settings-schema": "@gschemaname@", ++"gettext-domain": "@gettext_domain@", ++"name": "Desktop Icons", ++"description": "Show icons on the desktop", ++"shell-version": [ "@shell_current@" ], ++"url": "@url@" ++} +diff --git a/meson.build b/meson.build +index b915b68c..63a7432e 100644 +--- a/meson.build ++++ b/meson.build +@@ -40,6 +40,7 @@ classic_extensions = [ + + default_extensions = classic_extensions + default_extensions += [ ++ 'desktop-icons', + 'drive-menu', + 'heads-up-display', + 'light-style', +-- +2.45.2 + diff --git a/SOURCES/window-list-reordering.patch b/SOURCES/window-list-reordering.patch new file mode 100644 index 0000000..989048e --- /dev/null +++ b/SOURCES/window-list-reordering.patch @@ -0,0 +1,1622 @@ +From 21e087ef90891e703338b00cea0cf38e11feae8f Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Florian=20M=C3=BCllner?= +Date: Wed, 25 Sep 2024 03:36:08 +0200 +Subject: [PATCH 01/22] window-list: Small stylesheet cleanup + +The light stylesheet duplicates some declarations, and the +last occurrence matches what we already inherit from the +dark stylesheet. + +Part-of: +--- + extensions/window-list/stylesheet-light.css | 10 ---------- + 1 file changed, 10 deletions(-) + +diff --git a/extensions/window-list/stylesheet-light.css b/extensions/window-list/stylesheet-light.css +index 375fd1bf..f9c51f8e 100644 +--- a/extensions/window-list/stylesheet-light.css ++++ b/extensions/window-list/stylesheet-light.css +@@ -21,21 +21,11 @@ + text-shadow: none; + } + +- .bottom-panel .window-button > StWidget { +- -st-natural-width: 18.7em; +- max-width: 18.75em; +- } +- + .window-button > StWidget { + color: #000; + background-color: transparent; + } + +-.window-button > StWidget { +- -st-natural-width: 18.75em; +- max-width: 18.75em; +-} +- + .window-button:hover > StWidget { + background-color: st-darken(#eee,5%); + } +-- +2.47.0 + + +From 5ac12f0ec7c378e4b65073823b0e03a0e9c219eb Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Florian=20M=C3=BCllner?= +Date: Wed, 25 Sep 2024 02:14:47 +0200 +Subject: [PATCH 02/22] window-list: Don't recreate icons on theme changes + +All icons use `StIcon`, which already updates itself correctly +on icon theme changes. + +Part-of: +--- + extensions/window-list/extension.js | 9 --------- + 1 file changed, 9 deletions(-) + +diff --git a/extensions/window-list/extension.js b/extensions/window-list/extension.js +index 3edbf8bf..9441fad1 100644 +--- a/extensions/window-list/extension.js ++++ b/extensions/window-list/extension.js +@@ -125,10 +125,6 @@ class WindowTitle extends St.BoxLayout { + this.label_actor.clutter_text.single_line_mode = true; + this.add_child(this.label_actor); + +- this._textureCache = St.TextureCache.get_default(); +- this._textureCache.connectObject('icon-theme-changed', +- () => this._updateIcon(), this); +- + this._metaWindow.connectObject( + 'notify::wm-class', + () => this._updateIcon(), GObject.ConnectFlags.AFTER, +@@ -591,11 +587,6 @@ class AppButton extends BaseButton { + this._appContextMenu.actor.hide(); + Main.uiGroup.add_child(this._appContextMenu.actor); + +- this._textureCache = St.TextureCache.get_default(); +- this._textureCache.connectObject('icon-theme-changed', () => { +- this._icon.child = app.create_icon_texture(ICON_TEXTURE_SIZE); +- }, this); +- + this.app.connectObject('windows-changed', + () => this._windowsChanged(), this); + this._windowsChanged(); +-- +2.47.0 + + +From 8c3a3f3b8a625bcfeedcfa367866f37ae9087c72 Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Florian=20M=C3=BCllner?= +Date: Wed, 25 Sep 2024 02:23:41 +0200 +Subject: [PATCH 03/22] window-list: Split out AppTitle class + +Even though it's just a box with icon and label, it's cleaner to +have a dedicated class. + +Part-of: +--- + extensions/window-list/extension.js | 47 ++++++++++++++++++----------- + 1 file changed, 30 insertions(+), 17 deletions(-) + +diff --git a/extensions/window-list/extension.js b/extensions/window-list/extension.js +index 9441fad1..a5bb55f6 100644 +--- a/extensions/window-list/extension.js ++++ b/extensions/window-list/extension.js +@@ -166,6 +166,35 @@ class WindowTitle extends St.BoxLayout { + } + } + ++class AppTitle extends St.BoxLayout { ++ static { ++ GObject.registerClass(this); ++ } ++ ++ constructor(app) { ++ super({ ++ style_class: 'window-button-box', ++ x_expand: true, ++ y_expand: true, ++ }); ++ ++ this._app = app; ++ ++ const icon = new St.Bin({ ++ style_class: 'window-button-icon', ++ child: app.create_icon_texture(ICON_TEXTURE_SIZE), ++ }); ++ this.add_child(icon); ++ ++ let label = new St.Label({ ++ text: app.get_name(), ++ y_align: Clutter.ActorAlign.CENTER, ++ }); ++ this.add_child(label); ++ this.label_actor = label; ++ } ++} ++ + class BaseButton extends DashItemContainer { + static { + GObject.registerClass({ +@@ -553,25 +582,9 @@ class AppButton extends BaseButton { + }); + stack.add_child(this._singleWindowTitle); + +- this._multiWindowTitle = new St.BoxLayout({ +- style_class: 'window-button-box', +- x_expand: true, +- }); ++ this._multiWindowTitle = new AppTitle(app); + stack.add_child(this._multiWindowTitle); + +- this._icon = new St.Bin({ +- style_class: 'window-button-icon', +- child: app.create_icon_texture(ICON_TEXTURE_SIZE), +- }); +- this._multiWindowTitle.add_child(this._icon); +- +- let label = new St.Label({ +- text: app.get_name(), +- y_align: Clutter.ActorAlign.CENTER, +- }); +- this._multiWindowTitle.add_child(label); +- this._multiWindowTitle.label_actor = label; +- + this._menuManager = new PopupMenu.PopupMenuManager(this); + this._menu = new PopupMenu.PopupMenu(this, 0.5, St.Side.BOTTOM); + this._menu.connect('open-state-changed', +-- +2.47.0 + + +From 0cc25fbf6e996e3d9b0352b728a23f8c308f01e5 Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Florian=20M=C3=BCllner?= +Date: Wed, 25 Sep 2024 02:55:14 +0200 +Subject: [PATCH 04/22] window-list: Simplify app button + +Depending on the number of windows, the button either shows the +title of the lone window, or the app title for multiple windows. + +While we always recreate the single-window title, we only create +the app title once and hide it as necessary. Avoiding re-creating +a simple actor 50% of mode transitions isn't worth the additional +complexity, so just handle both single- and multi-window titles +the same way. + +Part-of: +--- + extensions/window-list/extension.js | 71 ++++++++++------------------- + 1 file changed, 25 insertions(+), 46 deletions(-) + +diff --git a/extensions/window-list/extension.js b/extensions/window-list/extension.js +index a5bb55f6..9b9ea7b9 100644 +--- a/extensions/window-list/extension.js ++++ b/extensions/window-list/extension.js +@@ -574,17 +574,6 @@ class AppButton extends BaseButton { + this.app = app; + this._updateVisibility(); + +- let stack = new St.Widget({layout_manager: new Clutter.BinLayout()}); +- this._button.set_child(stack); +- +- this._singleWindowTitle = new St.Bin({ +- x_expand: true, +- }); +- stack.add_child(this._singleWindowTitle); +- +- this._multiWindowTitle = new AppTitle(app); +- stack.add_child(this._multiWindowTitle); +- + this._menuManager = new PopupMenu.PopupMenuManager(this); + this._menu = new PopupMenu.PopupMenu(this, 0.5, St.Side.BOTTOM); + this._menu.connect('open-state-changed', +@@ -594,12 +583,6 @@ class AppButton extends BaseButton { + this._menuManager.addMenu(this._menu); + Main.uiGroup.add_child(this._menu.actor); + +- this._appContextMenu = new AppContextMenu(this); +- this._appContextMenu.connect('open-state-changed', +- this._onMenuStateChanged.bind(this)); +- this._appContextMenu.actor.hide(); +- Main.uiGroup.add_child(this._appContextMenu.actor); +- + this.app.connectObject('windows-changed', + () => this._windowsChanged(), this); + this._windowsChanged(); +@@ -646,37 +629,33 @@ class AppButton extends BaseButton { + } + + _windowsChanged() { +- let windows = this.getWindowList(); +- this._singleWindowTitle.visible = windows.length === 1; +- this._multiWindowTitle.visible = !this._singleWindowTitle.visible; +- +- if (this._singleWindowTitle.visible) { +- if (!this._windowTitle) { +- this.metaWindow = windows[0]; +- this._windowTitle = new WindowTitle(this.metaWindow); +- this._singleWindowTitle.child = this._windowTitle; +- this._windowContextMenu = new WindowContextMenu(this, this.metaWindow); +- this._windowContextMenu.connect( +- 'open-state-changed', this._onMenuStateChanged.bind(this)); +- Main.uiGroup.add_child(this._windowContextMenu.actor); +- this._windowContextMenu.actor.hide(); +- this._contextMenuManager.addMenu(this._windowContextMenu); +- } +- this._contextMenuManager.removeMenu(this._appContextMenu); +- this._contextMenu = this._windowContextMenu; +- this.label_actor = this._windowTitle.label_actor; ++ const windows = this.getWindowList(); ++ const singleWindowMode = windows.length === 1; ++ ++ if (this._singleWindowMode === singleWindowMode) ++ return; ++ ++ this._singleWindowMode = singleWindowMode; ++ ++ this._button.child?.destroy(); ++ this._contextMenu?.destroy(); ++ ++ if (this._singleWindowMode) { ++ const [window] = windows; ++ this._button.child = new WindowTitle(window); ++ this._contextMenu = new WindowContextMenu(this, window); + } else { +- if (this._windowTitle) { +- this.metaWindow = null; +- this._singleWindowTitle.child = null; +- this._windowTitle = null; +- this._windowContextMenu.destroy(); +- this._windowContextMenu = null; +- } +- this._contextMenu = this._appContextMenu; +- this._contextMenuManager.addMenu(this._appContextMenu); +- this.label_actor = this._multiWindowTitle.label_actor; ++ this._button.child = new AppTitle(this.app); ++ this._contextMenu = new AppContextMenu(this); + } ++ ++ this.label_actor = this._button.child.label_actor; ++ ++ this._contextMenu.connect( ++ 'open-state-changed', this._onMenuStateChanged.bind(this)); ++ Main.uiGroup.add_child(this._contextMenu.actor); ++ this._contextMenu.actor.hide(); ++ this._contextMenuManager.addMenu(this._contextMenu); + } + + _onClicked(actor, button) { +-- +2.47.0 + + +From 88d42e3ac829f90a3b3e1b56fcfac483a8563449 Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Florian=20M=C3=BCllner?= +Date: Mon, 7 Oct 2024 17:22:04 +0200 +Subject: [PATCH 05/22] window-list: Fix minimized styling + +Commit 039c66e7b7c wrapped the button in a container to +animate transitions, but didn't adjust the `.minimized` +styling to still apply to the button (where it is +expected) rather than the wrapper. + +Fix this just like commit c72b8b21 did for the +`.focused` styling. + +Part-of: +--- + extensions/window-list/extension.js | 4 ++-- + 1 file changed, 2 insertions(+), 2 deletions(-) + +diff --git a/extensions/window-list/extension.js b/extensions/window-list/extension.js +index 9b9ea7b9..3a8f612a 100644 +--- a/extensions/window-list/extension.js ++++ b/extensions/window-list/extension.js +@@ -478,9 +478,9 @@ class WindowButton extends BaseButton { + super._updateStyle(); + + if (this.metaWindow.minimized) +- this.add_style_class_name('minimized'); ++ this._button.add_style_class_name('minimized'); + else +- this.remove_style_class_name('minimized'); ++ this._button.remove_style_class_name('minimized'); + } + + _windowEnteredOrLeftMonitor(metaDisplay, monitorIndex, metaWindow) { +-- +2.47.0 + + +From ce37919c1e3643363858f67fc749981a0afc1b8d Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Florian=20M=C3=BCllner?= +Date: Mon, 7 Oct 2024 17:10:43 +0200 +Subject: [PATCH 06/22] window-list: Fix active state + +Commit c72b8b21 fixed the styling of the active window's button, +but missed that the `active` property uses the style information +as well. + +Adjust it to use the correct actor when checking for the style class. + +Closes https://gitlab.gnome.org/GNOME/gnome-shell-extensions/-/issues/529 + +Part-of: +--- + extensions/window-list/extension.js | 2 +- + 1 file changed, 1 insertion(+), 1 deletion(-) + +diff --git a/extensions/window-list/extension.js b/extensions/window-list/extension.js +index 3a8f612a..e7a1c777 100644 +--- a/extensions/window-list/extension.js ++++ b/extensions/window-list/extension.js +@@ -252,7 +252,7 @@ class BaseButton extends DashItemContainer { + } + + get active() { +- return this.has_style_class_name('focused'); ++ return this._button.has_style_class_name('focused'); + } + + // eslint-disable-next-line camelcase +-- +2.47.0 + + +From 0c0115c847188838c3132dbba5fc99c7a6052f8e Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Florian=20M=C3=BCllner?= +Date: Tue, 1 Oct 2024 14:52:02 +0200 +Subject: [PATCH 07/22] window-list: Add missing action + +Commit 24ba03fe9 added a new setting, but forgot to create the +corresponding action. + +Part-of: +--- + extensions/window-list/prefs.js | 2 ++ + 1 file changed, 2 insertions(+) + +diff --git a/extensions/window-list/prefs.js b/extensions/window-list/prefs.js +index 194d1f9d..5da645df 100644 +--- a/extensions/window-list/prefs.js ++++ b/extensions/window-list/prefs.js +@@ -30,6 +30,8 @@ class WindowListPrefsWidget extends Adw.PreferencesPage { + this._settings.create_action('show-on-all-monitors')); + this._actionGroup.add_action( + this._settings.create_action('display-all-workspaces')); ++ this._actionGroup.add_action( ++ this._settings.create_action('embed-previews')); + + const groupingGroup = new Adw.PreferencesGroup({ + title: _('Window Grouping'), +-- +2.47.0 + + +From b3401e8354892c82e0198bb8fb4d99210a9fc494 Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Florian=20M=C3=BCllner?= +Date: Tue, 1 Oct 2024 14:46:15 +0200 +Subject: [PATCH 08/22] window-list: Remove superfluous bindings + +The setting is already bound to the switch via the corresponding action, +no need to also set up a binding. + +In fact, the second binding is actively harmful, as it keeps the +connection alive until dispose, so the setting is reset on +garbage collection. + +Closes https://gitlab.gnome.org/GNOME/gnome-shell-extensions/-/issues/511 + +Part-of: +--- + extensions/window-list/prefs.js | 4 ---- + 1 file changed, 4 deletions(-) + +diff --git a/extensions/window-list/prefs.js b/extensions/window-list/prefs.js +index 5da645df..cf56be5b 100644 +--- a/extensions/window-list/prefs.js ++++ b/extensions/window-list/prefs.js +@@ -75,8 +75,6 @@ class WindowListPrefsWidget extends Adw.PreferencesPage { + action_name: 'window-list.display-all-workspaces', + valign: Gtk.Align.CENTER, + }); +- this._settings.bind('display-all-workspaces', +- toggle, 'active', Gio.SettingsBindFlags.DEFAULT); + row = new Adw.ActionRow({ + title: _('Show windows from all workspaces'), + activatable_widget: toggle, +@@ -88,8 +86,6 @@ class WindowListPrefsWidget extends Adw.PreferencesPage { + action_name: 'window-list.embed-previews', + valign: Gtk.Align.CENTER, + }); +- this._settings.bind('embed-previews', +- toggle, 'active', Gio.SettingsBindFlags.DEFAULT); + row = new Adw.ActionRow({ + title: _('Show workspace previews'), + activatable_widget: toggle, +-- +2.47.0 + + +From 101043326755dda2144e9939681e5f6be4ed116d Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Florian=20M=C3=BCllner?= +Date: Tue, 1 Oct 2024 14:55:44 +0200 +Subject: [PATCH 09/22] window-list: Switch to Adw.SwitchRow + +libadwaita fixed the actionable implementation of Adw.SwitchRow, +so can use the convenience widget instead of composing our own. + +Part-of: +--- + extensions/window-list/prefs.js | 27 ++++++--------------------- + 1 file changed, 6 insertions(+), 21 deletions(-) + +diff --git a/extensions/window-list/prefs.js b/extensions/window-list/prefs.js +index cf56be5b..0633d590 100644 +--- a/extensions/window-list/prefs.js ++++ b/extensions/window-list/prefs.js +@@ -60,37 +60,22 @@ class WindowListPrefsWidget extends Adw.PreferencesPage { + const miscGroup = new Adw.PreferencesGroup(); + this.add(miscGroup); + +- let toggle = new Gtk.Switch({ +- action_name: 'window-list.show-on-all-monitors', +- valign: Gtk.Align.CENTER, +- }); +- let row = new Adw.ActionRow({ ++ let row = new Adw.SwitchRow({ + title: _('Show on all monitors'), +- activatable_widget: toggle, ++ action_name: 'window-list.show-on-all-monitors', + }); +- row.add_suffix(toggle); + miscGroup.add(row); + +- toggle = new Gtk.Switch({ +- action_name: 'window-list.display-all-workspaces', +- valign: Gtk.Align.CENTER, +- }); +- row = new Adw.ActionRow({ ++ row = new Adw.SwitchRow({ + title: _('Show windows from all workspaces'), +- activatable_widget: toggle, ++ action_name: 'window-list.display-all-workspaces', + }); +- row.add_suffix(toggle); + miscGroup.add(row); + +- toggle = new Gtk.Switch({ +- action_name: 'window-list.embed-previews', +- valign: Gtk.Align.CENTER, +- }); +- row = new Adw.ActionRow({ ++ row = new Adw.SwitchRow({ + title: _('Show workspace previews'), +- activatable_widget: toggle, ++ action_name: 'window-list.embed-previews', + }); +- row.add_suffix(toggle); + miscGroup.add(row); + } + } +-- +2.47.0 + + +From 1774cead56dcda6abdce8a1e6427dbeb866d7a0d Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Florian=20M=C3=BCllner?= +Date: Tue, 15 Oct 2024 17:48:52 +0200 +Subject: [PATCH 10/22] window-list: Remove outdated style + +A long time ago, the window list used to embed the bottom message +tray, which caused notifications to inherit the window-list's +font style. + +Since that's no longer the case, we have no business in messing +with notification styling, so stop doing that. + +Part-of: +--- + extensions/window-list/stylesheet-dark.css | 4 ---- + 1 file changed, 4 deletions(-) + +diff --git a/extensions/window-list/stylesheet-dark.css b/extensions/window-list/stylesheet-dark.css +index b9087971..f02fca60 100644 +--- a/extensions/window-list/stylesheet-dark.css ++++ b/extensions/window-list/stylesheet-dark.css +@@ -81,7 +81,3 @@ + width: 24px; + height: 24px; + } +- +-.notification { +- font-weight: normal; +-} +-- +2.47.0 + + +From cba87ff1919c3075ee428a73d3870c92cc5e214b Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Florian=20M=C3=BCllner?= +Date: Thu, 26 Sep 2024 19:07:11 +0200 +Subject: [PATCH 11/22] window-list: Split out some common code + +Adding an app button and adding a window button involves some +shared steps, move those to a shared `_addButton()` method. + +Part-of: +--- + extensions/window-list/extension.js | 15 ++++++++------- + 1 file changed, 8 insertions(+), 7 deletions(-) + +diff --git a/extensions/window-list/extension.js b/extensions/window-list/extension.js +index e7a1c777..6eb08da0 100644 +--- a/extensions/window-list/extension.js ++++ b/extensions/window-list/extension.js +@@ -964,14 +964,18 @@ class WindowList extends St.Widget { + this._removeApp(app); + } + +- _addApp(app, animate) { +- let button = new AppButton(app, this._perMonitor, this._monitor.index); ++ _addButton(button, animate) { + this._settings.bind('display-all-workspaces', + button, 'ignore-workspace', Gio.SettingsBindFlags.GET); + this._windowList.add_child(button); + button.show(animate); + } + ++ _addApp(app, animate) { ++ const button = new AppButton(app, this._perMonitor, this._monitor.index); ++ this._addButton(button, animate); ++ } ++ + _removeApp(app) { + let children = this._windowList.get_children(); + let child = children.find(c => c.app === app); +@@ -992,11 +996,8 @@ class WindowList extends St.Widget { + this._windowSignals.set( + win, win.connect('unmanaged', () => this._removeWindow(win))); + +- let button = new WindowButton(win, this._perMonitor, this._monitor.index); +- this._settings.bind('display-all-workspaces', +- button, 'ignore-workspace', Gio.SettingsBindFlags.GET); +- this._windowList.add_child(button); +- button.show(animate); ++ const button = new WindowButton(win, this._perMonitor, this._monitor.index); ++ this._addButton(button, animate); + } + + _removeWindow(win) { +-- +2.47.0 + + +From 02f295445a3383d2cf346860fefeace65be28c2c Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Florian=20M=C3=BCllner?= +Date: Thu, 3 Oct 2024 17:19:31 +0200 +Subject: [PATCH 12/22] window-list: Split out common TitleWidget class + +Both app- and window title use the same structure, so add a shared +base class. + +Part-of: +--- + extensions/window-list/extension.js | 61 +++++++++++++++-------------- + 1 file changed, 32 insertions(+), 29 deletions(-) + +diff --git a/extensions/window-list/extension.js b/extensions/window-list/extension.js +index 6eb08da0..383d0b72 100644 +--- a/extensions/window-list/extension.js ++++ b/extensions/window-list/extension.js +@@ -105,25 +105,42 @@ class WindowContextMenu extends PopupMenu.PopupMenu { + } + } + +-class WindowTitle extends St.BoxLayout { ++class TitleWidget extends St.BoxLayout { + static { +- GObject.registerClass(this); ++ GObject.registerClass({ ++ GTypeFlags: GObject.TypeFlags.ABSTRACT, ++ }, this); + } + +- constructor(metaWindow) { ++ constructor() { + super({ + style_class: 'window-button-box', + x_expand: true, + y_expand: true, + }); + +- this._metaWindow = metaWindow; +- +- this._icon = new St.Bin({style_class: 'window-button-icon'}); ++ this._icon = new St.Bin({ ++ style_class: 'window-button-icon', ++ }); + this.add_child(this._icon); +- this.label_actor = new St.Label({y_align: Clutter.ActorAlign.CENTER}); +- this.label_actor.clutter_text.single_line_mode = true; +- this.add_child(this.label_actor); ++ ++ this._label = new St.Label({ ++ y_align: Clutter.ActorAlign.CENTER, ++ }); ++ this.add_child(this._label); ++ this.label_actor = this._label; ++ } ++} ++ ++class WindowTitle extends TitleWidget { ++ static { ++ GObject.registerClass(this); ++ } ++ ++ constructor(metaWindow) { ++ super(); ++ ++ this._metaWindow = metaWindow; + + this._metaWindow.connectObject( + 'notify::wm-class', +@@ -148,9 +165,9 @@ class WindowTitle extends St.BoxLayout { + return; + + if (this._metaWindow.minimized) +- this.label_actor.text = '[%s]'.format(this._metaWindow.title); ++ this._label.text = '[%s]'.format(this._metaWindow.title); + else +- this.label_actor.text = this._metaWindow.title; ++ this._label.text = this._metaWindow.title; + } + + _updateIcon() { +@@ -166,32 +183,18 @@ class WindowTitle extends St.BoxLayout { + } + } + +-class AppTitle extends St.BoxLayout { ++class AppTitle extends TitleWidget { + static { + GObject.registerClass(this); + } + + constructor(app) { +- super({ +- style_class: 'window-button-box', +- x_expand: true, +- y_expand: true, +- }); ++ super(); + + this._app = app; + +- const icon = new St.Bin({ +- style_class: 'window-button-icon', +- child: app.create_icon_texture(ICON_TEXTURE_SIZE), +- }); +- this.add_child(icon); +- +- let label = new St.Label({ +- text: app.get_name(), +- y_align: Clutter.ActorAlign.CENTER, +- }); +- this.add_child(label); +- this.label_actor = label; ++ this._icon.child = app.create_icon_texture(ICON_TEXTURE_SIZE); ++ this._label.text = app.get_name(); + } + } + +-- +2.47.0 + + +From 426f72d1991316b927495ca7385d95b15022da77 Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Florian=20M=C3=BCllner?= +Date: Thu, 3 Oct 2024 17:27:57 +0200 +Subject: [PATCH 13/22] window-list: Add TitleWidget:abstract-label property + +When true, the real label is replaced by a more abstract +representation. When used as drag actor, the focus is not +on identifying the window/app, but about picking a drop +location, and the reduced style helps with that. + +Part-of: +--- + extensions/window-list/extension.js | 22 ++++++++++++++++++++++ + extensions/window-list/stylesheet-dark.css | 6 ++++++ + 2 files changed, 28 insertions(+) + +diff --git a/extensions/window-list/extension.js b/extensions/window-list/extension.js +index 383d0b72..3ed1c357 100644 +--- a/extensions/window-list/extension.js ++++ b/extensions/window-list/extension.js +@@ -109,6 +109,12 @@ class TitleWidget extends St.BoxLayout { + static { + GObject.registerClass({ + GTypeFlags: GObject.TypeFlags.ABSTRACT, ++ Properties: { ++ 'abstract-label': GObject.ParamSpec.boolean( ++ 'abstract-label', null, null, ++ GObject.ParamFlags.READWRITE, ++ false), ++ }, + }, this); + } + +@@ -129,6 +135,22 @@ class TitleWidget extends St.BoxLayout { + }); + this.add_child(this._label); + this.label_actor = this._label; ++ ++ this.bind_property('abstract-label', ++ this._label, 'visible', ++ GObject.BindingFlags.SYNC_CREATE | ++ GObject.BindingFlags.INVERT_BOOLEAN); ++ ++ this._abstractLabel = new St.Widget({ ++ style_class: 'window-button-abstract-label', ++ x_expand: true, ++ y_expand: true, ++ }); ++ this.add_child(this._abstractLabel); ++ ++ this.bind_property('abstract-label', ++ this._abstractLabel, 'visible', ++ GObject.BindingFlags.SYNC_CREATE); + } + } + +diff --git a/extensions/window-list/stylesheet-dark.css b/extensions/window-list/stylesheet-dark.css +index f02fca60..fce6bcc5 100644 +--- a/extensions/window-list/stylesheet-dark.css ++++ b/extensions/window-list/stylesheet-dark.css +@@ -81,3 +81,9 @@ + width: 24px; + height: 24px; + } ++ ++.window-button-abstract-label { ++ background-color: #888; ++ border-radius: 99px; ++ margin: 6px; ++} +-- +2.47.0 + + +From 509e41d89e4f7d661ec9a749e04d583e1affada5 Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Florian=20M=C3=BCllner?= +Date: Wed, 25 Sep 2024 03:20:52 +0200 +Subject: [PATCH 14/22] window-list: Split out `_createTitleActor()` hook + +This will allow creating a suitable drag actor that matches the +current title. In particular this allows for a drag actor that +isn't based on `ClutterClone`, and therefore doesn't inherit +focus/active/minimize/etc. styles that don't make sense outside +the actual window list. + +Part-of: +--- + extensions/window-list/extension.js | 23 ++++++++++++++++++++--- + 1 file changed, 20 insertions(+), 3 deletions(-) + +diff --git a/extensions/window-list/extension.js b/extensions/window-list/extension.js +index 3ed1c357..21823cf8 100644 +--- a/extensions/window-list/extension.js ++++ b/extensions/window-list/extension.js +@@ -357,6 +357,11 @@ class BaseButton extends DashItemContainer { + this._onClicked(this, 1); + } + ++ _createTitleActor() { ++ throw new GObject.NotImplementedError( ++ `_createTitleActor in ${this.constructor.name}`); ++ } ++ + _onClicked(_actor, _button) { + throw new GObject.NotImplementedError( + `_onClicked in ${this.constructor.name}`); +@@ -467,7 +472,7 @@ class WindowButton extends BaseButton { + + this._updateVisibility(); + +- this._windowTitle = new WindowTitle(this.metaWindow); ++ this._windowTitle = this._createTitleActor(); + this._button.set_child(this._windowTitle); + this.label_actor = this._windowTitle.label_actor; + +@@ -483,6 +488,10 @@ class WindowButton extends BaseButton { + this._updateStyle(); + } + ++ _createTitleActor() { ++ return new WindowTitle(this.metaWindow); ++ } ++ + _onClicked(actor, button) { + if (this._contextMenu.isOpen) { + this._contextMenu.close(); +@@ -667,13 +676,12 @@ class AppButton extends BaseButton { + + if (this._singleWindowMode) { + const [window] = windows; +- this._button.child = new WindowTitle(window); + this._contextMenu = new WindowContextMenu(this, window); + } else { +- this._button.child = new AppTitle(this.app); + this._contextMenu = new AppContextMenu(this); + } + ++ this._button.child = this._createTitleActor(); + this.label_actor = this._button.child.label_actor; + + this._contextMenu.connect( +@@ -683,6 +691,15 @@ class AppButton extends BaseButton { + this._contextMenuManager.addMenu(this._contextMenu); + } + ++ _createTitleActor() { ++ if (this._singleWindowMode) { ++ const [window] = this.getWindowList(); ++ return new WindowTitle(window); ++ } else { ++ return new AppTitle(this.app); ++ } ++ } ++ + _onClicked(actor, button) { + let menuWasOpen = this._menu.isOpen; + if (menuWasOpen) +-- +2.47.0 + + +From 62c8cd20e2c425909ff361ec05cba4e847c12886 Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Florian=20M=C3=BCllner?= +Date: Wed, 19 Jun 2024 13:01:37 +0200 +Subject: [PATCH 15/22] window-list: Rename XDND related methods and props + +The window list buttons themselves will become draggable, so +include "xdnd" in the existing drag handling to disambiguate +it. + +Part-of: +--- + extensions/window-list/extension.js | 20 ++++++++++---------- + 1 file changed, 10 insertions(+), 10 deletions(-) + +diff --git a/extensions/window-list/extension.js b/extensions/window-list/extension.js +index 21823cf8..d765f58f 100644 +--- a/extensions/window-list/extension.js ++++ b/extensions/window-list/extension.js +@@ -866,12 +866,12 @@ class WindowList extends St.Widget { + 'window-created', (dsp, win) => this._addWindow(win, true), this); + + Main.xdndHandler.connectObject( +- 'drag-begin', () => this._monitorDrag(), +- 'drag-end', () => this._stopMonitoringDrag(), ++ 'drag-begin', () => this._monitorXdndDrag(), ++ 'drag-end', () => this._stopMonitoringXdndDrag(), + this); + +- this._dragMonitor = { +- dragMotion: this._onDragMotion.bind(this), ++ this._xdndDragMonitor = { ++ dragMotion: this._onXdndDragMotion.bind(this), + }; + + this._dndTimeoutId = 0; +@@ -1059,16 +1059,16 @@ class WindowList extends St.Widget { + child?.animateOutAndDestroy(); + } + +- _monitorDrag() { +- DND.addDragMonitor(this._dragMonitor); ++ _monitorXdndDrag() { ++ DND.addDragMonitor(this._xdndDragMonitor); + } + +- _stopMonitoringDrag() { +- DND.removeDragMonitor(this._dragMonitor); ++ _stopMonitoringXdndDrag() { ++ DND.removeDragMonitor(this._xdndDragMonitor); + this._removeActivateTimeout(); + } + +- _onDragMotion(dragEvent) { ++ _onXdndDragMotion(dragEvent) { + if (Main.overview.visible || + !this.contains(dragEvent.targetActor)) { + this._removeActivateTimeout(); +@@ -1116,7 +1116,7 @@ class WindowList extends St.Widget { + this._windowSignals.forEach((id, win) => win.disconnect(id)); + this._windowSignals.clear(); + +- this._stopMonitoringDrag(); ++ this._stopMonitoringXdndDrag(); + + this._settings.disconnectObject(); + this._settings = null; +-- +2.47.0 + + +From d85fae629186c66d7fb2ebf665ee185b8f8763ae Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Florian=20M=C3=BCllner?= +Date: Wed, 25 Sep 2024 04:13:25 +0200 +Subject: [PATCH 16/22] window-list: Allow rearranging window buttons + +We currently sort buttons by the stable sequence to get a persistent +and predictable order. However some users want to customize that +order, and rearrange the buttons as they see fit. + +Support that use case by implementing drag-and-drop behavior based +on the overview's dash. + +Closes https://gitlab.gnome.org/GNOME/gnome-shell-extensions/issues/4 + +Part-of: +--- + extensions/window-list/extension.js | 147 ++++++++++++++++++++ + extensions/window-list/stylesheet-dark.css | 14 +- + extensions/window-list/stylesheet-light.css | 5 + + 3 files changed, 164 insertions(+), 2 deletions(-) + +diff --git a/extensions/window-list/extension.js b/extensions/window-list/extension.js +index d765f58f..bd361646 100644 +--- a/extensions/window-list/extension.js ++++ b/extensions/window-list/extension.js +@@ -26,12 +26,25 @@ import {WorkspaceIndicator} from './workspaceIndicator.js'; + const ICON_TEXTURE_SIZE = 24; + const DND_ACTIVATE_TIMEOUT = 500; + ++const MIN_DRAG_UPDATE_INTERVAL = 500 * GLib.TIME_SPAN_MILLISECOND; ++ + const GroupingMode = { + NEVER: 0, + AUTO: 1, + ALWAYS: 2, + }; + ++class DragPlaceholderItem extends DashItemContainer { ++ static { ++ GObject.registerClass(this); ++ } ++ ++ constructor() { ++ super(); ++ this.setChild(new St.Bin({style_class: 'placeholder'})); ++ } ++} ++ + /** + * @param {Shell.App} app - an app + * @returns {number} - the smallest stable sequence of the app's windows +@@ -220,6 +233,22 @@ class AppTitle extends TitleWidget { + } + } + ++class DragActor extends St.Bin { ++ static { ++ GObject.registerClass(this); ++ } ++ ++ constructor(source, titleActor) { ++ super({ ++ style_class: 'window-button-drag-actor', ++ child: titleActor, ++ width: source.width, ++ }); ++ ++ this.source = source; ++ } ++} ++ + class BaseButton extends DashItemContainer { + static { + GObject.registerClass({ +@@ -230,6 +259,10 @@ class BaseButton extends DashItemContainer { + GObject.ParamFlags.READWRITE, + false), + }, ++ Signals: { ++ 'drag-begin': {}, ++ 'drag-end': {}, ++ }, + }, this); + } + +@@ -274,6 +307,15 @@ class BaseButton extends DashItemContainer { + this._windowEnteredOrLeftMonitor.bind(this), + this); + } ++ ++ this._button._delegate = this; ++ this._draggable = DND.makeDraggable(this._button); ++ this._draggable.connect('drag-begin', () => { ++ this._removeLongPressTimeout(); ++ this.emit('drag-begin'); ++ }); ++ this._draggable.connect('drag-cancelled', () => this.emit('drag-end')); ++ this._draggable.connect('drag-end', () => this.emit('drag-end')); + } + + get active() { +@@ -357,6 +399,17 @@ class BaseButton extends DashItemContainer { + this._onClicked(this, 1); + } + ++ getDragActor() { ++ const titleActor = this._createTitleActor(); ++ titleActor.set({abstractLabel: true}); ++ ++ return new DragActor(this, titleActor); ++ } ++ ++ getDragActorSource() { ++ return this; ++ } ++ + _createTitleActor() { + throw new GObject.NotImplementedError( + `_createTitleActor in ${this.constructor.name}`); +@@ -874,9 +927,19 @@ class WindowList extends St.Widget { + dragMotion: this._onXdndDragMotion.bind(this), + }; + ++ this._itemDragMonitor = { ++ dragMotion: this._onItemDragMotion.bind(this), ++ }; ++ + this._dndTimeoutId = 0; + this._dndWindow = null; + ++ this._dragPlaceholder = null; ++ this._dragPlaceholderPos = -1; ++ this._lastPlaceholderUpdate = 0; ++ ++ this._delegate = this; ++ + this._settings = settings; + this._settings.connectObject('changed::grouping-mode', + () => this._groupingModeChanged(), this); +@@ -1009,6 +1072,14 @@ class WindowList extends St.Widget { + _addButton(button, animate) { + this._settings.bind('display-all-workspaces', + button, 'ignore-workspace', Gio.SettingsBindFlags.GET); ++ ++ button.connect('drag-begin', ++ () => this._monitorItemDrag()); ++ button.connect('drag-end', () => { ++ this._stopMonitoringItemDrag(); ++ this._clearDragPlaceholder(); ++ }); ++ + this._windowList.add_child(button); + button.show(animate); + } +@@ -1059,6 +1130,82 @@ class WindowList extends St.Widget { + child?.animateOutAndDestroy(); + } + ++ _clearDragPlaceholder() { ++ this._dragPlaceholder?.animateOutAndDestroy(); ++ this._dragPlaceholder = null; ++ this._dragPlaceholderPos = -1; ++ } ++ ++ handleDragOver(source, _actor, x, _y, _time) { ++ if (!(source instanceof BaseButton)) ++ return DND.DragMotionResult.NO_DROP; ++ ++ const buttons = this._windowList.get_children().filter(c => c instanceof BaseButton); ++ const buttonPos = buttons.indexOf(source); ++ const numButtons = buttons.length; ++ let boxWidth = this._windowList.width; ++ ++ // Transform to window list coordinates for index calculation ++ // (mostly relevant for RTL to discard workspace indicator etc.) ++ x -= this._windowList.x; ++ ++ const rtl = this.text_direction === Clutter.TextDirection.RTL; ++ let pos = rtl ++ ? numButtons - Math.round(x * numButtons / boxWidth) ++ : Math.round(x * numButtons / boxWidth); ++ ++ pos = Math.clamp(pos, 0, numButtons); ++ ++ const timeDelta = ++ GLib.get_monotonic_time() - this._lastPlaceholderUpdate; ++ ++ if (pos !== this._dragPlaceholderPos && timeDelta >= MIN_DRAG_UPDATE_INTERVAL) { ++ this._clearDragPlaceholder(); ++ this._dragPlaceholderPos = pos; ++ ++ this._lastPlaceholderUpdate = GLib.get_monotonic_time(); ++ ++ // Don't allow positioning before or after self ++ if (pos === buttonPos || pos === buttonPos + 1) ++ return DND.DragMotionResult.CONTINUE; ++ ++ this._dragPlaceholder = new DragPlaceholderItem(); ++ const sibling = buttons[pos] ?? null; ++ if (sibling) ++ this._windowList.insert_child_below(this._dragPlaceholder, sibling); ++ else ++ this._windowList.insert_child_above(this._dragPlaceholder, null); ++ this._dragPlaceholder.show(true); ++ } ++ ++ return this._dragPlaceholder ++ ? DND.DragMotionResult.MOVE_DROP ++ : DND.DragMotionResult.NO_DROP; ++ } ++ ++ acceptDrop(source, _actor, _x, _y, _time) { ++ if (this._dragPlaceholderPos >= 0) ++ this._windowList.set_child_at_index(source, this._dragPlaceholderPos); ++ ++ this._clearDragPlaceholder(); ++ ++ return true; ++ } ++ ++ _monitorItemDrag() { ++ DND.addDragMonitor(this._itemDragMonitor); ++ } ++ ++ _stopMonitoringItemDrag() { ++ DND.removeDragMonitor(this._itemDragMonitor); ++ } ++ ++ _onItemDragMotion(dragEvent) { ++ if (!this._windowList.contains(dragEvent.targetActor)) ++ this._clearDragPlaceholder(); ++ return DND.DragMotionResult.CONTINUE; ++ } ++ + _monitorXdndDrag() { + DND.addDragMonitor(this._xdndDragMonitor); + } +diff --git a/extensions/window-list/stylesheet-dark.css b/extensions/window-list/stylesheet-dark.css +index fce6bcc5..c92081d2 100644 +--- a/extensions/window-list/stylesheet-dark.css ++++ b/extensions/window-list/stylesheet-dark.css +@@ -17,10 +17,19 @@ + height: 2.45em; + } + +-.window-button { ++.window-button, ++.window-button-drag-actor { + padding: 4px, 3px; + } + ++.window-button-drag-actor { ++ background-color: #444; ++ border-radius: 7px; ++ border-width: 2px; ++ border-color: #fff; ++ box-shadow: 0 1px 2px rgba(0,0,0,0.1); ++} ++ + .window-button:first-child:ltr { + padding-left: 2px; + } +@@ -41,7 +50,8 @@ + transition: 100ms ease; + } + +-.window-button > StWidget { ++.window-button > StWidget, ++.window-list .placeholder { + -st-natural-width: 18.75em; + max-width: 18.75em; + } +diff --git a/extensions/window-list/stylesheet-light.css b/extensions/window-list/stylesheet-light.css +index f9c51f8e..5fb39b2f 100644 +--- a/extensions/window-list/stylesheet-light.css ++++ b/extensions/window-list/stylesheet-light.css +@@ -56,3 +56,8 @@ + color: #aaa; + background-color: #f9f9f9; + } ++ ++.window-button-drag-actor { ++ background-color: #ddd; ++ border-color: #888; ++} +-- +2.47.0 + + +From fc37241b994da3a846f5d7457e4fd21dd2fcf855 Mon Sep 17 00:00:00 2001 +From: Jakub Steiner +Date: Thu, 3 Oct 2024 14:18:32 +0200 +Subject: [PATCH 17/22] window-list: Indicate drop target more prominently + +The drop target is the main focus of the drag operation, so make +its styling more prominent. + +Part-of: +--- + extensions/window-list/stylesheet-dark.css | 6 ++++++ + extensions/window-list/stylesheet-light.css | 5 ++++- + 2 files changed, 10 insertions(+), 1 deletion(-) + +diff --git a/extensions/window-list/stylesheet-dark.css b/extensions/window-list/stylesheet-dark.css +index c92081d2..4c06ebc0 100644 +--- a/extensions/window-list/stylesheet-dark.css ++++ b/extensions/window-list/stylesheet-dark.css +@@ -56,6 +56,12 @@ + max-width: 18.75em; + } + ++.window-list .placeholder { ++ border: 1px solid rgba(255,255,255,0.4); ++ border-radius: 7px; ++ margin: 4px; ++} ++ + .window-button:hover > StWidget { + background-color: #303030; + } +diff --git a/extensions/window-list/stylesheet-light.css b/extensions/window-list/stylesheet-light.css +index 5fb39b2f..1ecb83a9 100644 +--- a/extensions/window-list/stylesheet-light.css ++++ b/extensions/window-list/stylesheet-light.css +@@ -23,7 +23,6 @@ + + .window-button > StWidget { + color: #000; +- background-color: transparent; + } + + .window-button:hover > StWidget { +@@ -61,3 +60,7 @@ + background-color: #ddd; + border-color: #888; + } ++ ++.window-list .placeholder { ++ border-color: rgba(0,0,0,0.5); ++} +-- +2.47.0 + + +From e8daa4529a03c863588230fbac55bedf4c821ac3 Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Florian=20M=C3=BCllner?= +Date: Thu, 3 Oct 2024 17:05:42 +0200 +Subject: [PATCH 18/22] window-list: Fade out drag source during drag + +During a drag operation, the focus is on the where to drop the dragged +item, not to identify it or its origin. + +Part-of: +--- + extensions/window-list/extension.js | 18 ++++++++++++++++-- + 1 file changed, 16 insertions(+), 2 deletions(-) + +diff --git a/extensions/window-list/extension.js b/extensions/window-list/extension.js +index bd361646..7825710f 100644 +--- a/extensions/window-list/extension.js ++++ b/extensions/window-list/extension.js +@@ -28,6 +28,9 @@ const DND_ACTIVATE_TIMEOUT = 500; + + const MIN_DRAG_UPDATE_INTERVAL = 500 * GLib.TIME_SPAN_MILLISECOND; + ++const DRAG_OPACITY = 0.3; ++const DRAG_FADE_DURATION = 200; ++ + const GroupingMode = { + NEVER: 0, + AUTO: 1, +@@ -1073,9 +1076,20 @@ class WindowList extends St.Widget { + this._settings.bind('display-all-workspaces', + button, 'ignore-workspace', Gio.SettingsBindFlags.GET); + +- button.connect('drag-begin', +- () => this._monitorItemDrag()); ++ button.connect('drag-begin', () => { ++ button.ease({ ++ opacity: 255 * DRAG_OPACITY, ++ duration: DRAG_FADE_DURATION, ++ }); ++ ++ this._monitorItemDrag(); ++ }); + button.connect('drag-end', () => { ++ button.ease({ ++ opacity: 255, ++ duration: DRAG_FADE_DURATION, ++ }); ++ + this._stopMonitoringItemDrag(); + this._clearDragPlaceholder(); + }); +-- +2.47.0 + + +From 58e06bd93f886b31ae24cb6871738dfdd9f60e85 Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Florian=20M=C3=BCllner?= +Date: Wed, 9 Oct 2024 19:15:16 +0200 +Subject: [PATCH 19/22] window-list: Shrink drag-actor size during drags + +Like the previous commit, this helps with putting the focus on +the target location instead of the dragged item. + +Part-of: +--- + extensions/window-list/extension.js | 33 +++++++++++++++++++++++++++-- + 1 file changed, 31 insertions(+), 2 deletions(-) + +diff --git a/extensions/window-list/extension.js b/extensions/window-list/extension.js +index 7825710f..43395d1c 100644 +--- a/extensions/window-list/extension.js ++++ b/extensions/window-list/extension.js +@@ -31,6 +31,8 @@ const MIN_DRAG_UPDATE_INTERVAL = 500 * GLib.TIME_SPAN_MILLISECOND; + const DRAG_OPACITY = 0.3; + const DRAG_FADE_DURATION = 200; + ++const DRAG_RESIZE_DURATION = 400; ++ + const GroupingMode = { + NEVER: 0, + AUTO: 1, +@@ -250,6 +252,24 @@ class DragActor extends St.Bin { + + this.source = source; + } ++ ++ setTargetWidth(width) { ++ const currentWidth = this.width; ++ ++ // set width immediately so shell's DND code uses correct values ++ this.set({width}); ++ ++ // then transition from the original to the new width ++ const laters = global.compositor.get_laters(); ++ laters.add(Meta.LaterType.BEFORE_REDRAW, () => { ++ this.set({width: currentWidth}); ++ this.ease({ ++ width, ++ duration: DRAG_RESIZE_DURATION, ++ }); ++ return GLib.SOURCE_REMOVE; ++ }); ++ } + } + + class BaseButton extends DashItemContainer { +@@ -317,7 +337,10 @@ class BaseButton extends DashItemContainer { + this._removeLongPressTimeout(); + this.emit('drag-begin'); + }); +- this._draggable.connect('drag-cancelled', () => this.emit('drag-end')); ++ this._draggable.connect('drag-cancelled', () => { ++ this._draggable._dragActor?.setTargetWidth(this.width); ++ this.emit('drag-end'); ++ }); + this._draggable.connect('drag-end', () => this.emit('drag-end')); + } + +@@ -406,7 +429,13 @@ class BaseButton extends DashItemContainer { + const titleActor = this._createTitleActor(); + titleActor.set({abstractLabel: true}); + +- return new DragActor(this, titleActor); ++ const dragActor = new DragActor(this, titleActor); ++ ++ const [, natWidth] = this.get_preferred_width(-1); ++ const targetWidth = Math.min(natWidth / 2, this.width); ++ dragActor.setTargetWidth(targetWidth); ++ ++ return dragActor; + } + + getDragActorSource() { +-- +2.47.0 + + +From be0234ced2cb32e7ae253097d5ba8a285ca78c5d Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Florian=20M=C3=BCllner?= +Date: Tue, 8 Oct 2024 19:25:53 +0200 +Subject: [PATCH 20/22] window-list: Handle DND events near the drop target + +Even with the previous change, the dragged actor has the tendency +of obscuring the possible drop target. To alleviate this, handle +DND events near drop targets as if they occurred on the target. + +Part-of: +--- + extensions/window-list/extension.js | 26 ++++++++++++++++++++++++-- + 1 file changed, 24 insertions(+), 2 deletions(-) + +diff --git a/extensions/window-list/extension.js b/extensions/window-list/extension.js +index 43395d1c..169b5518 100644 +--- a/extensions/window-list/extension.js ++++ b/extensions/window-list/extension.js +@@ -33,6 +33,8 @@ const DRAG_FADE_DURATION = 200; + + const DRAG_RESIZE_DURATION = 400; + ++const DRAG_PROXIMITY_THRESHOLD = 30; ++ + const GroupingMode = { + NEVER: 0, + AUTO: 1, +@@ -961,6 +963,7 @@ class WindowList extends St.Widget { + + this._itemDragMonitor = { + dragMotion: this._onItemDragMotion.bind(this), ++ dragDrop: this._onItemDragDrop.bind(this), + }; + + this._dndTimeoutId = 0; +@@ -1244,11 +1247,30 @@ class WindowList extends St.Widget { + } + + _onItemDragMotion(dragEvent) { +- if (!this._windowList.contains(dragEvent.targetActor)) +- this._clearDragPlaceholder(); ++ const {source, targetActor, dragActor, x, y} = dragEvent; ++ ++ const hasTarget = this._windowList.contains(targetActor); ++ const isNear = Math.abs(y - this.y) < DRAG_PROXIMITY_THRESHOLD; ++ ++ if (hasTarget || isNear) ++ return this.handleDragOver(source, dragActor, x, y); ++ ++ this._clearDragPlaceholder(); + return DND.DragMotionResult.CONTINUE; + } + ++ _onItemDragDrop(dropEvent) { ++ if (this._dragPlaceholderPos < 0) ++ return DND.DragDropResult.CONTINUE; ++ ++ const {source} = dropEvent.dropActor; ++ this.acceptDrop(source); ++ dropEvent.dropActor.destroy(); ++ // HACK: SUCESS would make more sense, but results in gnome-shell ++ // skipping all drag-end code ++ return DND.DragDropResult.CONTINUE; ++ } ++ + _monitorXdndDrag() { + DND.addDragMonitor(this._xdndDragMonitor); + } +-- +2.47.0 + + +From 3e707e6fa6ee4c23e3f6f220a93fdbc651757763 Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Florian=20M=C3=BCllner?= +Date: Wed, 26 Jun 2024 00:58:18 +0200 +Subject: [PATCH 21/22] window-list: Add `id` property to buttons + +A string ID that uniquely identifies a button will allow to +serialize/deserialize the positions in the next commit. + +Part-of: +--- + extensions/window-list/extension.js | 8 ++++++++ + 1 file changed, 8 insertions(+) + +diff --git a/extensions/window-list/extension.js b/extensions/window-list/extension.js +index 169b5518..b5be15e6 100644 +--- a/extensions/window-list/extension.js ++++ b/extensions/window-list/extension.js +@@ -575,6 +575,10 @@ class WindowButton extends BaseButton { + this._updateStyle(); + } + ++ get id() { ++ return `window:${this.metaWindow.get_id()}`; ++ } ++ + _createTitleActor() { + return new WindowTitle(this.metaWindow); + } +@@ -714,6 +718,10 @@ class AppButton extends BaseButton { + this._updateStyle(); + } + ++ get id() { ++ return `app:${this.app.get_id()}`; ++ } ++ + _windowEnteredOrLeftMonitor(metaDisplay, monitorIndex, metaWindow) { + if (this._windowTracker.get_window_app(metaWindow) === this.app && + monitorIndex === this._monitorIndex) { +-- +2.47.0 + + +From f6f3176f3fb004a5410de83ee1aceae7e594150f Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Florian=20M=C3=BCllner?= +Date: Tue, 24 Sep 2024 20:31:06 +0200 +Subject: [PATCH 22/22] window-list: Save and restore positions as runtime + state + +While it doesn't make sense for window list positions to be truly +persistent like dash items, some persistence is desirable. + +Otherwise any manually set position is lost when the extension +is disabled, for example when locking the screen. + +To address this, serialize the positions as runtime state on drop, +and restore them when populating the list. + +Part-of: +--- + extensions/window-list/extension.js | 28 ++++++++++++++++++++++++++++ + 1 file changed, 28 insertions(+) + +diff --git a/extensions/window-list/extension.js b/extensions/window-list/extension.js +index b5be15e6..885bb5ac 100644 +--- a/extensions/window-list/extension.js ++++ b/extensions/window-list/extension.js +@@ -35,6 +35,8 @@ const DRAG_RESIZE_DURATION = 400; + + const DRAG_PROXIMITY_THRESHOLD = 30; + ++const SAVED_POSITIONS_KEY = 'window-list-positions'; ++ + const GroupingMode = { + NEVER: 0, + AUTO: 1, +@@ -1095,6 +1097,8 @@ class WindowList extends St.Widget { + for (let i = 0; i < apps.length; i++) + this._addApp(apps[i], false); + } ++ ++ this._restorePositions(); + } + + _updateKeyboardAnchor() { +@@ -1243,9 +1247,33 @@ class WindowList extends St.Widget { + + this._clearDragPlaceholder(); + ++ this._savePositions(); ++ + return true; + } + ++ _getPositionStateKey() { ++ return `${SAVED_POSITIONS_KEY}:${this._monitor.index}`; ++ } ++ ++ _savePositions() { ++ const buttons = this._windowList.get_children() ++ .filter(b => b instanceof BaseButton); ++ global.set_runtime_state(this._getPositionStateKey(), ++ new GLib.Variant('as', buttons.map(b => b.id))); ++ } ++ ++ _restorePositions() { ++ const positions = global.get_runtime_state('as', ++ this._getPositionStateKey())?.deepUnpack() ?? []; ++ ++ for (const button of this._windowList.get_children()) { ++ const pos = positions.indexOf(button.id); ++ if (pos > -1) ++ this._windowList.set_child_at_index(button, pos); ++ } ++ } ++ + _monitorItemDrag() { + DND.addDragMonitor(this._itemDragMonitor); + } +-- +2.47.0 + diff --git a/SPECS/gnome-shell-extensions.spec b/SPECS/gnome-shell-extensions.spec new file mode 100644 index 0000000..1c1e466 --- /dev/null +++ b/SPECS/gnome-shell-extensions.spec @@ -0,0 +1,1212 @@ +## START: Set by rpmautospec +## (rpmautospec version 0.6.5) +## RPMAUTOSPEC: autorelease, autochangelog +%define autorelease(e:s:pb:n) %{?-p:0.}%{lua: + release_number = 2; + base_release_number = tonumber(rpm.expand("%{?-b*}%{!?-b:1}")); + print(release_number + base_release_number - 1); +}%{?-e:.%{-e*}}%{?-s:.%{-s*}}%{!?-n:%{?dist}} +## END: Set by rpmautospec + +# Minimum GNOME Shell version supported +%global min_gs_version %%(cut -d "." -f 1 <<<%{version}) + +%global pkg_prefix gnome-shell-extension +%global tarball_version %%(echo %{version} | tr '~' '.') +%global major_version %%(cut -d "." -f 1 <<<%{tarball_version}) + +%if 0%{?rhel} +%global xsession 0 +%else +%global xsession 1 +%endif + +Name: gnome-shell-extensions +Version: 47.0 +Release: %autorelease +Summary: Modify and extend GNOME Shell functionality and behavior + +License: GPL-2.0-or-later +URL: http://wiki.gnome.org/Projects/GnomeShell/Extensions +Source0: http://ftp.gnome.org/pub/GNOME/sources/%{name}/%{major_version}/%{name}-%{tarball_version}.tar.xz + +BuildRequires: meson +BuildRequires: git +BuildRequires: gettext >= 0.19.6 +BuildRequires: glib2%{?_isa} +Requires: gnome-shell >= %{min_gs_version} +BuildArch: noarch + + +Patch: extra-extensions-0001-Add-gesture-inhibitor-extension.patch +Patch: extra-extensions-0002-Add-classification-banner.patch +Patch: extra-extensions-0003-Add-heads-up-display.patch +Patch: extra-extensions-0004-Add-custom-menu-extension.patch +Patch: extra-extensions-0005-Add-desktop-icons-extension.patch + +Patch: 0001-Include-status-icons-in-classic-session.patch + +Patch: window-list-reordering.patch + +%description +GNOME Shell Extensions is a collection of extensions providing additional and +optional functionality to GNOME Shell. + +Enabled extensions: + * apps-menu + * auto-move-windows + * classification-banner + * custom-menu + * desktop-icons + * drive-menu + * gesture-inhibitor + * heads-up-display + * launch-new-instance + * light-style + * native-window-placement + * places-menu + * screenshot-window-sizer + * status-icons + * system-monitor + * user-theme + * window-list + * windowsNavigator + * workspace-indicator + + +%package -n %{pkg_prefix}-common +Summary: Files common to GNOME Shell Extensions +License: GPL-2.0-or-later +Requires: gnome-shell >= %{min_gs_version} +Obsoletes: %{pkg_prefix}-horizontal-workspaces < 40.0~alpha.1-3 + +%description -n %{pkg_prefix}-common +GNOME Shell Extensions is a collection of extensions providing additional and +optional functionality to GNOME Shell. + +This package provides common data files shared by various extensions. + + +%package -n gnome-classic-session +Summary: GNOME "classic" mode session +License: GPL-2.0-or-later +Requires: %{pkg_prefix}-apps-menu = %{version}-%{release} +Requires: %{pkg_prefix}-launch-new-instance = %{version}-%{release} +Requires: %{pkg_prefix}-places-menu = %{version}-%{release} +Requires: %{pkg_prefix}-window-list = %{version}-%{release} +Requires: nautilus + +%description -n gnome-classic-session +This package contains the required components for the GNOME Shell "classic" +mode, which aims to provide a GNOME 2-like user interface. + + +%if %{xsession} +%package -n gnome-classic-session-xsession +Summary: GNOME "classic" mode session on X11 +License: GPL-2.0-or-later +Requires: gnome-classic-session = %{version}-%{release} +# The X11 session is deprecated and eventually will be removed +Provides: deprecated() + +%description -n gnome-classic-session-xsession +This package contains the required components for the GNOME Shell "classic" +mode on X11, which aims to provide a GNOME 2-like user interface. +%endif + + +%package -n %{pkg_prefix}-apps-menu +Summary: Application menu for GNOME Shell +License: GPL-2.0-or-later +Requires: %{pkg_prefix}-common = %{version}-%{release} +Requires: gnome-menus + +%description -n %{pkg_prefix}-apps-menu +This GNOME Shell extension adds a GNOME 2.x style menu for applications. + + +%package -n %{pkg_prefix}-auto-move-windows +Summary: Assign specific workspaces to applications in GNOME Shell +License: GPL-2.0-or-later +Requires: %{pkg_prefix}-common = %{version}-%{release} + +%description -n %{pkg_prefix}-auto-move-windows +This GNOME Shell extension enables easy workspace management. A specific +workspace can be assigned to each application as soon as it creates a window, in +a manner configurable with a GSettings key. + + +%package -n %{pkg_prefix}-classification-banner +Summary: Display classification level banner in GNOME Shell +Group: User Interface/Desktops +License: GPLv2+ +Requires: %{pkg_prefix}-common = %{version}-%{release} + +%description -n %{pkg_prefix}-classification-banner +This GNOME Shell extension adds a banner that displays the classification level. + + +%package -n %{pkg_prefix}-custom-menu +Summary: Add a custom menu to the desktop +Group: User Interface/Desktops +License: GPLv2+ +Requires: %{pkg_prefix}-common = %{version}-%{release} + +%description -n %{pkg_prefix}-custom-menu +This GNOME Shell extension adds a custom menu to the desktop background. + + +%package -n %{pkg_prefix}-desktop-icons +Summary: Desktop icons support for GNOME Shell (stub) +License: GPL-2.0-or-later +Requires: %{pkg_prefix}-common = %{version}-%{release} + +%description -n %{pkg_prefix}-desktop-icons +This GNOME Shell extension provides support for icons on the desktop. + + +%package -n %{pkg_prefix}-drive-menu +Summary: Drive status menu for GNOME Shell +License: GPL-2.0-or-later +Requires: %{pkg_prefix}-common = %{version}-%{release} + +%description -n %{pkg_prefix}-drive-menu +This GNOME Shell extension provides a panel status menu for accessing and +unmounting removable devices. + + +%package -n %{pkg_prefix}-gesture-inhibitor +Summary: Gesture inhibitor +Group: User Interface/Desktops +License: GPLv2+ +Requires: %{pkg_prefix}-common = %{version}-%{release} + +%description -n %{pkg_prefix}-gesture-inhibitor +This GNOME Shell extension allows disabling the default desktop gestures. + + +%package -n %{pkg_prefix}-heads-up-display +Summary: Display persistent on-screen message +Group: User Interface/Desktops +License: GPLv3+ +Requires: %{pkg_prefix}-common = %{version}-%{release} + +%description -n %{pkg_prefix}-heads-up-display +This GNOME Shell extension displays a persistent message in the top middle of the screen. +This message can appear on the login screen, lock screen, or regular user session. + + +%package -n %{pkg_prefix}-launch-new-instance +Summary: Always launch a new application instance for GNOME Shell +License: GPL-2.0-or-later +Requires: %{pkg_prefix}-common = %{version}-%{release} + +%description -n %{pkg_prefix}-launch-new-instance +This GNOME Shell extension modifies the behavior of clicking in the dash and app +launcher to always launch a new application instance. + + +%package -n %{pkg_prefix}-light-style +Summary: Use light style in GNOME Shell +License: GPL-2.0-or-later +Requires: %{pkg_prefix}-common = %{version}-%{release} + +%description -n %{pkg_prefix}-light-style +This GNOME Shell extension changes the default style to light. + + +%package -n %{pkg_prefix}-native-window-placement +Summary: Native window placement for GNOME Shell +License: GPL-2.0-or-later +Requires: %{pkg_prefix}-common = %{version}-%{release} + +%description -n %{pkg_prefix}-native-window-placement +This GNOME Shell extension provides additional configurability for the window +layout in the overview, including a mechanism similar to KDE4. + + +%package -n %{pkg_prefix}-places-menu +Summary: Places status menu for GNOME Shell +License: GPL-2.0-or-later +Requires: %{pkg_prefix}-common = %{version}-%{release} + +%description -n %{pkg_prefix}-places-menu +This GNOME Shell extension add a system status menu for quickly navigating +places in the system. + + +%package -n %{pkg_prefix}-screenshot-window-sizer +Summary: Screenshot window sizer for GNOME Shell +License: GPL-2.0-or-later +Requires: %{pkg_prefix}-common = %{version}-%{release} + +%description -n %{pkg_prefix}-screenshot-window-sizer +This GNOME Shell extension allows to easily resize windows for GNOME Software +screenshots. + + +%package -n %{pkg_prefix}-status-icons +Summary: Status icon support for GNOME Shell +License: GPLv2+ +Requires: %{pkg_prefix}-common = %{version}-%{release} +Provides: %{pkg_prefix}-top-icons = %{version}-%{release} +Obsoletes: %{pkg_prefix}-top-icons < 47~beta-1 + +%description -n %{pkg_prefix}-status-icons +This GNOME Shell extension displays status icons in the top bar. + + +%package -n %{pkg_prefix}-system-monitor +Summary: System monitor for GNOME Shell +License: GPL-2.0-or-later +Requires: %{pkg_prefix}-common = %{version}-%{release} + +%description -n %{pkg_prefix}-system-monitor +This GNOME Shell extension displays system usage information in the top bar. + + +%package -n %{pkg_prefix}-user-theme +Summary: Support for custom themes in GNOME Shell +License: GPL-2.0-or-later +Requires: %{pkg_prefix}-common = %{version}-%{release} + +%description -n %{pkg_prefix}-user-theme +This GNOME Shell extension enables loading a GNOME Shell theme from +~/.themes//gnome-shell/. + + +%package -n %{pkg_prefix}-window-list +Summary: Display a window list at the bottom of the screen in GNOME Shell +License: GPL-2.0-or-later +Requires: %{pkg_prefix}-common = %{version}-%{release} + +%description -n %{pkg_prefix}-window-list +This GNOME Shell extension displays a window list at the bottom of the screen. + + +%package -n %{pkg_prefix}-windowsNavigator +Summary: Support for keyboard selection of windows and workspaces in GNOME Shell +License: GPL-2.0-or-later +Requires: %{pkg_prefix}-common = %{version}-%{release} + +%description -n %{pkg_prefix}-windowsNavigator +This GNOME Shell extension enables keyboard selection of windows and workspaces +in overlay mode, by pressing the Alt and Ctrl key respectively. + + +%package -n %{pkg_prefix}-workspace-indicator +Summary: Workspace indicator for GNOME Shell +License: GPL-2.0-or-later +Requires: %{pkg_prefix}-common = %{version}-%{release} + +%description -n %{pkg_prefix}-workspace-indicator +This GNOME Shell extension add a system status menu for quickly changing +workspaces. + + +%prep +%autosetup -S git -n %{name}-%{tarball_version} + + +%build +%meson -Dextension_set="all" -Dclassic_mode=true +%meson_build + + +%install +%meson_install + +%find_lang %{name} + +%if !%{xsession} +rm -rf %{buildroot}/%{_datadir}/xsessions +%endif + + +%files -n %{pkg_prefix}-common -f %{name}.lang +%doc NEWS README.md +%license COPYING + + +%files -n gnome-classic-session +%{_datadir}/gnome-shell/modes/classic.json +%{_datadir}/wayland-sessions/gnome-classic.desktop +%{_datadir}/wayland-sessions/gnome-classic-wayland.desktop +%{_datadir}/glib-2.0/schemas/00_org.gnome.shell.extensions.classic.gschema.override + + +%if %{xsession} +%files -n gnome-classic-session-xsession +%{_datadir}/xsessions/gnome-classic.desktop +%{_datadir}/xsessions/gnome-classic-xorg.desktop +%endif + + +%files -n %{pkg_prefix}-apps-menu +%{_datadir}/glib-2.0/schemas/org.gnome.shell.extensions.apps-menu.gschema.xml +%{_datadir}/gnome-shell/extensions/apps-menu*/ + + +%files -n %{pkg_prefix}-auto-move-windows +%{_datadir}/glib-2.0/schemas/org.gnome.shell.extensions.auto-move-windows.gschema.xml +%{_datadir}/gnome-shell/extensions/auto-move-windows*/ + +%files -n %{pkg_prefix}-classification-banner +%{_datadir}/glib-2.0/schemas/org.gnome.shell.extensions.classification-banner.gschema.xml +%{_datadir}/gnome-shell/extensions/classification-banner*/ + + +%files -n %{pkg_prefix}-custom-menu +%{_datadir}/gnome-shell/extensions/custom-menu*/ + + +%files -n %{pkg_prefix}-desktop-icons +%{_datadir}/gnome-shell/extensions/desktop-icons*/ + + +%files -n %{pkg_prefix}-drive-menu +%{_datadir}/gnome-shell/extensions/drive-menu*/ + + +%files -n %{pkg_prefix}-gesture-inhibitor +%{_datadir}/glib-2.0/schemas/org.gnome.shell.extensions.gesture-inhibitor.gschema.xml +%{_datadir}/gnome-shell/extensions/gesture-inhibitor*/ + + +%files -n %{pkg_prefix}-heads-up-display +%{_datadir}/glib-2.0/schemas/org.gnome.shell.extensions.heads-up-display.gschema.xml +%{_datadir}/gnome-shell/extensions/heads-up-display*/ + + +%files -n %{pkg_prefix}-launch-new-instance +%{_datadir}/gnome-shell/extensions/launch-new-instance*/ + + +%files -n %{pkg_prefix}-light-style +%{_datadir}/gnome-shell/extensions/light-style*/ + + +%files -n %{pkg_prefix}-native-window-placement +%{_datadir}/glib-2.0/schemas/org.gnome.shell.extensions.native-window-placement.gschema.xml +%{_datadir}/gnome-shell/extensions/native-window-placement*/ + + +%files -n %{pkg_prefix}-places-menu +%{_datadir}/gnome-shell/extensions/places-menu*/ + + +%files -n %{pkg_prefix}-screenshot-window-sizer +%{_datadir}/glib-2.0/schemas/org.gnome.shell.extensions.screenshot-window-sizer.gschema.xml +%{_datadir}/gnome-shell/extensions/screenshot-window-sizer*/ + + +%files -n %{pkg_prefix}-status-icons +%{_datadir}/gnome-shell/extensions/status-icons*/ + + +%files -n %{pkg_prefix}-system-monitor +%{_datadir}/glib-2.0/schemas/org.gnome.shell.extensions.system-monitor.gschema.xml +%{_datadir}/gnome-shell/extensions/system-monitor*/ + + +%files -n %{pkg_prefix}-user-theme +%{_datadir}/glib-2.0/schemas/org.gnome.shell.extensions.user-theme.gschema.xml +%{_datadir}/gnome-shell/extensions/user-theme*/ + + +%files -n %{pkg_prefix}-window-list +%{_datadir}/gnome-shell/extensions/window-list*/ +%{_datadir}/glib-2.0/schemas/org.gnome.shell.extensions.window-list.gschema.xml + + +%files -n %{pkg_prefix}-windowsNavigator +%{_datadir}/gnome-shell/extensions/windowsNavigator*/ + + +%files -n %{pkg_prefix}-workspace-indicator +%{_datadir}/gnome-shell/extensions/workspace-indicator*/ +%{_datadir}/glib-2.0/schemas/org.gnome.shell.extensions.workspace-indicator.gschema.xml + + +%changelog +## START: Generated by rpmautospec +* Thu Oct 17 2024 Florian Müllner - 47.0-2 +- Allow reordering items in window-list + +* Mon Sep 16 2024 Florian Müllner - 47.0-1 +- Update to 47.0 + +* Tue Sep 10 2024 Florian Müllner - 47~rc-2 +- Adjust to "top-icons" name change + +* Tue Sep 03 2024 Florian Müllner - 47~rc-1 +- Update to 47.rc + +* Thu Aug 22 2024 Florian Müllner - 47~alpha-3 +- Add desktop-icons stub + +* Tue Jul 30 2024 Florian Müllner - 47~alpha-2 +- Adjust classification banner for GNOME 47 changes + +* Tue Jul 23 2024 Florian Müllner - 47~alpha-1 +- Update to 47.alpha + +* Tue Jul 02 2024 Florian Müllner - 46.2-4 +- Extend workspace buttons to screen edge + +* Mon Jul 01 2024 Florian Müllner - 46.2-3 +- Conditionalize Xorg session + +* Mon Jun 24 2024 Troy Dawson - 46.2-2 +- Bump release for June 2024 mass rebuild + +* Mon May 27 2024 Florian Müllner - 46.2-1 +- Update to 46.2 + +* Wed May 15 2024 Florian Müllner - 46.1-3 +- Re-apply downstream patches + +* Thu May 02 2024 Tomas Pelka - 46.1-2 +- Add gating.yaml via API + +* Mon Apr 22 2024 Florian Müllner - 46.1-1 +- Pick up changes from F40 + +* Wed Apr 10 2024 Florian Müllner - 46.0-1 +- Backport F40 changes + +* Wed Jan 24 2024 Fedora Release Engineering - 46~alpha-3 +- Rebuilt for https://fedoraproject.org/wiki/Fedora_40_Mass_Rebuild + +* Fri Jan 19 2024 Fedora Release Engineering - 46~alpha-2 +- Rebuilt for https://fedoraproject.org/wiki/Fedora_40_Mass_Rebuild + +* Sun Jan 07 2024 Florian Müllner - 46~alpha-1 +- Update to 46.alpha + +* Sat Dec 02 2023 Florian Müllner - 45.2-1 +- Update to 45.2 + +* Tue Oct 31 2023 Florian Müllner - 45.1-1 +- Update to 45.1 + +* Sat Sep 16 2023 Florian Müllner - 45.0-1 +- Update to 45.0 + +* Wed Sep 06 2023 Adam Williamson - 45~rc-2 +- Rebuild on a side tag to create combined update + +* Wed Sep 06 2023 Florian Müllner - 45~rc-1 +- Update to 45.rc + +* Mon Aug 07 2023 Florian Müllner - 45~beta-1 +- Update to 45.beta + +* Wed Jul 19 2023 Fedora Release Engineering - 45~alpha-2 +- Rebuilt for https://fedoraproject.org/wiki/Fedora_39_Mass_Rebuild + +* Thu Jul 06 2023 Florian Müllner - 45~alpha-1 +- Update to 45.alpha + +* Sun Mar 19 2023 Florian Müllner - 44.0-1 +- Update to 44.0 + +* Mon Mar 06 2023 Florian Müllner - 44~rc-1 +- Update to 44.rc + +* Tue Feb 14 2023 Florian Müllner - 44~beta-1 +- Update to 44.beta + +* Thu Jan 19 2023 Fedora Release Engineering - 43.1-3 +- Rebuilt for https://fedoraproject.org/wiki/Fedora_38_Mass_Rebuild + +* Fri Oct 28 2022 Florian Müllner - 43.1-2 +- Adjust gnome-shell dependency + +* Wed Oct 26 2022 Florian Müllner - 43.1-1 +- Update to 43.1 + +* Sat Sep 17 2022 Florian Müllner - 43.0-1 +- Update to 43.0 + +* Sun Sep 04 2022 Florian Müllner - 43~rc-1 +- Update to 43.rc + +* Wed Aug 10 2022 Florian Müllner - 43~beta-1 +- Update to 43.beta + +* Thu Jul 21 2022 Fedora Release Engineering - 43~alpha-2 +- Rebuilt for https://fedoraproject.org/wiki/Fedora_37_Mass_Rebuild + +* Sun Jul 10 2022 Florian Müllner - 43~alpha-1 +- Update to 43.alpha + +* Sat May 28 2022 Florian Müllner - 42.2-1 +- Update to 42.2 + +* Fri May 06 2022 Florian Müllner - 42.1-1 +- Update to 42.1 + +* Sun Mar 13 2022 Florian Müllner - 42.0-1 +- Update to 42.0 + +* Mon Mar 07 2022 Florian Müllner - 42~rc-1 +- Update to 42.rc + +* Tue Feb 15 2022 Florian Müllner - 42~beta-1 +- Update to 42.beta + +* Thu Jan 20 2022 Fedora Release Engineering - 42~alpha-2 +- Rebuilt for https://fedoraproject.org/wiki/Fedora_36_Mass_Rebuild + +* Tue Jan 11 2022 Florian Müllner - 42~alpha-1 +- Update to 42.alpha + +* Fri Oct 29 2021 Neal Gompa - 41.0-2 +- Backport GNOME Classic session for Wayland (#2015741) + +* Sun Sep 19 2021 Florian Müllner - 41.0-1 +- Update to 41.0 + +* Mon Sep 06 2021 Florian Müllner - 41~rc.1-1 +- Update to 41.rc.1 + +* Sun Sep 05 2021 Florian Müllner - 41~rc-1 +- Update to 41.rc + +* Wed Aug 18 2021 Florian Müllner - 41~beta-1 +- Update to 41.beta + +* Thu Jul 22 2021 Fedora Release Engineering - 40.3-2 +- Rebuilt for https://fedoraproject.org/wiki/Fedora_35_Mass_Rebuild + +* Mon Jul 12 2021 Florian Müllner - 40.3-1 +- Update to 40.3 + +* Thu Jun 10 2021 Florian Müllner - 40.2-1 +- Update to 40.2 + +* Thu May 13 2021 Florian Müllner - 40.1-1 +- Update to 40.1 + +* Sat Mar 20 2021 Florian Müllner - 40.0-1 +- Update to 40.0 + +* Mon Mar 15 2021 Florian Müllner - 40.rc-1 +- Update to 40.rc + +* Wed Feb 24 2021 Florian Müllner - 40.beta-1 +- Update to 40.beta + +* Thu Feb 18 2021 Kalev Lember - 40.0~alpha.1-6.20210212git9fa522c +- Fix typo in horizontal-workspaces obsoletes package name + +* Thu Feb 18 2021 Kalev Lember - 40.0~alpha.1-5.20210212git9fa522c +- Fix obsoletes version + +* Mon Feb 15 2021 Mohamed El Morabity - 40.0~alpha.1-4.20210212git9fa522c +- Add Obsoletes for horizontal-workspaces extension to fix upgrades to Fedora 34 + (RHBZ #1928415) + +* Fri Feb 12 2021 Florian Müllner - 40.0~alpha.1-3.20210212git9fa522c +- Build snapshot of current upstream +- Drop horizontal-workspaces subpackage + (removed upstream, because horizontal workspaces are the default now) + +* Tue Jan 26 2021 Fedora Release Engineering - 40.0~alpha.1-2 +- Rebuilt for https://fedoraproject.org/wiki/Fedora_34_Mass_Rebuild + +* Fri Jan 15 2021 Florian Müllner - 40.0~alpha.1-1 +- Update to 40.alpha.1 + +* Wed Dec 02 2020 Florian Müllner - 40.0~alpha-1 +- Update to 40.alpha + +* Mon Oct 05 2020 Florian Müllner - 3.38.1-1 +- Update to 3.38.1 + +* Mon Sep 14 2020 Florian Müllner - 3.38.0-1 +- Update to 3.38.0 + +* Sun Sep 06 2020 Florian Müllner - 3.37.92-1 +- Update to 3.37.92 + +* Mon Aug 24 2020 Florian Müllner - 3.37.91-1 +- Update to 3.37.91 + +* Wed Aug 19 2020 Kalev Lember - 3.37.90-2 +- Rebuild + +* Tue Aug 11 2020 Florian Müllner - 3.37.90-1 +- Update to 3.37.90 + +* Mon Jul 27 2020 Fedora Release Engineering - 3.37.3-2 +- Rebuilt for https://fedoraproject.org/wiki/Fedora_33_Mass_Rebuild + +* Tue Jul 07 2020 Florian Müllner - 3.37.3-1 +- Update to 3.37.3 + +* Wed Jun 03 2020 Florian Müllner - 3.37.2-1 +- Update to 3.37.2 + +* Thu Apr 30 2020 Florian Müllner - 3.37.1-1 +- Update to 3.37.1 + +* Tue Mar 31 2020 Florian Müllner - 3.36.1-1 +- Update to 3.36.1 + +* Sat Mar 07 2020 Florian Müllner - 3.36.0-1 +- Update to 3.36.0 + +* Sun Mar 01 2020 Florian Müllner - 3.35.92-1 +- Update to 3.35.92 + +* Tue Feb 18 2020 Florian Müllner - 3.35.91-1 +- Update to 3.35.91 + +* Thu Feb 06 2020 Florian Müllner - 3.35.90-1 +- Update to 3.35.90 + +* Tue Jan 28 2020 Fedora Release Engineering - 3.35.3-2 +- Rebuilt for https://fedoraproject.org/wiki/Fedora_32_Mass_Rebuild + +* Sun Jan 05 2020 Florian Müllner - 3.35.3-1 +- Update to 3.35.3 + +* Wed Dec 11 2019 Florian Müllner - 3.35.2-1 +- Update to 3.35.2 + +* Wed Oct 09 2019 Florian Müllner - 3.34.1-1 +- Update to 3.34.1 + +* Wed Sep 25 2019 Debarshi Ray - 3.34.0-2 +- Unbreak the 'classic' GNOME session + +* Mon Sep 09 2019 Florian Müllner - 3.34.0-1 +- Update to 3.34.0 + +* Wed Sep 04 2019 Florian Müllner - 3.33.92-1 +- Update to 3.33.92 + +* Wed Aug 21 2019 Florian Müllner - 3.33.91-1 +- Update to 3.33.91 + +* Sat Aug 10 2019 Florian Müllner - 3.33.90-1 +- Update to 3.33.90 + +* Thu Jul 25 2019 Fedora Release Engineering - 3.33.4-2 +- Rebuilt for https://fedoraproject.org/wiki/Fedora_31_Mass_Rebuild + +* Sat Jul 20 2019 Florian Müllner - 3.33.4-1 +- Update to 3.33.4 + +* Mon Jun 24 2019 Florian Müllner - 3.33.3-1 +- Update to 3.33.3 + +* Wed May 22 2019 Florian Müllner - 3.33.2-1 +- Update to 3.33.2 + +* Tue May 14 2019 Florian Müllner - 3.33.1-1 +- Update to 3.33.1 + +* Wed Apr 17 2019 Florian Müllner - 3.32.1-1 +- Update to 3.32.1 + +* Tue Mar 12 2019 Florian Müllner - 3.32.0-1 +- Update to 3.32.0 + +* Tue Mar 05 2019 Florian Müllner - 3.31.92-1 +- Update to 3.31.92 + +* Thu Feb 21 2019 Florian Müllner - 3.31.91-1 +- Update to 3.31.91 + +* Thu Feb 07 2019 Florian Müllner - 3.31.90-1 +- Update to 3.31.90 + +* Thu Jan 31 2019 Fedora Release Engineering - 3.31.2-3 +- Rebuilt for https://fedoraproject.org/wiki/Fedora_30_Mass_Rebuild + +* Wed Nov 21 2018 Mohamed El Morabity - 3.31.2-2 +- Fix alternate-tab extension Obsoletes tag (RHBZ #1650519) + +* Wed Nov 14 2018 Florian Müllner - 3.31.2-1 +- Update to 3.31.2 + +* Mon Oct 08 2018 Florian Müllner - 3.30.1-1 +- Update to 3.30.1 + +* Tue Sep 04 2018 Florian Müllner - 3.30.0-1 +- Update to 3.30.0 + +* Mon Aug 20 2018 Florian Müllner - 3.29.91-1 +- Update to 3.29.91 + +* Wed Aug 01 2018 Florian Müllner - 3.29.90-1 +- Update to 3.29.90 + +* Fri Jul 13 2018 Fedora Release Engineering - 3.29.2-2 +- Rebuilt for https://fedoraproject.org/wiki/Fedora_29_Mass_Rebuild + +* Thu May 24 2018 Florian Müllner - 3.29.2-1 +- Update to 3.29.2 + +* Fri Apr 13 2018 Florian Müllner - 3.28.1-1 +- Update to 3.28.1 + +* Mon Mar 12 2018 Florian Müllner - 3.28.0-1 +- Update to 3.28.0 + +* Mon Mar 05 2018 Florian Müllner - 3.27.92-1 +- Update to 3.27.92 + +* Thu Feb 22 2018 Florian Müllner - 3.27.91-1 +- Update to 3.27.91 + +* Wed Feb 07 2018 Fedora Release Engineering - 3.27.1-3 +- Rebuilt for https://fedoraproject.org/wiki/Fedora_28_Mass_Rebuild + +* Sat Jan 06 2018 Igor Gnatenko - 3.27.1-2 +- Remove obsolete scriptlets + +* Tue Oct 17 2017 Florian Müllner - 3.27.1-1 +- Update to 3.27.1 + +* Wed Oct 04 2017 Florian Müllner - 3.26.1-1 +- Update to 3.26.1 + +* Tue Sep 12 2017 Florian Müllner - 3.26.0-1 +- Update to 3.26.0 + +* Tue Aug 22 2017 Florian Müllner - 3.25.91-1 +- Update to 3.25.91 + +* Fri Aug 11 2017 Kevin Fenzi - 3.25.90-2 +- Rebuild with older working rpm + +* Thu Aug 10 2017 Florian Müllner - 3.25.90-1 +- Update to 3.25.90 + +* Wed Jul 26 2017 Fedora Release Engineering - 3.25.4-2 +- Rebuilt for https://fedoraproject.org/wiki/Fedora_27_Mass_Rebuild + +* Thu Jul 20 2017 Florian Müllner - 3.25.4-1 +- Update to 3.25.4 + +* Wed Jun 21 2017 Florian Müllner - 3.25.3-1 +- Update to 3.25.3 + +* Thu May 25 2017 Florian Müllner - 3.25.2-1 +- Update to 3.25.2 + +* Thu Apr 27 2017 Florian Müllner - 3.25.1-1 +- Update to 3.25.1 + +* Tue Apr 11 2017 Florian Müllner - 3.24.1-1 +- Update to 3.24.1 + +* Mon Mar 20 2017 Florian Müllner - 3.24.0-1 +- Update to 3.24.0 + +* Tue Mar 14 2017 Florian Müllner - 3.23.92-1 +- Update to 3.23.92 + +* Wed Mar 01 2017 Florian Müllner - 3.23.91-1 +- Update to 3.23.91 + +* Thu Feb 16 2017 Florian Müllner - 3.23.90-1 +- Update to 3.23.90 + +* Fri Feb 10 2017 Fedora Release Engineering - 3.23.2-2 +- Rebuilt for https://fedoraproject.org/wiki/Fedora_26_Mass_Rebuild + +* Wed Nov 23 2016 Florian Müllner - 3.23.2-1 +- Update to 3.23.2 + +* Tue Oct 11 2016 Florian Müllner - 3.22.1-1 +- Update to 3.22.1 + +* Mon Sep 19 2016 Florian Müllner - 3.22.0-1 +- Update to 3.22.0 + +* Tue Sep 13 2016 Florian Müllner - 3.21.92-1 +- Update to 3.21.92 + +* Tue Aug 30 2016 Florian Müllner - 3.21.91-1 +- Update to 3.21.91 + +* Fri Aug 19 2016 Florian Müllner - 3.21.90-1 +- Update to 3.21.90 + +* Wed Jul 20 2016 Florian Müllner - 3.21.4-1 +- Update to 3.21.4 + +* Tue Jun 21 2016 Florian Müllner - 3.21.3-1 +- Update to 3.21.3 + +* Fri May 27 2016 Florian Müllner - 3.21.2-1 +- Update to 3.21.2 + +* Tue May 10 2016 Florian Müllner - 3.20.1-1 +- Update to 3.20.1 + +* Tue Mar 22 2016 Florian Müllner - 3.20.0-1 +- Update to 3.20.0 + +* Wed Mar 16 2016 Florian Müllner - 3.19.92-1 +- Update to 3.19.92 + +* Thu Mar 03 2016 Florian Müllner - 3.19.91-1 +- Update to 3.19.91 + +* Fri Feb 19 2016 Florian Müllner - 3.19.90-1 +- Update to 3.19.90 + +* Wed Feb 03 2016 Fedora Release Engineering - 3.19.4-2 +- Rebuilt for https://fedoraproject.org/wiki/Fedora_24_Mass_Rebuild + +* Thu Jan 21 2016 Florian Müllner - 3.19.4-1 +- Update to 3.19.4 + +* Thu Dec 17 2015 Florian Müllner - 3.19.3-1 +- Update to 3.19.3 + +* Wed Nov 25 2015 Florian Müllner - 3.19.2-1 +- Update to 3.19.2 + +* Thu Oct 29 2015 Florian Müllner - 3.19.1-1 +- Update to 3.19.1 + +* Thu Oct 15 2015 Florian Müllner - 3.18.1-1 +- Update to 3.18.1 + +* Mon Sep 21 2015 Florian Müllner - 3.18.0-1 +- Update to 3.18.0 + +* Wed Sep 16 2015 Florian Müllner - 3.17.92-1 +- Update to 3.17.92 + +* Thu Sep 03 2015 Florian Müllner - 3.17.91-1 +- Update to 3.17.91 + +* Thu Aug 20 2015 Florian Müllner - 3.17.90-1 +- Update to 3.17.90 + +* Wed Aug 19 2015 Kalev Lember - 3.17.4-2 +- Don't own /usr/share/gnome-shell/extensions directory: now part of + gnome-shell package + +* Thu Jul 23 2015 Florian Müllner - 3.17.4-1 +- Update to 3.17.4 + +* Thu Jul 02 2015 Florian Müllner - 3.17.3-1 +- Update to 3.17.3 + +* Wed Jun 17 2015 Fedora Release Engineering - 3.17.2-2 +- Rebuilt for https://fedoraproject.org/wiki/Fedora_23_Mass_Rebuild + +* Wed May 27 2015 Florian Müllner - 3.17.2-1 +- Update to 3.17.2 + +* Fri May 01 2015 Kalev Lember - 3.17.1-2 +- Add glib-compile-schemas rpm scripts for screenshot-window-sizer + +* Thu Apr 30 2015 Florian Müllner - 3.17.1-1 +- Update to 3.17.1 + +* Tue Apr 14 2015 Florian Müllner - 3.16.1-1 +- Update to 3.16.1 + +* Mon Mar 23 2015 Florian Müllner - 3.16.0-1 +- Update to 3.16.0 + +* Tue Mar 17 2015 Florian Müllner - 3.15.92-1 +- Update to 3.15.92 + +* Thu Mar 05 2015 Kalev Lember - 3.15.91-2 +- Obsolete the systemMonitor extension that was dropped in 3.15.91 + +* Thu Mar 05 2015 Florian Müllner - 3.15.91-1 +- Update to 3.15.91 + +* Fri Feb 20 2015 Florian Müllner - 3.15.90-1 +- Update to 3.15.90 + +* Wed Jan 21 2015 Florian Müllner - 3.15.4-1 +- Update to 3.15.4 + +* Fri Dec 19 2014 Florian Müllner - 3.15.3.1-1 +- Update to 3.15.3.1 + +* Fri Dec 19 2014 Florian Müllner - 3.15.3-1 +- Update to 3.15.3 + +* Thu Nov 27 2014 Florian Müllner - 3.15.2-1 +- Update to 3.15.2 + +* Thu Oct 30 2014 Florian Müllner - 3.15.1-1 +- Update to 3.15.1 + +* Tue Oct 14 2014 Florian Müllner - 3.14.1-1 +- Update to 3.14.1 + +* Mon Sep 22 2014 Florian Müllner - 3.14.0-1 +- Update to 3.14.0 + +* Wed Sep 17 2014 Florian Müllner - 3.13.92-1 +- Update to 3.13.92 + +* Wed Sep 03 2014 Florian Müllner - 3.13.91-1 +- Update to 3.13.91 + +* Wed Aug 20 2014 Mohamed El Morabity - 3.13.90-1 +- Update to 3.13.90 + +* Thu Jul 24 2014 Kalev Lember - 3.13.4-1 +- Update to 3.13.4 + +* Thu Jun 26 2014 Richard Hughes - 3.13.3-1 +- Update to 3.13.3 + +* Sat Jun 07 2014 Fedora Release Engineering - 3.13.2-2 +- Rebuilt for https://fedoraproject.org/wiki/Fedora_21_Mass_Rebuild + +* Wed May 28 2014 Mohamed El Morabity - 3.13.2-1 +- Update to 3.13.2 + +* Fri May 02 2014 Kalev Lember - 3.13.1-1 +- Update to 3.13.1 + +* Tue Mar 25 2014 Richard Hughes - 3.12.0-1 +- Update to 3.12.0 + +* Thu Mar 20 2014 Mohamed El Morabity - 3.11.92-1 +- Update to 3.11.92 + +* Thu Mar 06 2014 Mohamed El Morabity - 3.11.91-1 +- Update to 3.11.91 + +* Thu Feb 20 2014 Mohamed El Morabity - 3.11.90-1 +- Update to 3.11.90 + +* Wed Feb 05 2014 Mohamed El Morabity - 3.11.5-1 +- Update to 3.11.5 + +* Mon Feb 03 2014 Mohamed El Morabity - 3.11.4-1 +- Update to 3.11.4 + +* Sun Dec 22 2013 Mohamed El Morabity - 3.11.3-1 +- Update to 3.11.3 + +* Wed Nov 13 2013 Mohamed El Morabity - 3.11.2-1 +- Update to 3.11.2 + +* Wed Oct 16 2013 Mohamed El Morabity - 3.10.1-1 +- Update to 3.10.1 + +* Tue Sep 24 2013 Mohamed El Morabity - 3.10.0-1 +- Update to 3.10.0 + +* Tue Sep 17 2013 Mohamed El Morabity - 3.9.92-1 +- Update to 3.9.92 + +* Tue Sep 03 2013 Mohamed El Morabity - 3.9.91-1 +- Update to 3.9.91 + +* Thu Aug 22 2013 Mohamed El Morabity - 3.9.90-1 +- Update to 3.9.90 +- Drop xrand-indicator subpackage, no longer provided upstream + +* Mon Aug 12 2013 Mohamed El Morabity - 3.9.5-3 +- Fix alternative-status-menu subpackage obsoleting + +* Mon Aug 12 2013 Nils Philippsen - 3.9.5-2 +- obsolete alternative-status-menu subpackage to allow smooth upgrades + +* Sun Aug 04 2013 Mohamed El Morabity - 3.9.5-1 +- Update to 3.9.5 +- Drop alternative-status-menu subpackage, no longer provided upstream + +* Sat Aug 03 2013 Fedora Release Engineering - 3.9.3-2 +- Rebuilt for https://fedoraproject.org/wiki/Fedora_20_Mass_Rebuild + +* Thu Jun 20 2013 Rahul Sundaram - 3.9.3-1 +- Update to 3.9.3 +- Obsolete default-min-max and static workspaces extensions +- Use make_install macro +- Fix bogus dates in spec changelog + +* Tue May 28 2013 Mohamed El Morabity - 3.9.2-1 +- Update to 3.9.2 + +* Fri May 10 2013 Mohamed El Morabity - 3.9.1-1 +- Update to 3.9.1 + +* Fri May 10 2013 Kalev Lember - 3.8.1-3 +- Obsolete gnome-applet-sensors + +* Wed May 01 2013 Kalev Lember - 3.8.1-2 +- Obsolete a few more fallback mode packages +- Remove gnome-panel provides + +* Tue Apr 16 2013 Matthias Clasen - 3.8.1-1 +- Update to 3.8.1 + +* Tue Mar 26 2013 Mohamed El Morabity - 3.8.0-1 +- Update to 3.8.0 + +* Tue Mar 19 2013 Ray Strode 3.7.92-1 +- Update to 3.7.92 + +* Tue Mar 05 2013 Mohamed El Morabity - 3.7.91-1 +- Update to 3.7.91 + +* Sat Mar 02 2013 Adel Gadllah - 3.7.90-2 +- Obsolete gnome-panel + +* Fri Feb 22 2013 Kalev Lember - 3.7.90-1 +- Update to 3.7.90 + +* Thu Feb 07 2013 Kalev Lember - 3.7.5.1-2 +- Depend on gnome-shell 3.7.5, there's no 3.7.5.1 + +* Thu Feb 07 2013 Mohamed El Morabity - 3.7.5.1-1 +- Update to 3.7.5 +- Enable new launch-new-instance and window-list extensions, and add them in the + classic-mode extension set +- Re-add places-menu in the classic-mode extension set + +* Wed Jan 16 2013 Mohamed El Morabity - 3.7.4-1 +- Update to 3.7.4 +- places-menu extension no longer part of the classic-mode extension set + +* Tue Jan 01 2013 Mohamed El Morabity - 3.7.3-1 +- Update to 3.7.3 +- Enable new default-min-max and static-workspaces extensions +- Provide new subpackage gnome-classic-session +- Revamp summaries and descriptions + +* Tue Oct 30 2012 Mohamed El Morabity - 3.7.1-1 +- Update to 3.7.1 +- Drop dock and gajim extensions, no longer provided + +* Tue Oct 30 2012 Mohamed El Morabity - 3.6.1-1 +- Update to 3.6.1 + +* Tue Oct 02 2012 Mohamed El Morabity - 3.6.0-1 +- Update to 3.6.0 + +* Thu Sep 06 2012 Mohamed El Morabity - 3.5.91-1 +- Update to 3.5.91 + +* Wed Aug 29 2012 Mohamed El Morabity - 3.5.90-1 +- Update to 3.5.90 + +* Sat Aug 11 2012 Mohamed El Morabity - 3.5.5-1 +- Update to 3.5.5 + +* Sun Jul 22 2012 Mohamed El Morabity - 3.5.4-1 +- Update to 3.5.4 + +* Wed Jul 18 2012 Mohamed El Morabity - 3.5.2-1 +- Update to 3.5.2 +- Drop useless Provides/Obsoletes + +* Sat Mar 24 2012 Mohamed El Morabity - 3.4.0-1 +- Update to 3.4.0 +- Minor spec fixes + +* Sat Mar 24 2012 Mohamed El Morabity - 3.3.92-1 +- Update to 3.3.92 + +* Tue Feb 28 2012 Mohamed El Morabity - 3.3.90-1 +- Update to 3.3.90 + +* Thu Feb 16 2012 Mohamed El Morabity - 3.3.5-1 +- Update to 3.3.5 +- Spec cleanup + +* Fri Jan 13 2012 Fedora Release Engineering - 3.3.2-2 +- Rebuilt for https://fedoraproject.org/wiki/Fedora_17_Mass_Rebuild + +* Wed Nov 30 2011 Mohamed El Morabity - 3.3.2-1 +- Update to 3.3.2 + +* Wed Nov 30 2011 Mohamed El Morabity - 3.2.1-1 +- Update to 3.2.1 +- Fix alternative-status-menu extension crash when login + +* Wed Nov 09 2011 Mohamed El Morabity - 3.2.0-2 +- Fix dock and alternate-tab extensions +- Fix GNOME Shell version to work with GS 3.2.1 + +* Mon Oct 03 2011 Mohamed El Morabity - 3.2.0-1 +- Update to 3.2.0 + +* Mon Sep 26 2011 Mohamed El Morabity - 3.1.91-3.20111001gite102c0c6 +- Update to a newer git snapshot +- Fix GNOME Shell version to work with GS 3.2.0 +- Add Requires on GS 3.2.0 or above to gnome-shell-common + +* Wed Sep 14 2011 Mohamed El Morabity - 3.1.91-2 +- Enable xrandr-indicator and workspace-indicator extensions + +* Mon Sep 12 2011 Michel Salim - 3.1.91-1 +- Update to 3.1.91 +- add more documentation + +* Thu Sep 1 2011 Michel Salim - 3.1.4-3.20110830git6b5e3a3e +- Update to git snapshot, for gnome-shell 3.1.90 + +* Sun Aug 21 2011 Michel Salim - 3.1.4-2 +- Enable apps-menu extension +- Spec cleanup + +* Sun Aug 21 2011 Michel Salim - 3.1.4-1 +- Update to 3.1.4 +- Enable systemMonitor extension +- Prepare xrandr-indicator, commenting out since it does not seem to work yet +- Rename subpackages in line with new guidelines (# 715367) +- Sort subpackages in alphabetical order + +* Sat May 28 2011 Timur Kristóf - 3.0.2-1.g63dd27cgit +- Update to a newer git snapshot +- Fix RHBZ bug #708230 +- Enabled systemMonitor extension, but commented out since the requirements are not available + +* Fri May 13 2011 Mohamed El Morabity - 3.0.1-3.03660fgit +- Update to a newer git snapshot +- Enable native-window-placement extension + +* Fri May 06 2011 Rahul Sundaram - 3.0.1-2b20cbagit +- Fix description + +* Thu May 5 2011 Elad Alfassa - 3.0.1-1.b20cbagit +- Update to a newer git snapshot +- Enabled the places-menu extension + +* Tue Apr 26 2011 Mohamed El Morabity - 3.0.1-1.f016b9git +- Update to a newer git snapshot (post-3.0.1 release) +- Enable drive-menu extension + +* Mon Apr 11 2011 Mohamed El Morabity - 3.0.0-5.6d56cfgit +- Enable auto-move-windows extension + +* Mon Apr 11 2011 Rahul Sundaram - 3.0.0-4.6d56cfgit +- Add glib2-devel as build requires + +* Mon Apr 11 2011 Rahul Sundaram - 3.0.0-3.6d56cfgit +- Tweak description +- Fix typo in configure + +* Mon Apr 11 2011 Rahul Sundaram - 3.0.0-2.6d56cfgit +- Added the user-theme extension +- Patch from Timur Kristóf + +* Fri Apr 08 2011 Rahul Sundaram - 3.0.0-1.6d56cfgit +- Make sure configure doesn't get called twice + +* Fri Apr 08 2011 Rahul Sundaram - 3.0.0-0.6d56cfgit +- Initial build + +## END: Generated by rpmautospec