From b58c67ad54ed58e4527b5fc7f6709db9960f0969 Mon Sep 17 00:00:00 2001 From: CentOS Sources Date: Tue, 9 May 2023 05:20:56 +0000 Subject: [PATCH] import gnome-shell-extensions-40.7-7.el9 --- ...0001-Add-gesture-inhibitor-extension.patch | 26 +- ...on-banner-Handle-fullscreen-monitors.patch | 68 ++ ...ktop-icons-Don-t-grab-focus-on-click.patch | 33 + ...-desktop-icons-Don-t-use-blocking-IO.patch | 108 +++ ...ook-into-LayoutManager-to-create-gri.patch | 60 ++ ...r-Allow-inhibiting-workspace-switch-.patch | 40 + ...plicitly-dispose-settings-on-destroy.patch | 46 + SOURCES/add-extra-extensions.patch | 832 +++++++++++++++++- SOURCES/window-list-touch.patch | 116 +++ SPECS/gnome-shell-extensions.spec | 50 +- 10 files changed, 1341 insertions(+), 38 deletions(-) create mode 100644 SOURCES/0001-classification-banner-Handle-fullscreen-monitors.patch create mode 100644 SOURCES/0001-desktop-icons-Don-t-grab-focus-on-click.patch create mode 100644 SOURCES/0001-desktop-icons-Don-t-use-blocking-IO.patch create mode 100644 SOURCES/0001-desktopManager-Hook-into-LayoutManager-to-create-gri.patch create mode 100644 SOURCES/0001-gesture-inhibitor-Allow-inhibiting-workspace-switch-.patch create mode 100644 SOURCES/0001-window-list-Explicitly-dispose-settings-on-destroy.patch create mode 100644 SOURCES/window-list-touch.patch diff --git a/SOURCES/0001-Add-gesture-inhibitor-extension.patch b/SOURCES/0001-Add-gesture-inhibitor-extension.patch index 460bfb2..81a0325 100644 --- a/SOURCES/0001-Add-gesture-inhibitor-extension.patch +++ b/SOURCES/0001-Add-gesture-inhibitor-extension.patch @@ -1,4 +1,4 @@ -From 95bf73c668eab5c222568bdb6a3030a5bed8d4d3 Mon Sep 17 00:00:00 2001 +From 38c4fc02dea622f198b078eb4003c777d982119c Mon Sep 17 00:00:00 2001 From: rpm-build Date: Thu, 28 Jan 2021 00:06:12 +0100 Subject: [PATCH 1/5] Add gesture-inhibitor extension @@ -170,22 +170,22 @@ index 00000000..37b93f21 @@ -0,0 +1 @@ +/* Add your custom extension styling here */ diff --git a/meson.build b/meson.build -index 8b67435b..fd328df5 100644 +index 3600e824..b3812b8d 100644 --- a/meson.build +++ b/meson.build -@@ -48,6 +48,7 @@ all_extensions += [ - 'auto-move-windows', +@@ -49,6 +49,7 @@ all_extensions += [ 'classification-banner', + 'custom-menu', 'dash-to-dock', + 'gesture-inhibitor', 'native-window-placement', 'panel-favorites', 'systemMonitor', -- -2.33.1 +2.38.1 -From 89daf03fcc0f7b157e90a5ef4487e94e27fe8d38 Mon Sep 17 00:00:00 2001 +From aff83154aa639e33e5ba925b5ddcc824a9beaf6e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Florian=20M=C3=BCllner?= Date: Wed, 20 Oct 2021 19:48:46 +0200 Subject: [PATCH 2/5] gesture-inhibitor: Fix up indentation @@ -273,10 +273,10 @@ index e74ede2f..734d61cc 100644 } -- -2.33.1 +2.38.1 -From 00ffe8e51dbb8609461239d753d2215e66b2b76d Mon Sep 17 00:00:00 2001 +From 5c8b087e99f79cc6bd83b5e7ad0775f8510e1a5d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Florian=20M=C3=BCllner?= Date: Wed, 20 Oct 2021 19:47:05 +0200 Subject: [PATCH 3/5] gesture-inhibitor: Adjust for GNOME 40 changes @@ -344,10 +344,10 @@ index 1d67dcc0..4bdf9260 100644 true Show OSK gesture -- -2.33.1 +2.38.1 -From cd42930b27efbffdac6b259bf7417a4528f5bfdf Mon Sep 17 00:00:00 2001 +From 7f8031a97046a18ebb39972150376b9f1cf9a70b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Florian=20M=C3=BCllner?= Date: Thu, 18 Nov 2021 15:54:23 +0100 Subject: [PATCH 4/5] gesture-inhibitor: Unbind setting on disable @@ -374,10 +374,10 @@ index 13586108..02b34ec4 100644 } -- -2.33.1 +2.38.1 -From 294fe1f115d8c23e71608c34be296dd0080f2671 Mon Sep 17 00:00:00 2001 +From 15b4dde292cd1dd33c881289e6182d7261bee544 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Florian=20M=C3=BCllner?= Date: Thu, 18 Nov 2021 16:06:09 +0100 Subject: [PATCH 5/5] gesture-inhibitor: Override :enabled property @@ -434,5 +434,5 @@ index 02b34ec4..fb8a6dc0 100644 }); } -- -2.33.1 +2.38.1 diff --git a/SOURCES/0001-classification-banner-Handle-fullscreen-monitors.patch b/SOURCES/0001-classification-banner-Handle-fullscreen-monitors.patch new file mode 100644 index 0000000..b2e6ccd --- /dev/null +++ b/SOURCES/0001-classification-banner-Handle-fullscreen-monitors.patch @@ -0,0 +1,68 @@ +From ffba821e1142c3cb96b9ced24dd1f161f0609d2a Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Florian=20M=C3=BCllner?= +Date: Wed, 14 Dec 2022 16:55:51 +0100 +Subject: [PATCH] classification-banner: Handle fullscreen monitors + +When a monitor is in fullscreen, we don't want its classification +banner to be offset by an imaginary panel, but at the top of the +screen. +--- + extensions/classification-banner/extension.js | 22 +++++++++++++++---- + 1 file changed, 18 insertions(+), 4 deletions(-) + +diff --git a/extensions/classification-banner/extension.js b/extensions/classification-banner/extension.js +index cc046e01..ea788022 100644 +--- a/extensions/classification-banner/extension.js ++++ b/extensions/classification-banner/extension.js +@@ -27,16 +27,19 @@ const Main = imports.ui.main; + const ClassificationBanner = GObject.registerClass( + class ClassificationBanner extends Clutter.Actor { + _init(index) { ++ const constraint = new Layout.MonitorConstraint({index}); + super._init({ + layout_manager: new Clutter.BinLayout(), +- constraints: new Layout.MonitorConstraint({ +- work_area: true, +- index, +- }), ++ constraints: constraint, + }); ++ this._monitorConstraint = constraint; + + this._settings = ExtensionUtils.getSettings(); + this.connect('destroy', () => { ++ if (this._fullscreenChangedId) ++ global.display.disconnect(this._fullscreenChangedId); ++ delete this._fullscreenChangedId; ++ + this._settings?.run_dispose(); + this._settings = null; + }); +@@ -94,6 +97,11 @@ class ClassificationBanner extends Clutter.Actor { + userLabel, 'visible', + Gio.SettingsBindFlags.GET); + ++ this._fullscreenChangedId = ++ global.display.connect('in-fullscreen-changed', ++ () => this._updateMonitorConstraint()); ++ this._updateMonitorConstraint(); ++ + this._settings.connect('changed::color', + () => this._updateStyles()); + this._settings.connect('changed::background-color', +@@ -110,6 +118,12 @@ class ClassificationBanner extends Clutter.Actor { + 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'); +-- +2.38.1 + diff --git a/SOURCES/0001-desktop-icons-Don-t-grab-focus-on-click.patch b/SOURCES/0001-desktop-icons-Don-t-grab-focus-on-click.patch new file mode 100644 index 0000000..2b1358d --- /dev/null +++ b/SOURCES/0001-desktop-icons-Don-t-grab-focus-on-click.patch @@ -0,0 +1,33 @@ +From 8bea7c892c24694efda753ad1d76ab470032c6fe Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Florian=20M=C3=BCllner?= +Date: Thu, 15 Dec 2022 17:09:45 +0100 +Subject: [PATCH] desktop-icons: Don't grab focus on click + +We will move keyboard focus away immediately, either when opening +the context menu or when starting the rubberband. + +In theory the grab is still useful, because it will move keyboard +focus to the grid when restoring focus after ending the rubberband +or closing the menu, however as keyboard navigation support is +lacking, all it does is preventing the focus to return to the +focus window after the operation. +--- + extensions/desktop-icons/desktopGrid.js | 2 -- + 1 file changed, 2 deletions(-) + +diff --git a/extensions/desktop-icons/desktopGrid.js b/extensions/desktop-icons/desktopGrid.js +index 002803c7..9a89d5a3 100644 +--- a/extensions/desktop-icons/desktopGrid.js ++++ b/extensions/desktop-icons/desktopGrid.js +@@ -559,8 +559,6 @@ var DesktopGrid = GObject.registerClass({ + let button = event.get_button(); + let [x, y] = event.get_coords(); + +- this._grid.grab_key_focus(); +- + if (button == 1) { + let shiftPressed = !!(event.get_state() & Clutter.ModifierType.SHIFT_MASK); + let controlPressed = !!(event.get_state() & Clutter.ModifierType.CONTROL_MASK); +-- +2.38.1 + diff --git a/SOURCES/0001-desktop-icons-Don-t-use-blocking-IO.patch b/SOURCES/0001-desktop-icons-Don-t-use-blocking-IO.patch new file mode 100644 index 0000000..e5d44e3 --- /dev/null +++ b/SOURCES/0001-desktop-icons-Don-t-use-blocking-IO.patch @@ -0,0 +1,108 @@ +From 2a1dd773a529c89b5f9577b53ae3c88aea2efc48 Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Florian=20M=C3=BCllner?= +Date: Tue, 17 Jan 2023 20:31:21 +0100 +Subject: [PATCH] desktop-icons: Don't use blocking IO + +--- + extensions/desktop-icons/desktopManager.js | 45 +++++++++++++++------- + 1 file changed, 32 insertions(+), 13 deletions(-) + +diff --git a/extensions/desktop-icons/desktopManager.js b/extensions/desktop-icons/desktopManager.js +index 74d0e6bd..75b2a22a 100644 +--- a/extensions/desktop-icons/desktopManager.js ++++ b/extensions/desktop-icons/desktopManager.js +@@ -54,6 +54,21 @@ function findMonitorIndexForPos(x, y) { + return getDpy().get_monitor_index_for_rect(new Meta.Rectangle({x, y})); + } + ++async function queryInfo(file, attributes = DesktopIconsUtil.DEFAULT_ATTRIBUTES, cancellable = null) { ++ const flags = Gio.FileQueryInfoFlags.NONE; ++ const priority = GLib.PRIORITY_DEFAULT; ++ return new Promise((resolve, reject) => { ++ file.query_info_async(attributes, flags, priority, cancellable, (o, res) => { ++ try { ++ const info = file.query_info_finish(res); ++ resolve(info); ++ } catch (e) { ++ reject(e); ++ } ++ }); ++ }); ++} ++ + + var DesktopManager = GObject.registerClass({ + Properties: { +@@ -272,9 +287,7 @@ var DesktopManager = GObject.registerClass({ + + if (!this._unixMode) { + let desktopDir = DesktopIconsUtil.getDesktopDir(); +- let fileInfo = desktopDir.query_info(Gio.FILE_ATTRIBUTE_UNIX_MODE, +- Gio.FileQueryInfoFlags.NONE, +- null); ++ let fileInfo = await queryInfo(desktopDir, Gio.FILE_ATTRIBUTE_UNIX_MODE); + this._unixMode = fileInfo.get_attribute_uint32(Gio.FILE_ATTRIBUTE_UNIX_MODE); + this._setWritableByOthers((this._unixMode & S_IWOTH) != 0); + } +@@ -283,7 +296,7 @@ var DesktopManager = GObject.registerClass({ + let items = []; + for (let item of await this._enumerateDesktop()) + items.push(item); +- for (let item of this._getMounts()) ++ for (let item of await this._getMounts()) + items.push(item); + + let tmpFileItems = new Map(); +@@ -328,14 +341,22 @@ var DesktopManager = GObject.registerClass({ + Gio.FileQueryInfoFlags.NONE, + GLib.PRIORITY_DEFAULT, + this._desktopEnumerateCancellable, +- (source, result) => { ++ async (source, result) => { + try { + let fileEnum = source.enumerate_children_finish(result); ++ let extraFolders = await Promise.all(DesktopIconsUtil.getExtraFolders() ++ .map(async ([folder, extras]) => { ++ const info = await queryInfo(folder, ++ DesktopIconsUtil.DEFAULT_ATTRIBUTES, ++ this._desktopEnumerateCancellable); ++ return [folder, info, extras]; ++ })); ++ + let resultGenerator = function *() { ++ for (let [newFolder, info, extras] of extraFolders) ++ yield [newFolder, info, extras]; ++ + let info; +- for (let [newFolder, extras] of DesktopIconsUtil.getExtraFolders()) { +- yield [newFolder, newFolder.query_info(DesktopIconsUtil.DEFAULT_ATTRIBUTES, Gio.FileQueryInfoFlags.NONE, this._desktopEnumerateCancellable), extras]; +- } + while ((info = fileEnum.next_file(null))) + yield [fileEnum.get_child(info), info, Prefs.FileType.NONE]; + }.bind(this); +@@ -359,19 +380,17 @@ var DesktopManager = GObject.registerClass({ + this._monitorDesktopDir.connect('changed', (obj, file, otherFile, eventType) => this._updateDesktopIfChanged(file, otherFile, eventType)); + } + +- _getMounts() { ++ async _getMounts() { + let files = []; + if (!Prefs.settings.get_boolean('show-mount')) + return files; + +- this._mountMonitor.get_mounts().forEach( mount => { ++ this._mountMonitor.get_mounts().forEach(async mount => { + if (this._isNetworkMount(mount)) + return; + + let file = mount.get_root(); +- let info = file.query_info(DesktopIconsUtil.DEFAULT_ATTRIBUTES, +- Gio.FileQueryInfoFlags.NONE, +- null); ++ let info = await queryInfo(file); + files.push([file, info, Prefs.FileType.MOUNT_DISK]); + }); + +-- +2.38.1 + diff --git a/SOURCES/0001-desktopManager-Hook-into-LayoutManager-to-create-gri.patch b/SOURCES/0001-desktopManager-Hook-into-LayoutManager-to-create-gri.patch new file mode 100644 index 0000000..5dd21d1 --- /dev/null +++ b/SOURCES/0001-desktopManager-Hook-into-LayoutManager-to-create-gri.patch @@ -0,0 +1,60 @@ +From 62289dff5cb2e615a277b72f034fa42f45aad639 Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Florian=20M=C3=BCllner?= +Date: Thu, 15 Dec 2022 15:14:08 +0100 +Subject: [PATCH] desktopManager: Hook into LayoutManager to create grids + +Right now we track the `monitors-changed` signal to recreate the +per-monitor grids. Usually that's enough, but if something else +causes backgrounds to update, we'll end up without desktop icons +until some other change (settings, mounts, monitor/resolution +changes, ...) results in a reload of the grid. + +To address this, hook into LayoutManager to always create the grid +when backgrounds are updated. +--- + extensions/desktop-icons/desktopManager.js | 15 +++++++++++---- + 1 file changed, 11 insertions(+), 4 deletions(-) + +diff --git a/extensions/desktop-icons/desktopManager.js b/extensions/desktop-icons/desktopManager.js +index 08bc82b7..74d0e6bd 100644 +--- a/extensions/desktop-icons/desktopManager.js ++++ b/extensions/desktop-icons/desktopManager.js +@@ -83,7 +83,6 @@ var DesktopManager = GObject.registerClass({ + this._discreteGpuAvailable = false; + this._rubberBandActive = false; + +- this._monitorsChangedId = Main.layoutManager.connect('monitors-changed', () => this._recreateDesktopIcons()); + this._rubberBand = new St.Widget({ style_class: 'rubber-band' }); + this._rubberBand.hide(); + Main.layoutManager._backgroundGroup.add_child(this._rubberBand); +@@ -109,6 +108,13 @@ var DesktopManager = GObject.registerClass({ + return origCapturedEvent.bind(this._grabHelper)(event); + }; + ++ this._origUpdateBackgrounds = ++ Main.layoutManager._updateBackgrounds; ++ Main.layoutManager._updateBackgrounds = () => { ++ this._origUpdateBackgrounds.call(Main.layoutManager); ++ this._recreateDesktopIcons(); ++ }; ++ + this._addDesktopIcons(); + this._monitorDesktopFolder(); + +@@ -843,9 +849,10 @@ var DesktopManager = GObject.registerClass({ + GLib.source_remove(this._deleteChildrenId); + this._deleteChildrenId = 0; + +- if (this._monitorsChangedId) +- Main.layoutManager.disconnect(this._monitorsChangedId); +- this._monitorsChangedId = 0; ++ if (this._origUpdateBackgrounds) ++ Main.layoutManager._updateBackgrounds = this._origUpdateBackgrounds; ++ delete this._origUpdateBackgrounds; ++ + if (this._stageReleaseEventId) + global.stage.disconnect(this._stageReleaseEventId); + this._stageReleaseEventId = 0; +-- +2.38.1 + diff --git a/SOURCES/0001-gesture-inhibitor-Allow-inhibiting-workspace-switch-.patch b/SOURCES/0001-gesture-inhibitor-Allow-inhibiting-workspace-switch-.patch new file mode 100644 index 0000000..ad0bbd8 --- /dev/null +++ b/SOURCES/0001-gesture-inhibitor-Allow-inhibiting-workspace-switch-.patch @@ -0,0 +1,40 @@ +From c70a1fa37f68687b8c0a013d2328e6262f8419d0 Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Florian=20M=C3=BCllner?= +Date: Fri, 9 Dec 2022 15:31:08 +0100 +Subject: [PATCH] gesture-inhibitor: Allow inhibiting workspace switch gesture + +--- + extensions/gesture-inhibitor/extension.js | 1 + + .../org.gnome.shell.extensions.gesture-inhibitor.gschema.xml | 4 ++++ + 2 files changed, 5 insertions(+) + +diff --git a/extensions/gesture-inhibitor/extension.js b/extensions/gesture-inhibitor/extension.js +index fb8a6dc0..d103d5b8 100644 +--- a/extensions/gesture-inhibitor/extension.js ++++ b/extensions/gesture-inhibitor/extension.js +@@ -48,6 +48,7 @@ class Extension { + { 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( +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 +index 4bdf9260..b06d027a 100644 +--- a/extensions/gesture-inhibitor/org.gnome.shell.extensions.gesture-inhibitor.gschema.xml ++++ b/extensions/gesture-inhibitor/org.gnome.shell.extensions.gesture-inhibitor.gschema.xml +@@ -12,6 +12,10 @@ + true + Application switch gesture + ++ ++ true ++ Workspace switch gesture ++ + + true + Unfullscreen gesture +-- +2.38.1 + diff --git a/SOURCES/0001-window-list-Explicitly-dispose-settings-on-destroy.patch b/SOURCES/0001-window-list-Explicitly-dispose-settings-on-destroy.patch new file mode 100644 index 0000000..9556d59 --- /dev/null +++ b/SOURCES/0001-window-list-Explicitly-dispose-settings-on-destroy.patch @@ -0,0 +1,46 @@ +From a31f4b6ca703faab25c306dc33056763642a83cb Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Florian=20M=C3=BCllner?= +Date: Fri, 30 Sep 2022 18:16:16 +0200 +Subject: [PATCH] window-list: Explicitly dispose settings on destroy + +This will not only disconnect the signal handler, but also remove +any bindings. This works around a crash that happens if a setting +that triggers the binding changes at the same time as a setting +that rebuilds the window list; in that case, the binding handler +runs after gjs has dropped its wrapper object, but before the +binding is removed automaticalled when the object is finalized. + +https://gitlab.gnome.org/GNOME/gnome-shell-extensions/-/issues/416 + +Part-of: +--- + extensions/window-list/extension.js | 6 +++--- + 1 file changed, 3 insertions(+), 3 deletions(-) + +diff --git a/extensions/window-list/extension.js b/extensions/window-list/extension.js +index 89413818..91ee3e6b 100644 +--- a/extensions/window-list/extension.js ++++ b/extensions/window-list/extension.js +@@ -842,8 +842,8 @@ class WindowList extends St.Widget { + this._dndWindow = null; + + this._settings = ExtensionUtils.getSettings(); +- this._groupingModeChangedId = this._settings.connect( +- 'changed::grouping-mode', this._groupingModeChanged.bind(this)); ++ this._settings.connect('changed::grouping-mode', ++ () => this._groupingModeChanged()); + this._grouped = undefined; + this._groupingModeChanged(); + } +@@ -1112,7 +1112,7 @@ class WindowList extends St.Widget { + Main.xdndHandler.disconnect(this._dragBeginId); + Main.xdndHandler.disconnect(this._dragEndId); + +- this._settings.disconnect(this._groupingModeChangedId); ++ this._settings.run_dispose(); + + let windows = global.get_window_actors(); + for (let i = 0; i < windows.length; i++) +-- +2.39.1 + diff --git a/SOURCES/add-extra-extensions.patch b/SOURCES/add-extra-extensions.patch index 7c54243..570e4c4 100644 --- a/SOURCES/add-extra-extensions.patch +++ b/SOURCES/add-extra-extensions.patch @@ -1,7 +1,7 @@ -From ce63b1027e8bf79688f38babecae0dcd867778f8 Mon Sep 17 00:00:00 2001 +From 6623a374036e0f2458d4b36e268f6e4dcc19a2d7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Florian=20M=C3=BCllner?= Date: Wed, 20 May 2015 17:44:50 +0200 -Subject: [PATCH 1/6] Add top-icons extension +Subject: [PATCH 1/7] Add top-icons extension --- extensions/top-icons/extension.js | 96 +++++++++++++++++++++++++++ @@ -152,7 +152,7 @@ index 00000000..25134b65 @@ -0,0 +1 @@ +/* This extensions requires no special styling */ diff --git a/meson.build b/meson.build -index 188e19e5..1d94882f 100644 +index 41a7e99d..f754767c 100644 --- a/meson.build +++ b/meson.build @@ -45,6 +45,7 @@ all_extensions = default_extensions @@ -164,13 +164,13 @@ index 188e19e5..1d94882f 100644 ] -- -2.33.1 +2.38.1 -From 40aa60ef32f9283147745ac960e7e22b2d608df5 Mon Sep 17 00:00:00 2001 +From a38891b5a6b0ba51998298963988bf146b2e1f86 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Florian=20M=C3=BCllner?= Date: Wed, 20 May 2015 18:05:41 +0200 -Subject: [PATCH 2/6] Add dash-to-dock extension +Subject: [PATCH 2/7] Add dash-to-dock extension --- extensions/dash-to-dock/Settings.ui | 2660 +++++++++++++++++ @@ -29865,7 +29865,7 @@ index 00000000..8cb14b88 +}); \ No newline at end of file diff --git a/meson.build b/meson.build -index 1d94882f..e3a64a92 100644 +index f754767c..e3d94918 100644 --- a/meson.build +++ b/meson.build @@ -44,6 +44,7 @@ default_extensions += [ @@ -44398,13 +44398,13 @@ index 6a40e212..14e60ccb 100644 +#~ msgid "0.000" +#~ msgstr "0.000" -- -2.33.1 +2.38.1 -From 7bcecaa3b0532221690b8cb0df5184a80ed15dd5 Mon Sep 17 00:00:00 2001 +From 6cad2aac52022030bcbbf03066fc08937f6d177f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Florian=20M=C3=BCllner?= Date: Wed, 20 May 2015 18:55:47 +0200 -Subject: [PATCH 3/6] Add panel-favorites extension +Subject: [PATCH 3/7] Add panel-favorites extension --- extensions/panel-favorites/extension.js | 257 ++++++++++++++++++++ @@ -44729,7 +44729,7 @@ index 00000000..120adacb + -y-offset: 6px; +} diff --git a/meson.build b/meson.build -index e3a64a92..47b6c46c 100644 +index e3d94918..12706001 100644 --- a/meson.build +++ b/meson.build @@ -46,6 +46,7 @@ all_extensions += [ @@ -44741,13 +44741,13 @@ index e3a64a92..47b6c46c 100644 'user-theme' ] -- -2.33.1 +2.38.1 -From 7a4402c27a93cfff76504f561013498f31966da8 Mon Sep 17 00:00:00 2001 +From dbd3ebbb2d3cf380f2c0a13f13618fedfd8bbad4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Florian=20M=C3=BCllner?= Date: Fri, 4 Mar 2016 17:07:21 +0100 -Subject: [PATCH 4/6] Add updates-dialog extension +Subject: [PATCH 4/7] Add updates-dialog extension --- extensions/updates-dialog/extension.js | 504 ++++++++++++++++++ @@ -45347,7 +45347,7 @@ index 00000000..25134b65 @@ -0,0 +1 @@ +/* This extensions requires no special styling */ diff --git a/meson.build b/meson.build -index 47b6c46c..c54ef777 100644 +index 12706001..c609ae52 100644 --- a/meson.build +++ b/meson.build @@ -48,6 +48,7 @@ all_extensions += [ @@ -45372,13 +45372,13 @@ index 0ed12762..10b1d517 100644 extensions/window-list/extension.js extensions/window-list/org.gnome.shell.extensions.window-list.gschema.xml -- -2.33.1 +2.38.1 -From d04e5acf2bf6076aeca3c2da27c8edfdd5098606 Mon Sep 17 00:00:00 2001 +From 5de33c69acd297659ac3182e85f0fe32771dd75e Mon Sep 17 00:00:00 2001 From: Carlos Soriano Date: Mon, 13 Aug 2018 17:28:41 +0200 -Subject: [PATCH 5/6] Add desktop icons extension +Subject: [PATCH 5/7] Add desktop icons extension --- .../desktop-icons/createFolderDialog.js | 165 +++ @@ -56921,7 +56921,7 @@ index 00000000..a468f4ab + } +} diff --git a/meson.build b/meson.build -index c54ef777..08213618 100644 +index c609ae52..b83f0795 100644 --- a/meson.build +++ b/meson.build @@ -28,6 +28,7 @@ uuid_suffix = '@gnome-shell-extensions.gcampax.github.com' @@ -66729,13 +66729,13 @@ index 14e60ccb..4bdf7154 100644 +#~ msgid "Huge" +#~ msgstr "巨大圖示" -- -2.33.1 +2.38.1 -From 01a4309a2574768c037f14732e8595c1f436170f Mon Sep 17 00:00:00 2001 +From 794dd76a7b7caf3324736de6c26bb992fe403daf 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 6/6] Add classification-banner +Subject: [PATCH 6/7] Add classification-banner --- extensions/classification-banner/adwShim.js | 202 ++++++++++++++++++ @@ -67417,7 +67417,7 @@ index 00000000..fb6a697e +.classification-message { font-weight: bold; } +.classification-banner { font-size: 0.9em; } diff --git a/meson.build b/meson.build -index 08213618..dea0a409 100644 +index b83f0795..c71636cb 100644 --- a/meson.build +++ b/meson.build @@ -45,6 +45,7 @@ default_extensions += [ @@ -67429,5 +67429,789 @@ index 08213618..dea0a409 100644 'native-window-placement', 'panel-favorites', -- -2.33.1 +2.38.1 + + +From 4cde1aa62f4605cde761c9f0f2f95af64515720d 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 7/7] Add custom-menu extension + +--- + extensions/custom-menu/config.js | 484 ++++++++++++++++++++++++ + extensions/custom-menu/extension.js | 217 +++++++++++ + extensions/custom-menu/meson.build | 7 + + extensions/custom-menu/metadata.json.in | 10 + + extensions/custom-menu/stylesheet.css | 1 + + meson.build | 1 + + 6 files changed, 720 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 + create mode 100644 extensions/custom-menu/stylesheet.css + +diff --git a/extensions/custom-menu/config.js b/extensions/custom-menu/config.js +new file mode 100644 +index 00000000..652c0223 +--- /dev/null ++++ b/extensions/custom-menu/config.js +@@ -0,0 +1,484 @@ ++const Gio = imports.gi.Gio; ++const GLib = imports.gi.GLib; ++const Json = imports.gi.Json; ++const Lang = imports.lang; ++const Main = imports.ui.main; ++const PopupMenu = imports.ui.popupMenu; ++const ByteArray = imports.byteArray; ++ ++const Me = imports.misc.extensionUtils.getCurrentExtension(); ++const getLogger = Me.imports.extension.getLogger; ++ ++const Entry = new Lang.Class({ ++ Name: 'Entry', ++ Abstract: true, ++ ++ _init: function(prop) { ++ this.type = prop.type; ++ this.title = prop.title || ""; ++ ++ this.__vars = prop.__vars || []; ++ this.updateEnv(prop); ++ }, ++ ++ setTitle: function(text) { ++ this.item.label.get_clutter_text().set_text(text); ++ }, ++ ++ updateEnv: function(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: function() { }, ++ ++ _try_destroy: function() { ++ try { ++ if(this.item && this.item.destroy) ++ this.item.destroy(); ++ } catch(e) { /* Ignore all errors during destory*/ } ++ }, ++}); ++ ++const DerivedEntry = new Lang.Class({ ++ Name: 'DerivedEntry', ++ ++ _init: function(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: function(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; ++ }, ++}); ++ ++/* ++ * callback: function (stdout, stderr, exit_status) { } ++ */ ++let __pipeOpenQueue = []; ++let __pipeExecTimer = null; ++ ++function pipeOpen(cmdline, env, callback) { ++ let param = [cmdline, env, callback] ++ __pipeOpenQueue.push(param); ++ if(__pipeExecTimer === null) { ++ __pipeExecTimer = GLib.timeout_add(GLib.PRIORITY_DEFAULT, 50, ++ function() { ++ let param = __pipeOpenQueue.shift(); ++ if(param === undefined) { ++ __pipeExecTimer = null; ++ return false; ++ } ++ if(realPipeOpen) realPipeOpen(param[0], param[1], param[2]); ++ 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) { ++ stdout_content = ByteArray.toString(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 = ByteArray.toString(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); ++ ++ // log(`Spawning ${cmdline}`); TODO: BEN ++ 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); ++ } ++ }); ++} ++ ++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 = { }; ++ ++const TogglerEntry = new Lang.Class({ ++ Name: 'TogglerEntry', ++ Extends: Entry, ++ ++ _init: function(prop) { ++ this.parent(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: function() { ++ 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', Lang.bind(this, this._onManuallyToggled)); ++ this._loadState(); ++ return this.item; ++ }, ++ ++ _onManuallyToggled: function(_, state) { ++ // when switched on again, this flag will get cleared. ++ this._manually_switched_off = !state; ++ this._storeState(state); ++ this._onToggled(state); ++ }, ++ ++ _onToggled: function(state) { ++ if(state) ++ _generalSpawn(this.command_on, this.__env, this.title); ++ else ++ _generalSpawn(this.command_off, this.__env, this.title); ++ }, ++ ++ _detect: function(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*$/))); ++ }); ++ }, ++ ++ compareState: function(new_state) { ++ // compare the new state with cached state ++ // notify when state is different ++ 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: function(state) { ++ let hash = JSON.stringify({ env: this.__env, detector: this.detector }); ++ _toggler_state_cache[hash] = state; ++ }, ++ ++ _loadState: function() { ++ 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: function() { ++ this._detect(Lang.bind(this, function(state) { ++ this.compareState(state); ++ ++ this._storeState(state); ++ this._loadState(); ++ //global.log(this.title + ': ' + this._manually_switched_off); ++ ++ 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: function() { ++ this.item.toggle(); ++ }, ++}); ++ ++const LauncherEntry = new Lang.Class({ ++ Name: 'LauncherEntry', ++ Extends: Entry, ++ ++ _init: function(prop) { ++ this.parent(prop); ++ ++ this.command = prop.command || ""; ++ }, ++ ++ createItem: function() { ++ this._try_destroy(); ++ ++ this.item = new PopupMenu.PopupMenuItem(this.title); ++ this.item.label.get_clutter_text().set_use_markup(true); ++ this.item.connect('activate', Lang.bind(this, this._onClicked)); ++ ++ return this.item; ++ }, ++ ++ _onClicked: function(_) { ++ _generalSpawn(this.command, this.__env, this.title); ++ }, ++ ++ perform: function() { ++ this.item.emit('activate'); ++ }, ++}); ++ ++const SubMenuEntry = new Lang.Class({ ++ Name: 'SubMenuEntry', ++ Extends: Entry, ++ ++ _init: function(prop) { ++ this.parent(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: function() { ++ 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: function() { ++ for(let i in this.entries) { ++ let entry = this.entries[i]; ++ entry.pulse(); ++ } ++ } ++}); ++ ++const SeparatorEntry = new Lang.Class({ ++ Name: 'SeparatorEntry', ++ Extends: Entry, ++ ++ _init: function(prop) { }, ++ ++ createItem: function() { ++ 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); ++} ++ ++var Loader = new Lang.Class({ ++ Name: 'ConfigLoader', ++ ++ _init: function(filename) { ++ if(filename) ++ this.loadConfig(filename); ++ }, ++ ++ loadConfig: function(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: function(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..9f3e3ce8 +--- /dev/null ++++ b/extensions/custom-menu/extension.js +@@ -0,0 +1,217 @@ ++/* 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 ++ */ ++ ++/* exported init */ ++ ++const GETTEXT_DOMAIN = 'custom-menu-panel'; ++const CONFIGURATION_FILE = '/.entries.json'; ++ ++const { GObject, St } = imports.gi; ++ ++const Gettext = imports.gettext.domain(GETTEXT_DOMAIN); ++const _ = Gettext.gettext; ++ ++const ExtensionUtils = imports.misc.extensionUtils; ++const Lang = imports.lang; ++const GLib = imports.gi.GLib; ++const Gio = imports.gi.Gio; ++const BackgroundMenu = imports.ui.backgroundMenu; ++const Main = imports.ui.main; ++const PanelMenu = imports.ui.panelMenu; ++const PopupMenu = imports.ui.popupMenu; ++ ++const Me = imports.misc.extensionUtils.getCurrentExtension(); ++const Config = Me.imports.config ++ ++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)); ++ } ++ } ++); ++ ++ ++const Logger = new Lang.Class({ ++ Name: 'Logger', ++ ++ _init: function(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: function() { ++ this.log = function(_) { }; ++ }, ++ ++ _initGnomeLog: function() { ++ this.log = function(s) { ++ global.log("custom-menu-panel> " + s); ++ }; ++ }, ++ ++ _initFileLog: function() { ++ 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: function(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; ++function getLogger() { ++ if(logger === null) ++ logger = new Logger("gnome-shell"); ++ return logger; ++} ++ ++class Extension { ++ constructor(uuid) { ++ this._uuid = uuid; ++ ++ ExtensionUtils.initTranslations(GETTEXT_DOMAIN); ++ } ++ ++ enable() { ++ BackgroundMenu.BackgroundMenu = CustomBackgroundMenu; ++ Main.layoutManager._updateBackgrounds(); ++ } ++ ++ disable() { ++ BackgroundMenu.BackgroundMenu = OriginalBackgroundMenu; ++ Main.layoutManager._updateBackgrounds(); ++ } ++} ++ ++function init(meta) { ++ return new Extension(meta.uuid); ++} +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/extensions/custom-menu/stylesheet.css b/extensions/custom-menu/stylesheet.css +new file mode 100644 +index 00000000..25134b65 +--- /dev/null ++++ b/extensions/custom-menu/stylesheet.css +@@ -0,0 +1 @@ ++/* This extensions requires no special styling */ +diff --git a/meson.build b/meson.build +index c71636cb..23fb2c89 100644 +--- a/meson.build ++++ b/meson.build +@@ -46,6 +46,7 @@ all_extensions = default_extensions + all_extensions += [ + 'auto-move-windows', + 'classification-banner', ++ 'custom-menu', + 'dash-to-dock', + 'native-window-placement', + 'panel-favorites', +-- +2.38.1 diff --git a/SOURCES/window-list-touch.patch b/SOURCES/window-list-touch.patch new file mode 100644 index 0000000..c537d4a --- /dev/null +++ b/SOURCES/window-list-touch.patch @@ -0,0 +1,116 @@ +From 0d9210e9c19c1bd9535ffb75b4834c2ccd8db6c2 Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Florian=20M=C3=BCllner?= +Date: Thu, 21 Apr 2022 16:34:50 +0200 +Subject: [PATCH 1/2] window-list: Fix primary button action on touch + +If a click event was triggered via touch rather than a pointer +device, the button parameter is 0 rather than a mouse button +number. + +Account for that to make sure that touch events are not misinterpreted +as right clicks. + +https://gitlab.gnome.org/GNOME/gnome-shell-extensions/-/issues/146 + +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 e122cf5f..43885378 100644 +--- a/extensions/window-list/extension.js ++++ b/extensions/window-list/extension.js +@@ -381,7 +381,7 @@ class WindowButton extends BaseButton { + return; + } + +- if (button === 1) ++ if (!button || button === 1) + _minimizeOrActivateWindow(this.metaWindow); + else + _openMenu(this._contextMenu); +@@ -623,7 +623,7 @@ class AppButton extends BaseButton { + if (contextMenuWasOpen) + this._contextMenu.close(); + +- if (button === 1) { ++ if (!button || button === 1) { + if (menuWasOpen) + return; + +-- +2.36.1 + + +From b080bb7ee88d0e5b35dc4a967d2e44eab7921b6f Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Florian=20M=C3=BCllner?= +Date: Thu, 5 May 2022 20:55:20 +0200 +Subject: [PATCH 2/2] window-list: Open menu on long press + +Right-click isn't available on touch, so implement long-press as +an alternative. + +https://gitlab.gnome.org/GNOME/gnome-shell-extensions/-/issues/146 + +Part-of: +--- + extensions/window-list/extension.js | 42 +++++++++++++++++++++++++++++ + 1 file changed, 42 insertions(+) + +diff --git a/extensions/window-list/extension.js b/extensions/window-list/extension.js +index 43885378..3d1cd053 100644 +--- a/extensions/window-list/extension.js ++++ b/extensions/window-list/extension.js +@@ -266,6 +266,48 @@ const BaseButton = GObject.registerClass({ + this._updateVisibility(); + } + ++ _setLongPressTimeout() { ++ if (this._longPressTimeoutId) ++ return; ++ ++ const { longPressDuration } = Clutter.Settings.get_default(); ++ this._longPressTimeoutId = ++ GLib.timeout_add(GLib.PRIORITY_DEFAULT, longPressDuration, () => { ++ delete this._longPressTimeoutId; ++ ++ if (this._canOpenPopupMenu() && !this._contextMenu.isOpen) ++ _openMenu(this._contextMenu); ++ return GLib.SOURCE_REMOVE; ++ }); ++ } ++ ++ _removeLongPressTimeout() { ++ if (!this._longPressTimeoutId) ++ return; ++ GLib.source_remove(this._longPressTimeoutId); ++ delete this._longPressTimeoutId; ++ } ++ ++ vfunc_button_press_event(buttonEvent) { ++ if (buttonEvent.button === 1) ++ this._setLongPressTimeout(); ++ return super.vfunc_button_press_event(buttonEvent); ++ } ++ ++ vfunc_button_release_event(buttonEvent) { ++ this._removeLongPressTimeout(); ++ ++ return super.vfunc_button_release_event(buttonEvent); ++ } ++ ++ vfunc_touch_event(touchEvent) { ++ if (touchEvent.type === Clutter.EventType.TOUCH_BEGIN) ++ this._setLongPressTimeout(); ++ else if (touchEvent.type === Clutter.EventType.TOUCH_END) ++ this._removeLongPressTimeout(); ++ return super.vfunc_touch_event(touchEvent); ++ } ++ + activate() { + if (this.active) + return; +-- +2.36.1 + diff --git a/SPECS/gnome-shell-extensions.spec b/SPECS/gnome-shell-extensions.spec index 599fbef..26b7e9d 100644 --- a/SPECS/gnome-shell-extensions.spec +++ b/SPECS/gnome-shell-extensions.spec @@ -7,7 +7,7 @@ Name: gnome-shell-extensions Version: 40.7 -Release: 2%{?dist} +Release: 7%{?dist} Summary: Modify and extend GNOME Shell functionality and behavior License: GPLv2+ @@ -33,6 +33,13 @@ Patch010: 0001-heads-up-display-Add-extension-for-showing-persisten.patch Patch011: 0001-Add-gesture-inhibitor-extension.patch Patch012: gnome-classic-wayland.patch Patch013: 0001-desktop-icons-Fix-stuck-grab-issue-with-rubber-bandi.patch +Patch014: window-list-touch.patch +Patch015: 0001-classification-banner-Handle-fullscreen-monitors.patch +Patch016: 0001-desktop-icons-Don-t-grab-focus-on-click.patch +Patch017: 0001-desktopManager-Hook-into-LayoutManager-to-create-gri.patch +Patch018: 0001-gesture-inhibitor-Allow-inhibiting-workspace-switch-.patch +Patch019: 0001-desktop-icons-Don-t-use-blocking-IO.patch +Patch020: 0001-window-list-Explicitly-dispose-settings-on-destroy.patch %description GNOME Shell Extensions is a collection of extensions providing additional and @@ -42,6 +49,7 @@ Enabled extensions: * apps-menu * auto-move-windows * classification-banner + * custom-menu * dash-to-dock * desktop-icons * drive-menu @@ -119,6 +127,16 @@ Requires: %{pkg_prefix}-common = %{version}-%{release} 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}-dash-to-dock Summary: Show the dash outside the activities overview License: GPLv2+ @@ -328,6 +346,10 @@ workspaces. %{_datadir}/gnome-shell/extensions/classification-banner*/ +%files -n %{pkg_prefix}-custom-menu +%{_datadir}/gnome-shell/extensions/custom-menu*/ + + %files -n %{pkg_prefix}-dash-to-dock %{_datadir}/glib-2.0/schemas/org.gnome.shell.extensions.dash-to-dock.gschema.xml %{_datadir}/gnome-shell/extensions/dash-to-dock*/ @@ -406,6 +428,32 @@ workspaces. %changelog +* Wed Feb 15 2023 Florian Müllner - 40.7-7 +- Fix crash on `dconf update` + Resolves: #2170067 + +* Wed Jan 18 2023 Florian Müllner - 40.7-6 +- Avoid blocking IO in desktop-icons + Resolves: #2162019 + +* Thu Jan 12 2023 Florian Müllner - 40.7-5 +- Add custom-menu extension + Resolves: #2160553 + +* Wed Dec 14 2022 Florian Müllner - 40.7-4 +- Adjust classification banner position in fullscreen + Resolves: #2153524 +- Don't grab focus when clicking desktop grid + Resolves: #2150001 +- Make desktop icons resilient to background reloads + Resolves: #2139895 +- Allow disabling workspace switch gesture + Resolves: #2154358 + +* Wed Jun 22 2022 Florian Müllner - 40.7-3 +- Improve window-list on touch + Resolves: #2099286 + * Fri May 13 2022 Florian Müllner - 40.7-2 - Require desktop-icons for classic session Resolves: #2047697