import RHEL 10 Beta gnome-shell-extensions-47~alpha-3.el10
This commit is contained in:
		
							parent
							
								
									60980493eb
								
							
						
					
					
						commit
						ad1c9fe27a
					
				
							
								
								
									
										2
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										2
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							| @ -1 +1 @@ | |||||||
| SOURCES/gnome-shell-extensions-3.32.1.tar.xz | gnome-shell-extensions-47.alpha.tar.xz | ||||||
|  | |||||||
| @ -1 +0,0 @@ | |||||||
| 51c1c16bcd0dc9125834b32d7c539c38fa9c4f52 SOURCES/gnome-shell-extensions-3.32.1.tar.xz |  | ||||||
| @ -1,4 +1,4 @@ | |||||||
| From a3db60786407481efbfc4875f887d03b58f0a7b7 Mon Sep 17 00:00:00 2001 | From 8a2191519e2431a946aa1be36474bfe323a454a8 Mon Sep 17 00:00:00 2001 | ||||||
| From: =?UTF-8?q?Florian=20M=C3=BCllner?= <fmuellner@gnome.org> | From: =?UTF-8?q?Florian=20M=C3=BCllner?= <fmuellner@gnome.org> | ||||||
| Date: Fri, 23 Feb 2018 16:56:46 +0100 | Date: Fri, 23 Feb 2018 16:56:46 +0100 | ||||||
| Subject: [PATCH] Include top-icons in classic session | Subject: [PATCH] Include top-icons in classic session | ||||||
| @ -8,25 +8,25 @@ Subject: [PATCH] Include top-icons in classic session | |||||||
|  1 file changed, 1 insertion(+), 1 deletion(-) |  1 file changed, 1 insertion(+), 1 deletion(-) | ||||||
| 
 | 
 | ||||||
| diff --git a/meson.build b/meson.build
 | diff --git a/meson.build b/meson.build
 | ||||||
| index 6764f9a..32743ed 100644
 | index dce1731c..b915b68c 100644
 | ||||||
| --- a/meson.build
 | --- a/meson.build
 | ||||||
| +++ b/meson.build
 | +++ b/meson.build
 | ||||||
| @@ -36,6 +36,7 @@ classic_extensions = [
 | @@ -34,6 +34,7 @@ classic_extensions = [
 | ||||||
|    'desktop-icons', |    'apps-menu', | ||||||
|    'places-menu', |    'places-menu', | ||||||
|    'launch-new-instance', |    'launch-new-instance', | ||||||
| +  'top-icons',
 | +  'top-icons',
 | ||||||
|    'window-list' |    'window-list' | ||||||
|  ] |  ] | ||||||
|   |   | ||||||
| @@ -56,7 +57,6 @@ all_extensions += [
 | @@ -44,7 +45,6 @@ default_extensions += [
 | ||||||
|    'no-hot-corner', |    'light-style', | ||||||
|    'panel-favorites', |    'screenshot-window-sizer', | ||||||
|    'systemMonitor', |    'system-monitor', | ||||||
| -  'top-icons',
 | -  'top-icons',
 | ||||||
|    'updates-dialog', |    'windowsNavigator', | ||||||
|    'user-theme', |    'workspace-indicator' | ||||||
|    'window-grouper' |  ] | ||||||
| -- 
 | -- 
 | ||||||
| 2.21.0 | 2.45.2 | ||||||
| 
 | 
 | ||||||
| @ -0,0 +1,51 @@ | |||||||
|  | From 97d71d4a7ef4b1d4c9c2eab55db62173311f5366 Mon Sep 17 00:00:00 2001 | ||||||
|  | From: =?UTF-8?q?Florian=20M=C3=BCllner?= <fmuellner@gnome.org> | ||||||
|  | Date: Tue, 2 Jul 2024 19:04:10 +0200 | ||||||
|  | Subject: [PATCH] workspace-indicator: Re-fittsify workspace previews | ||||||
|  | 
 | ||||||
|  | For the window-list extension, it is important that the workspace | ||||||
|  | previews extend to the bottom edge for easier click targets. | ||||||
|  | 
 | ||||||
|  | That broke while merging the code with the workspace-indicator, | ||||||
|  | fix it again by moving the padding from the parent box into the | ||||||
|  | thumbnail children. | ||||||
|  | ---
 | ||||||
|  |  .../workspace-indicator/stylesheet-dark.css       | 15 ++++++++++++++- | ||||||
|  |  1 file changed, 14 insertions(+), 1 deletion(-) | ||||||
|  | 
 | ||||||
|  | diff --git a/extensions/workspace-indicator/stylesheet-dark.css b/extensions/workspace-indicator/stylesheet-dark.css
 | ||||||
|  | index b4a716b8..3c57c3e6 100644
 | ||||||
|  | --- a/extensions/workspace-indicator/stylesheet-dark.css
 | ||||||
|  | +++ b/extensions/workspace-indicator/stylesheet-dark.css
 | ||||||
|  | @@ -18,7 +18,6 @@
 | ||||||
|  |  } | ||||||
|  |   | ||||||
|  |  .workspace-indicator .workspaces-box { | ||||||
|  | -  padding: 5px;
 | ||||||
|  |    spacing: 3px; | ||||||
|  |  } | ||||||
|  |   | ||||||
|  | @@ -27,6 +26,20 @@
 | ||||||
|  |    spacing: 6px; | ||||||
|  |  } | ||||||
|  |   | ||||||
|  | +.workspace-indicator .workspace-box {
 | ||||||
|  | +  padding-top: 5px;
 | ||||||
|  | +  padding-bottom: 5px;
 | ||||||
|  | +}
 | ||||||
|  | +
 | ||||||
|  | +.workspace-indicator StButton:first-child:ltr > .workspace-box,
 | ||||||
|  | +.workspace-indicator StButton:last-child:rtl > .workspace-box {
 | ||||||
|  | +  padding-left: 5px;
 | ||||||
|  | +}
 | ||||||
|  | +.workspace-indicator StButton:last-child:ltr > .workspace-box,
 | ||||||
|  | +.workspace-indicator StButton:first-child:rtl > .workspace-box {
 | ||||||
|  | +  padding-right: 5px;
 | ||||||
|  | +}
 | ||||||
|  | +
 | ||||||
|  |  .workspace-indicator-menu .workspace-box { | ||||||
|  |    spacing: 6px; | ||||||
|  |  } | ||||||
|  | -- 
 | ||||||
|  | 2.45.2 | ||||||
|  | 
 | ||||||
| @ -1,186 +0,0 @@ | |||||||
| From 2a498fef3ec02d834346b545aeacba0a6224494e Mon Sep 17 00:00:00 2001 |  | ||||||
| From: rpm-build <rpm-build> |  | ||||||
| Date: Thu, 28 Jan 2021 00:06:12 +0100 |  | ||||||
| Subject: [PATCH] Add gesture-inhibitor extension |  | ||||||
| 
 |  | ||||||
| This extension may disable default GNOME Shell gestures. |  | ||||||
| ---
 |  | ||||||
|  extensions/gesture-inhibitor/extension.js     | 75 +++++++++++++++++++ |  | ||||||
|  extensions/gesture-inhibitor/meson.build      |  8 ++ |  | ||||||
|  extensions/gesture-inhibitor/metadata.json.in | 12 +++ |  | ||||||
|  ...l.extensions.gesture-inhibitor.gschema.xml | 25 +++++++ |  | ||||||
|  extensions/gesture-inhibitor/stylesheet.css   |  1 + |  | ||||||
|  meson.build                                   |  1 + |  | ||||||
|  6 files changed, 122 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 |  | ||||||
|  create mode 100644 extensions/gesture-inhibitor/stylesheet.css |  | ||||||
| 
 |  | ||||||
| diff --git a/extensions/gesture-inhibitor/extension.js b/extensions/gesture-inhibitor/extension.js
 |  | ||||||
| new file mode 100644 |  | ||||||
| index 00000000..e74ede2f
 |  | ||||||
| --- /dev/null
 |  | ||||||
| +++ b/extensions/gesture-inhibitor/extension.js
 |  | ||||||
| @@ -0,0 +1,75 @@
 |  | ||||||
| +/* 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 2 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 <http://www.gnu.org/licenses/>.
 |  | ||||||
| + *
 |  | ||||||
| + * SPDX-License-Identifier: GPL-2.0-or-later
 |  | ||||||
| + */
 |  | ||||||
| +
 |  | ||||||
| +/* exported init */
 |  | ||||||
| +
 |  | ||||||
| +const Clutter = imports.gi.Clutter;
 |  | ||||||
| +const ExtensionUtils = imports.misc.extensionUtils;
 |  | ||||||
| +const Me = ExtensionUtils.getCurrentExtension();
 |  | ||||||
| +const ViewSelector = imports.ui.viewSelector;
 |  | ||||||
| +const EdgeDragAction = imports.ui.edgeDragAction;
 |  | ||||||
| +const WindowManager = imports.ui.windowManager;
 |  | ||||||
| +const St = imports.gi.St;
 |  | ||||||
| +const Gio = imports.gi.Gio;
 |  | ||||||
| +
 |  | ||||||
| +class Extension {
 |  | ||||||
| +    constructor() {
 |  | ||||||
| +	this._settings = ExtensionUtils.getSettings();
 |  | ||||||
| +	let actions = global.stage.get_actions();
 |  | ||||||
| +
 |  | ||||||
| +	actions.forEach(a => {
 |  | ||||||
| +	    if (a instanceof ViewSelector.ShowOverviewAction)
 |  | ||||||
| +		this._showOverview = a;
 |  | ||||||
| +	    else if (a instanceof WindowManager.AppSwitchAction)
 |  | ||||||
| +		this._appSwitch = a;
 |  | ||||||
| +	    else if (a instanceof EdgeDragAction.EdgeDragAction &&
 |  | ||||||
| +		     a._side == St.Side.BOTTOM)
 |  | ||||||
| +		this._showOsk = a;
 |  | ||||||
| +	    else if (a instanceof EdgeDragAction.EdgeDragAction &&
 |  | ||||||
| +		     a._side == St.Side.TOP)
 |  | ||||||
| +		this._unfullscreen = a;
 |  | ||||||
| +	    else if (a instanceof EdgeDragAction.EdgeDragAction)
 |  | ||||||
| +		this._showAppGrid = a;
 |  | ||||||
| +	});
 |  | ||||||
| +
 |  | ||||||
| +	this._map = [
 |  | ||||||
| +	    { setting: 'overview', action: this._showOverview },
 |  | ||||||
| +	    { setting: 'app-switch', action: this._appSwitch },
 |  | ||||||
| +	    { setting: 'show-osk', action: this._showOsk },
 |  | ||||||
| +	    { setting: 'unfullscreen', action: this._unfullscreen },
 |  | ||||||
| +	    { setting: 'show-app-grid', action: this._showAppGrid }
 |  | ||||||
| +	];
 |  | ||||||
| +    }
 |  | ||||||
| +
 |  | ||||||
| +    enable() {
 |  | ||||||
| +	this._map.forEach(m => {
 |  | ||||||
| +	    this._settings.bind(m.setting, m.action, 'enabled',
 |  | ||||||
| +				Gio.SettingsBindFlags.DEFAULT);
 |  | ||||||
| +	});
 |  | ||||||
| +    }
 |  | ||||||
| +
 |  | ||||||
| +    disable() {
 |  | ||||||
| +	this._map.forEach(m => {
 |  | ||||||
| +	    m.action.enabled = true;
 |  | ||||||
| +	});
 |  | ||||||
| +    }
 |  | ||||||
| +}
 |  | ||||||
| +
 |  | ||||||
| +function init() {
 |  | ||||||
| +    return new Extension();
 |  | ||||||
| +}
 |  | ||||||
| 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..1d67dcc0
 |  | ||||||
| --- /dev/null
 |  | ||||||
| +++ b/extensions/gesture-inhibitor/org.gnome.shell.extensions.gesture-inhibitor.gschema.xml
 |  | ||||||
| @@ -0,0 +1,25 @@
 |  | ||||||
| +<schemalist>
 |  | ||||||
| +  <schema id="org.gnome.shell.extensions.gesture-inhibitor" path="/org/gnome/shell/extensions/gesture-inhibitor/">
 |  | ||||||
| +    <key name="show-app-grid" type="b">
 |  | ||||||
| +      <default>true</default>
 |  | ||||||
| +      <summary>Show app grid gesture</summary>
 |  | ||||||
| +    </key>
 |  | ||||||
| +    <key name="show-osk" type="b">
 |  | ||||||
| +      <default>true</default>
 |  | ||||||
| +      <summary>Show OSK gesture</summary>
 |  | ||||||
| +    </key>
 |  | ||||||
| +    <key name="overview" type="b">
 |  | ||||||
| +      <default>true</default>
 |  | ||||||
| +      <summary>Show Overview gesture</summary>
 |  | ||||||
| +    </key>
 |  | ||||||
| +    <key name="app-switch" type="b">
 |  | ||||||
| +      <default>true</default>
 |  | ||||||
| +      <summary>Application switch gesture</summary>
 |  | ||||||
| +    </key>
 |  | ||||||
| +    <key name="unfullscreen" type="b">
 |  | ||||||
| +      <default>true</default>
 |  | ||||||
| +      <summary>Unfullscreen gesture</summary>
 |  | ||||||
| +    </key>
 |  | ||||||
| +  </schema>
 |  | ||||||
| +</schemalist>
 |  | ||||||
| +
 |  | ||||||
| diff --git a/extensions/gesture-inhibitor/stylesheet.css b/extensions/gesture-inhibitor/stylesheet.css
 |  | ||||||
| new file mode 100644 |  | ||||||
| index 00000000..37b93f21
 |  | ||||||
| --- /dev/null
 |  | ||||||
| +++ b/extensions/gesture-inhibitor/stylesheet.css
 |  | ||||||
| @@ -0,0 +1 @@
 |  | ||||||
| +/* Add your custom extension styling here */
 |  | ||||||
| diff --git a/meson.build b/meson.build
 |  | ||||||
| index e163b84d..ba84f8f3 100644
 |  | ||||||
| --- a/meson.build
 |  | ||||||
| +++ b/meson.build
 |  | ||||||
| @@ -55,6 +55,7 @@ all_extensions += [
 |  | ||||||
|    'dash-to-dock', |  | ||||||
|    'dash-to-panel', |  | ||||||
|    'disable-screenshield', |  | ||||||
| +  'gesture-inhibitor',
 |  | ||||||
|    'native-window-placement', |  | ||||||
|    'no-hot-corner', |  | ||||||
|    'panel-favorites', |  | ||||||
| -- 
 |  | ||||||
| 2.32.0 |  | ||||||
| 
 |  | ||||||
| @ -1,83 +0,0 @@ | |||||||
| From f5e47cd8ca32ae433f6906b01a509c5a304894d9 Mon Sep 17 00:00:00 2001 |  | ||||||
| From: =?UTF-8?q?Florian=20M=C3=BCllner?= <fmuellner@gnome.org> |  | ||||||
| Date: Sat, 24 Oct 2020 01:14:44 +0200 |  | ||||||
| Subject: [PATCH] Update desktop-icons gettext domain |  | ||||||
| 
 |  | ||||||
| ---
 |  | ||||||
|  extensions/desktop-icons/createFolderDialog.js | 2 +- |  | ||||||
|  extensions/desktop-icons/desktopGrid.js        | 2 +- |  | ||||||
|  extensions/desktop-icons/fileItem.js           | 2 +- |  | ||||||
|  extensions/desktop-icons/prefs.js              | 8 +++++--- |  | ||||||
|  4 files changed, 8 insertions(+), 6 deletions(-) |  | ||||||
| 
 |  | ||||||
| diff --git a/extensions/desktop-icons/createFolderDialog.js b/extensions/desktop-icons/createFolderDialog.js
 |  | ||||||
| index f3e40e9..5038762 100644
 |  | ||||||
| --- a/extensions/desktop-icons/createFolderDialog.js
 |  | ||||||
| +++ b/extensions/desktop-icons/createFolderDialog.js
 |  | ||||||
| @@ -21,7 +21,7 @@ const { Clutter, GObject, GLib, Gio, St } = imports.gi;
 |  | ||||||
|  const Signals = imports.signals; |  | ||||||
|   |  | ||||||
|  const Dialog = imports.ui.dialog; |  | ||||||
| -const Gettext = imports.gettext.domain('desktop-icons');
 |  | ||||||
| +const Gettext = imports.gettext.domain('gnome-shell-extensions');
 |  | ||||||
|  const ModalDialog = imports.ui.modalDialog; |  | ||||||
|  const ShellEntry = imports.ui.shellEntry; |  | ||||||
|  const Tweener = imports.ui.tweener; |  | ||||||
| diff --git a/extensions/desktop-icons/desktopGrid.js b/extensions/desktop-icons/desktopGrid.js
 |  | ||||||
| index a2d1f12..94d2dfd 100644
 |  | ||||||
| --- a/extensions/desktop-icons/desktopGrid.js
 |  | ||||||
| +++ b/extensions/desktop-icons/desktopGrid.js
 |  | ||||||
| @@ -44,7 +44,7 @@ const Util = imports.misc.util;
 |  | ||||||
|   |  | ||||||
|  const Clipboard = St.Clipboard.get_default(); |  | ||||||
|  const CLIPBOARD_TYPE = St.ClipboardType.CLIPBOARD; |  | ||||||
| -const Gettext = imports.gettext.domain('desktop-icons');
 |  | ||||||
| +const Gettext = imports.gettext.domain('gnome-shell-extensions');
 |  | ||||||
|   |  | ||||||
|  const _ = Gettext.gettext; |  | ||||||
|   |  | ||||||
| diff --git a/extensions/desktop-icons/fileItem.js b/extensions/desktop-icons/fileItem.js
 |  | ||||||
| index 0c6a54d..d6d43c9 100644
 |  | ||||||
| --- a/extensions/desktop-icons/fileItem.js
 |  | ||||||
| +++ b/extensions/desktop-icons/fileItem.js
 |  | ||||||
| @@ -42,7 +42,7 @@ const Prefs = Me.imports.prefs;
 |  | ||||||
|  const DBusUtils = Me.imports.dbusUtils; |  | ||||||
|  const DesktopIconsUtil = Me.imports.desktopIconsUtil; |  | ||||||
|   |  | ||||||
| -const Gettext = imports.gettext.domain('desktop-icons');
 |  | ||||||
| +const Gettext = imports.gettext.domain('gnome-shell-extensions');
 |  | ||||||
|   |  | ||||||
|  const _ = Gettext.gettext; |  | ||||||
|   |  | ||||||
| diff --git a/extensions/desktop-icons/prefs.js b/extensions/desktop-icons/prefs.js
 |  | ||||||
| index 4b8d986..51daf15 100644
 |  | ||||||
| --- a/extensions/desktop-icons/prefs.js
 |  | ||||||
| +++ b/extensions/desktop-icons/prefs.js
 |  | ||||||
| @@ -26,7 +26,7 @@ const Gettext = imports.gettext;
 |  | ||||||
|   |  | ||||||
|  const Config = imports.misc.config; |  | ||||||
|   |  | ||||||
| -var _ = Gettext.domain('desktop-icons').gettext;
 |  | ||||||
| +var _ = Gettext.domain('gnome-shell-extensions').gettext;
 |  | ||||||
|   |  | ||||||
|  const SCHEMA_NAUTILUS = 'org.gnome.nautilus.preferences'; |  | ||||||
|  const SCHEMA_GTK = 'org.gtk.Settings.FileChooser'; |  | ||||||
| @@ -51,11 +51,13 @@ var CLICK_POLICY_SINGLE = false;
 |  | ||||||
|  function initTranslations() { |  | ||||||
|      let extension = ExtensionUtils.getCurrentExtension(); |  | ||||||
|   |  | ||||||
| +    let domain = extension.metadata['gettext-domain'] || 'desktop-icons';
 |  | ||||||
| +
 |  | ||||||
|      let localedir = extension.dir.get_child('locale'); |  | ||||||
|      if (localedir.query_exists(null)) |  | ||||||
| -        Gettext.bindtextdomain('desktop-icons', localedir.get_path());
 |  | ||||||
| +        Gettext.bindtextdomain(domain, localedir.get_path());
 |  | ||||||
|      else |  | ||||||
| -        Gettext.bindtextdomain('desktop-icons', Config.LOCALEDIR);
 |  | ||||||
| +        Gettext.bindtextdomain(domain, Config.LOCALEDIR);
 |  | ||||||
|  } |  | ||||||
|   |  | ||||||
|  function init() { |  | ||||||
| -- 
 |  | ||||||
| 2.21.1 |  | ||||||
| 
 |  | ||||||
| @ -1,98 +0,0 @@ | |||||||
| From e768ad73e2d68b3f1567051675ba0539a75e3105 Mon Sep 17 00:00:00 2001 |  | ||||||
| From: =?UTF-8?q?Florian=20M=C3=BCllner?= <fmuellner@gnome.org> |  | ||||||
| Date: Sat, 18 May 2019 19:37:05 +0200 |  | ||||||
| Subject: [PATCH] Update style |  | ||||||
| 
 |  | ||||||
| ---
 |  | ||||||
|  data/gnome-shell-sass | 2 +- |  | ||||||
|  1 file changed, 1 insertion(+), 1 deletion(-) |  | ||||||
| 
 |  | ||||||
| Submodule data/gnome-shell-sass 1a56956..8842e57: |  | ||||||
| diff --git a/data/gnome-shell-sass/_common.scss b/data/gnome-shell-sass/_common.scss
 |  | ||||||
| index a6357ba..62d9c82 100644
 |  | ||||||
| --- a/data/gnome-shell-sass/_common.scss
 |  | ||||||
| +++ b/data/gnome-shell-sass/_common.scss
 |  | ||||||
| @@ -571,6 +571,18 @@ StScrollBar {
 |  | ||||||
|    app menu inside the main app window itself rather than the top bar |  | ||||||
|  */ |  | ||||||
|   |  | ||||||
| +/*************
 |  | ||||||
| + * App Icons *
 |  | ||||||
| + *************/
 |  | ||||||
| +/* Outline for low res icons */
 |  | ||||||
| +.lowres-icon {
 |  | ||||||
| +    icon-shadow: 0 1px 2px rgba(0,0,0,0.3);
 |  | ||||||
| +}
 |  | ||||||
| +
 |  | ||||||
| +/* Drapshadow for large icons */
 |  | ||||||
| +.icon-dropshadow {
 |  | ||||||
| +  icon-shadow: 0 1px 2px rgba(0,0,0,0.4);
 |  | ||||||
| +}
 |  | ||||||
|   |  | ||||||
|  /* OSD */ |  | ||||||
|  .osd-window { |  | ||||||
| @@ -680,7 +692,8 @@ StScrollBar {
 |  | ||||||
|      spacing: 8px; |  | ||||||
|    } |  | ||||||
|   |  | ||||||
| -  .ws-switcher-active-up, .ws-switcher-active-down {
 |  | ||||||
| +  .ws-switcher-active-up, .ws-switcher-active-down,
 |  | ||||||
| +  .ws-switcher-active-left, .ws-switcher-active-right {
 |  | ||||||
|      height: 50px; |  | ||||||
|      background-color: $selected_bg_color; |  | ||||||
|      color: $selected_fg_color; |  | ||||||
| @@ -781,6 +794,11 @@ StScrollBar {
 |  | ||||||
|        color: lighten($fg_color,10%); |  | ||||||
|      } |  | ||||||
|   |  | ||||||
| +    .panel-logo-icon {
 |  | ||||||
| +      padding-right: .4em;
 |  | ||||||
| +      icon-size: 1em;
 |  | ||||||
| +    }
 |  | ||||||
| +
 |  | ||||||
|      .system-status-icon { icon-size: 1.09em; padding: 0 5px; } |  | ||||||
|      .unlock-screen &, |  | ||||||
|      .login-screen &, |  | ||||||
| @@ -1406,6 +1424,14 @@ StScrollBar {
 |  | ||||||
|   |  | ||||||
|    } |  | ||||||
|   |  | ||||||
| +  .app-well-hover-text {
 |  | ||||||
| +      text-align: center;
 |  | ||||||
| +      color: $osd_fg_color;
 |  | ||||||
| +      background-color: $osd_bg_color;
 |  | ||||||
| +      border-radius: 5px;
 |  | ||||||
| +      padding: 3px;
 |  | ||||||
| +  }
 |  | ||||||
| +
 |  | ||||||
|    .app-well-app-running-dot { //running apps indicator |  | ||||||
|      width: 10px; height: 3px; |  | ||||||
|      background-color: $selected_bg_color; |  | ||||||
| @@ -1801,7 +1827,12 @@ StScrollBar {
 |  | ||||||
|    .login-dialog-banner { color: darken($osd_fg_color,10%); } |  | ||||||
|    .login-dialog-button-box { spacing: 5px; } |  | ||||||
|    .login-dialog-message-warning { color: $warning_color; } |  | ||||||
| -  .login-dialog-message-hint { padding-top: 0; padding-bottom: 20px; }
 |  | ||||||
| +  .login-dialog-message-hint, .login-dialog-message {
 |  | ||||||
| +    color: darken($osd_fg_color, 20%);
 |  | ||||||
| +    padding-top: 0;
 |  | ||||||
| +    padding-bottom: 20px;
 |  | ||||||
| +    min-height: 2.75em;
 |  | ||||||
| +  }
 |  | ||||||
|    .login-dialog-user-selection-box { padding: 100px 0px; } |  | ||||||
|    .login-dialog-not-listed-label { |  | ||||||
|      padding-left: 2px; |  | ||||||
| @@ -1856,6 +1887,10 @@ StScrollBar {
 |  | ||||||
|        padding-bottom: 12px; |  | ||||||
|        spacing: 8px; |  | ||||||
|        width: 23em; |  | ||||||
| +      .login-dialog-timed-login-indicator {
 |  | ||||||
| +          height: 2px;
 |  | ||||||
| +          background-color: darken($fg_color,40%);
 |  | ||||||
| +      }
 |  | ||||||
|    } |  | ||||||
|   |  | ||||||
|    .login-dialog-prompt-label { |  | ||||||
| -- 
 |  | ||||||
| 2.21.0 |  | ||||||
| 
 |  | ||||||
| @ -1,33 +0,0 @@ | |||||||
| From 0bbeadadc41128b2be1f2b56c60b5a7a671d40da Mon Sep 17 00:00:00 2001 |  | ||||||
| From: =?UTF-8?q?Florian=20M=C3=BCllner?= <fmuellner@gnome.org> |  | ||||||
| Date: Thu, 27 Jun 2019 03:57:53 +0200 |  | ||||||
| Subject: [PATCH] apps-menu: Add missing chain-up |  | ||||||
| 
 |  | ||||||
| PanelMenu.Button is a bit weird in that it also "contains" its parent |  | ||||||
| actor. That container is supposed to be destroyed with the button, but |  | ||||||
| as we currently don't chain up to the parent class' _onDestroy(), we |  | ||||||
| leave behind an empty container every time the extension is disabled. |  | ||||||
| 
 |  | ||||||
| Fix this by adding the missing chain-up. |  | ||||||
| 
 |  | ||||||
| https://gitlab.gnome.org/GNOME/gnome-shell-extensions/merge_requests/75 |  | ||||||
| ---
 |  | ||||||
|  extensions/apps-menu/extension.js | 2 ++ |  | ||||||
|  1 file changed, 2 insertions(+) |  | ||||||
| 
 |  | ||||||
| diff --git a/extensions/apps-menu/extension.js b/extensions/apps-menu/extension.js
 |  | ||||||
| index b9e7111..9803cc1 100644
 |  | ||||||
| --- a/extensions/apps-menu/extension.js
 |  | ||||||
| +++ b/extensions/apps-menu/extension.js
 |  | ||||||
| @@ -433,6 +433,8 @@ class ApplicationsButton extends PanelMenu.Button {
 |  | ||||||
|      } |  | ||||||
|   |  | ||||||
|      _onDestroy() { |  | ||||||
| +        super._onDestroy();
 |  | ||||||
| +
 |  | ||||||
|          Main.overview.disconnect(this._showingId); |  | ||||||
|          Main.overview.disconnect(this._hidingId); |  | ||||||
|          appSys.disconnect(this._installedChangedId); |  | ||||||
| -- 
 |  | ||||||
| 2.21.0 |  | ||||||
| 
 |  | ||||||
| @ -1,40 +0,0 @@ | |||||||
| From 52ee25effa3debb21307e33ac223cf48ac7bc57a Mon Sep 17 00:00:00 2001 |  | ||||||
| From: =?UTF-8?q?Florian=20M=C3=BCllner?= <fmuellner@gnome.org> |  | ||||||
| Date: Thu, 17 Mar 2016 17:15:38 +0100 |  | ||||||
| Subject: [PATCH] apps-menu: Explicitly set label_actor |  | ||||||
| 
 |  | ||||||
| For some reason orca fails to pick up the label of category items, |  | ||||||
| so set the label_actor explicitly as workaround. |  | ||||||
| ---
 |  | ||||||
|  extensions/apps-menu/extension.js | 8 ++++++-- |  | ||||||
|  1 file changed, 6 insertions(+), 2 deletions(-) |  | ||||||
| 
 |  | ||||||
| diff --git a/extensions/apps-menu/extension.js b/extensions/apps-menu/extension.js
 |  | ||||||
| index d62e3d7..cc399c6 100644
 |  | ||||||
| --- a/extensions/apps-menu/extension.js
 |  | ||||||
| +++ b/extensions/apps-menu/extension.js
 |  | ||||||
| @@ -29,7 +29,9 @@ class ActivitiesMenuItem extends PopupMenu.PopupBaseMenuItem {
 |  | ||||||
|      constructor(button) { |  | ||||||
|          super(); |  | ||||||
|          this._button = button; |  | ||||||
| -        this.actor.add_child(new St.Label({ text: _('Activities Overview') }));
 |  | ||||||
| +        let label = new St.Label({ text: _('Activities Overview') });
 |  | ||||||
| +        this.actor.add_child(label);
 |  | ||||||
| +        this.actor.label_actor = label;
 |  | ||||||
|      } |  | ||||||
|   |  | ||||||
|      activate(event) { |  | ||||||
| @@ -120,7 +122,9 @@ class CategoryMenuItem extends PopupMenu.PopupBaseMenuItem {
 |  | ||||||
|          else |  | ||||||
|              name = _('Favorites'); |  | ||||||
|   |  | ||||||
| -        this.actor.add_child(new St.Label({ text: name }));
 |  | ||||||
| +        let label = new St.Label({ text: name });
 |  | ||||||
| +        this.actor.add_child(label);
 |  | ||||||
| +        this.actor.label_actor = label;
 |  | ||||||
|          this.actor.connect('motion-event', this._onMotionEvent.bind(this)); |  | ||||||
|      } |  | ||||||
|   |  | ||||||
| -- 
 |  | ||||||
| 2.21.0 |  | ||||||
| 
 |  | ||||||
| @ -1,32 +0,0 @@ | |||||||
| From 3e3634b59455da0cbae1de4af0ce5cf97be8b80d Mon Sep 17 00:00:00 2001 |  | ||||||
| From: Ray Strode <rstrode@redhat.com> |  | ||||||
| Date: Tue, 21 Jan 2014 16:48:17 -0500 |  | ||||||
| Subject: [PATCH] apps-menu: add logo icon to Applications menu |  | ||||||
| 
 |  | ||||||
| Brand requested it. |  | ||||||
| ---
 |  | ||||||
|  extensions/apps-menu/extension.js | 8 ++++++++ |  | ||||||
|  1 file changed, 8 insertions(+) |  | ||||||
| 
 |  | ||||||
| diff --git a/extensions/apps-menu/extension.js b/extensions/apps-menu/extension.js
 |  | ||||||
| index d7ba570..d62e3d7 100644
 |  | ||||||
| --- a/extensions/apps-menu/extension.js
 |  | ||||||
| +++ b/extensions/apps-menu/extension.js
 |  | ||||||
| @@ -390,6 +390,14 @@ class ApplicationsButton extends PanelMenu.Button {
 |  | ||||||
|   |  | ||||||
|          let hbox = new St.BoxLayout({ style_class: 'panel-status-menu-box' }); |  | ||||||
|   |  | ||||||
| +        let iconFile = Gio.File.new_for_path(
 |  | ||||||
| +            '/usr/share/icons/hicolor/scalable/apps/start-here.svg');
 |  | ||||||
| +        this._icon = new St.Icon({
 |  | ||||||
| +            gicon: new Gio.FileIcon({ file: iconFile }),
 |  | ||||||
| +            style_class: 'panel-logo-icon'
 |  | ||||||
| +        });
 |  | ||||||
| +        hbox.add_actor(this._icon);
 |  | ||||||
| +
 |  | ||||||
|          this._label = new St.Label({ |  | ||||||
|              text: _('Applications'), |  | ||||||
|              y_expand: true, |  | ||||||
| -- 
 |  | ||||||
| 2.21.0 |  | ||||||
| 
 |  | ||||||
| @ -1,33 +0,0 @@ | |||||||
| From 8a5e793b3d984f3acc378cf8914410311e9dde0e Mon Sep 17 00:00:00 2001 |  | ||||||
| From: Daniel van Vugt <daniel.van.vugt@canonical.com> |  | ||||||
| Date: Thu, 28 Jan 2021 16:33:50 +0800 |  | ||||||
| Subject: [PATCH] auto-move-windows: Don't move windows already on all |  | ||||||
|  workspaces |  | ||||||
| 
 |  | ||||||
| This fixes a particular case of mutter#992. |  | ||||||
| 
 |  | ||||||
| Although gnome-shell will also be softened to not crash in future, it's |  | ||||||
| also a good idea for the extension to explicitly decide how it wants to |  | ||||||
| handle windows that are already on all workspaces. |  | ||||||
| 
 |  | ||||||
| Part-of: <https://gitlab.gnome.org/GNOME/gnome-shell-extensions/-/merge_requests/157> |  | ||||||
| ---
 |  | ||||||
|  extensions/auto-move-windows/extension.js | 2 +- |  | ||||||
|  1 file changed, 1 insertion(+), 1 deletion(-) |  | ||||||
| 
 |  | ||||||
| diff --git a/extensions/auto-move-windows/extension.js b/extensions/auto-move-windows/extension.js
 |  | ||||||
| index b9bc3a0..3859809 100644
 |  | ||||||
| --- a/extensions/auto-move-windows/extension.js
 |  | ||||||
| +++ b/extensions/auto-move-windows/extension.js
 |  | ||||||
| @@ -72,7 +72,7 @@ class WindowMover {
 |  | ||||||
|      } |  | ||||||
|   |  | ||||||
|      _moveWindow(window, workspaceNum) { |  | ||||||
| -        if (window.skip_taskbar)
 |  | ||||||
| +        if (window.skip_taskbar || window.is_on_all_workspaces())
 |  | ||||||
|              return; |  | ||||||
|   |  | ||||||
|          // ensure we have the required number of workspaces |  | ||||||
| -- 
 |  | ||||||
| 2.37.1 |  | ||||||
| 
 |  | ||||||
| @ -1,68 +0,0 @@ | |||||||
| From 3d32ab1848011a3a7af97255307b3541a7553b09 Mon Sep 17 00:00:00 2001 |  | ||||||
| From: =?UTF-8?q?Florian=20M=C3=BCllner?= <fmuellner@gnome.org> |  | ||||||
| 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 6c2fe007..1cf03b3f 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;
 |  | ||||||
| +
 |  | ||||||
|              if (this._settings) |  | ||||||
|                  this._settings.run_dispose(); |  | ||||||
|              this._settings = null; |  | ||||||
| @@ -95,6 +98,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', |  | ||||||
| @@ -111,6 +119,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 |  | ||||||
| 
 |  | ||||||
| @ -1,39 +0,0 @@ | |||||||
| From b9ba6b8708c18fb14033150fdb02a508457e0a17 Mon Sep 17 00:00:00 2001 |  | ||||||
| From: =?UTF-8?q?Florian=20M=C3=BCllner?= <fmuellner@gnome.org> |  | ||||||
| Date: Fri, 2 Feb 2024 15:39:32 +0100 |  | ||||||
| Subject: [PATCH] classification-banner: Hide from picks |  | ||||||
| 
 |  | ||||||
| Banners are laid out via a fullscreen actor. While the actor is |  | ||||||
| not reactive, it can still interfere with picks (for example |  | ||||||
| during drag-and-drop operations). |  | ||||||
| 
 |  | ||||||
| Avoid that by explicitly hiding the actor from picks. |  | ||||||
| ---
 |  | ||||||
|  extensions/classification-banner/extension.js | 4 +++- |  | ||||||
|  1 file changed, 3 insertions(+), 1 deletion(-) |  | ||||||
| 
 |  | ||||||
| diff --git a/extensions/classification-banner/extension.js b/extensions/classification-banner/extension.js
 |  | ||||||
| index ea788022..2bde741e 100644
 |  | ||||||
| --- a/extensions/classification-banner/extension.js
 |  | ||||||
| +++ b/extensions/classification-banner/extension.js
 |  | ||||||
| @@ -18,7 +18,7 @@
 |  | ||||||
|   |  | ||||||
|  /* exported init */ |  | ||||||
|   |  | ||||||
| -const { Clutter, Gio, GLib, GObject, St } = imports.gi;
 |  | ||||||
| +const { Clutter, Gio, GLib, GObject, Shell, St } = imports.gi;
 |  | ||||||
|   |  | ||||||
|  const ExtensionUtils = imports.misc.extensionUtils; |  | ||||||
|  const Layout = imports.ui.layout; |  | ||||||
| @@ -34,6 +34,8 @@ class ClassificationBanner extends Clutter.Actor {
 |  | ||||||
|          }); |  | ||||||
|          this._monitorConstraint = constraint; |  | ||||||
|   |  | ||||||
| +        Shell.util_set_hidden_from_pick(this, true);
 |  | ||||||
| +
 |  | ||||||
|          this._settings = ExtensionUtils.getSettings(); |  | ||||||
|          this.connect('destroy', () => { |  | ||||||
|              if (this._fullscreenChangedId) |  | ||||||
| -- 
 |  | ||||||
| 2.43.0 |  | ||||||
| 
 |  | ||||||
| @ -1,208 +0,0 @@ | |||||||
| From 3c62051c0a154ae987bb0126e8adb6cd86aa69a2 Mon Sep 17 00:00:00 2001 |  | ||||||
| From: =?UTF-8?q?Florian=20M=C3=BCllner?= <fmuellner@gnome.org> |  | ||||||
| Date: Mon, 24 Feb 2020 16:17:05 +0100 |  | ||||||
| Subject: [PATCH] dashToDock: Handle no-overview case |  | ||||||
| 
 |  | ||||||
| There is no longer an overview in GNOME Classic, so in order to be |  | ||||||
| used in that environment, the extension must deal with that case. |  | ||||||
| ---
 |  | ||||||
|  extensions/dash-to-dock/docking.js | 122 ++++++++++++++++------------- |  | ||||||
|  1 file changed, 68 insertions(+), 54 deletions(-) |  | ||||||
| 
 |  | ||||||
| diff --git a/extensions/dash-to-dock/docking.js b/extensions/dash-to-dock/docking.js
 |  | ||||||
| index d35094b..2b8353a 100644
 |  | ||||||
| --- a/extensions/dash-to-dock/docking.js
 |  | ||||||
| +++ b/extensions/dash-to-dock/docking.js
 |  | ||||||
| @@ -233,7 +233,7 @@ var DockedDash = class DashToDock {
 |  | ||||||
|          // Create a new dash object |  | ||||||
|          this.dash = new MyDash.MyDash(this._settings, this._remoteModel, this._monitorIndex); |  | ||||||
|   |  | ||||||
| -        if (!this._settings.get_boolean('show-show-apps-button'))
 |  | ||||||
| +        if (Main.overview.isDummy || !this._settings.get_boolean('show-show-apps-button'))
 |  | ||||||
|              this.dash.hideShowAppsButton(); |  | ||||||
|   |  | ||||||
|          // Create the main actor and the containers for sliding in and out and |  | ||||||
| @@ -272,45 +272,11 @@ var DockedDash = class DashToDock {
 |  | ||||||
|          this.dash.actor.add_constraint(this.constrainSize); |  | ||||||
|   |  | ||||||
|          this._signalsHandler.add([ |  | ||||||
| -            Main.overview,
 |  | ||||||
| -            'item-drag-begin',
 |  | ||||||
| -            this._onDragStart.bind(this)
 |  | ||||||
| -        ], [
 |  | ||||||
| -            Main.overview,
 |  | ||||||
| -            'item-drag-end',
 |  | ||||||
| -            this._onDragEnd.bind(this)
 |  | ||||||
| -        ], [
 |  | ||||||
| -            Main.overview,
 |  | ||||||
| -            'item-drag-cancelled',
 |  | ||||||
| -            this._onDragEnd.bind(this)
 |  | ||||||
| -        ], [
 |  | ||||||
|              // update when workarea changes, for instance if  other extensions modify the struts |  | ||||||
|              //(like moving th panel at the bottom) |  | ||||||
|              global.display, |  | ||||||
|              'workareas-changed', |  | ||||||
|              this._resetPosition.bind(this) |  | ||||||
| -        ], [
 |  | ||||||
| -            Main.overview,
 |  | ||||||
| -            'showing',
 |  | ||||||
| -            this._onOverviewShowing.bind(this)
 |  | ||||||
| -        ], [
 |  | ||||||
| -            Main.overview,
 |  | ||||||
| -            'hiding',
 |  | ||||||
| -            this._onOverviewHiding.bind(this)
 |  | ||||||
| -        ], [
 |  | ||||||
| -            // Hide on appview
 |  | ||||||
| -            Main.overview.viewSelector,
 |  | ||||||
| -            'page-changed',
 |  | ||||||
| -            this._pageChanged.bind(this)
 |  | ||||||
| -        ], [
 |  | ||||||
| -            Main.overview.viewSelector,
 |  | ||||||
| -            'page-empty',
 |  | ||||||
| -            this._onPageEmpty.bind(this)
 |  | ||||||
| -        ], [
 |  | ||||||
| -            // Ensure the ShowAppsButton status is kept in sync
 |  | ||||||
| -            Main.overview.viewSelector._showAppsButton,
 |  | ||||||
| -            'notify::checked',
 |  | ||||||
| -            this._syncShowAppsButtonToggled.bind(this)
 |  | ||||||
|          ], [ |  | ||||||
|              global.display, |  | ||||||
|              'in-fullscreen-changed', |  | ||||||
| @@ -325,15 +291,6 @@ var DockedDash = class DashToDock {
 |  | ||||||
|              this.dash, |  | ||||||
|              'icon-size-changed', |  | ||||||
|              () => { Main.overview.dashIconSize = this.dash.iconSize; } |  | ||||||
| -        ], [
 |  | ||||||
| -            // This duplicate the similar signal which is in owerview.js.
 |  | ||||||
| -            // Being connected and thus executed later this effectively
 |  | ||||||
| -            // overwrite any attempt to use the size of the default dash
 |  | ||||||
| -            //which given the customization is usually much smaller.
 |  | ||||||
| -            // I can't easily disconnect the original signal
 |  | ||||||
| -            Main.overview._controls.dash,
 |  | ||||||
| -            'icon-size-changed',
 |  | ||||||
| -            () => { Main.overview.dashIconSize = this.dash.iconSize; }
 |  | ||||||
|          ], [ |  | ||||||
|              // sync hover after a popupmenu is closed |  | ||||||
|              this.dash, |  | ||||||
| @@ -341,6 +298,53 @@ var DockedDash = class DashToDock {
 |  | ||||||
|              () => { this._box.sync_hover() } |  | ||||||
|          ]); |  | ||||||
|   |  | ||||||
| +        if (!Main.overview.isDummy) {
 |  | ||||||
| +            this._signalsHandler.add([
 |  | ||||||
| +                Main.overview,
 |  | ||||||
| +                'item-drag-begin',
 |  | ||||||
| +                this._onDragStart.bind(this)
 |  | ||||||
| +            ], [
 |  | ||||||
| +                Main.overview,
 |  | ||||||
| +                'item-drag-end',
 |  | ||||||
| +                this._onDragEnd.bind(this)
 |  | ||||||
| +            ], [
 |  | ||||||
| +                Main.overview,
 |  | ||||||
| +                'item-drag-cancelled',
 |  | ||||||
| +                this._onDragEnd.bind(this)
 |  | ||||||
| +            ], [
 |  | ||||||
| +                Main.overview,
 |  | ||||||
| +                'showing',
 |  | ||||||
| +                this._onOverviewShowing.bind(this)
 |  | ||||||
| +            ], [
 |  | ||||||
| +                Main.overview,
 |  | ||||||
| +                'hiding',
 |  | ||||||
| +                this._onOverviewHiding.bind(this)
 |  | ||||||
| +            ], [
 |  | ||||||
| +                // Hide on appview
 |  | ||||||
| +                Main.overview.viewSelector,
 |  | ||||||
| +                'page-changed',
 |  | ||||||
| +                this._pageChanged.bind(this)
 |  | ||||||
| +            ], [
 |  | ||||||
| +                Main.overview.viewSelector,
 |  | ||||||
| +                'page-empty',
 |  | ||||||
| +                this._onPageEmpty.bind(this)
 |  | ||||||
| +            ], [
 |  | ||||||
| +                // Ensure the ShowAppsButton status is kept in sync
 |  | ||||||
| +                Main.overview.viewSelector._showAppsButton,
 |  | ||||||
| +                'notify::checked',
 |  | ||||||
| +                this._syncShowAppsButtonToggled.bind(this)
 |  | ||||||
| +            ], [
 |  | ||||||
| +                // This duplicate the similar signal which is in owerview.js.
 |  | ||||||
| +                // Being connected and thus executed later this effectively
 |  | ||||||
| +                // overwrite any attempt to use the size of the default dash
 |  | ||||||
| +                //which given the customization is usually much smaller.
 |  | ||||||
| +                // I can't easily disconnect the original signal
 |  | ||||||
| +                Main.overview._controls.dash,
 |  | ||||||
| +                'icon-size-changed',
 |  | ||||||
| +                () => { Main.overview.dashIconSize = this.dash.iconSize; }
 |  | ||||||
| +            ]);
 |  | ||||||
| +        }
 |  | ||||||
| +
 |  | ||||||
|          this._injectionsHandler = new Utils.InjectionsHandler(); |  | ||||||
|          this._themeManager = new Theming.ThemeManager(this._settings, this); |  | ||||||
|   |  | ||||||
| @@ -370,14 +374,17 @@ var DockedDash = class DashToDock {
 |  | ||||||
|          this._dashSpacer = new OverviewControls.DashSpacer(); |  | ||||||
|          this._dashSpacer.setDashActor(this._box); |  | ||||||
|   |  | ||||||
| -        if (this._position == St.Side.LEFT)
 |  | ||||||
| -            Main.overview._controls._group.insert_child_at_index(this._dashSpacer, this._rtl ? -1 : 0); // insert on first
 |  | ||||||
| -        else if (this._position ==  St.Side.RIGHT)
 |  | ||||||
| -            Main.overview._controls._group.insert_child_at_index(this._dashSpacer, this._rtl ? 0 : -1); // insert on last
 |  | ||||||
| -        else if (this._position == St.Side.TOP)
 |  | ||||||
| -            Main.overview._overview.insert_child_at_index(this._dashSpacer, 0);
 |  | ||||||
| -        else if (this._position == St.Side.BOTTOM)
 |  | ||||||
| -            Main.overview._overview.insert_child_at_index(this._dashSpacer, -1);
 |  | ||||||
| +        if (!Main.overview.isDummy) {
 |  | ||||||
| +            const { _controls, _overview } = Main.overview;
 |  | ||||||
| +            if (this._position == St.Side.LEFT)
 |  | ||||||
| +                _controls._group.insert_child_at_index(this._dashSpacer, this._rtl ? -1 : 0); // insert on first
 |  | ||||||
| +            else if (this._position ==  St.Side.RIGHT)
 |  | ||||||
| +                _controls._group.insert_child_at_index(this._dashSpacer, this._rtl ? 0 : -1); // insert on last
 |  | ||||||
| +            else if (this._position == St.Side.TOP)
 |  | ||||||
| +                _overview.insert_child_at_index(this._dashSpacer, 0);
 |  | ||||||
| +            else if (this._position == St.Side.BOTTOM)
 |  | ||||||
| +                _overview.insert_child_at_index(this._dashSpacer, -1);
 |  | ||||||
| +        }
 |  | ||||||
|   |  | ||||||
|          // Add dash container actor and the container to the Chrome. |  | ||||||
|          this.actor.set_child(this._slider); |  | ||||||
| @@ -412,7 +419,7 @@ var DockedDash = class DashToDock {
 |  | ||||||
|   |  | ||||||
|          // Since Gnome 3.8 dragging an app without having opened the overview before cause the attemp to |  | ||||||
|          //animate a null target since some variables are not initialized when the viewSelector is created |  | ||||||
| -        if (Main.overview.viewSelector._activePage == null)
 |  | ||||||
| +        if (!Main.overview.isDummy && Main.overview.viewSelector._activePage == null)
 |  | ||||||
|              Main.overview.viewSelector._activePage = Main.overview.viewSelector._workspacesPage; |  | ||||||
|   |  | ||||||
|          this._updateVisibilityMode(); |  | ||||||
| @@ -493,7 +500,8 @@ var DockedDash = class DashToDock {
 |  | ||||||
|              this._settings, |  | ||||||
|              'changed::show-show-apps-button', |  | ||||||
|              () => { |  | ||||||
| -                    if (this._settings.get_boolean('show-show-apps-button'))
 |  | ||||||
| +                    if (!Main.overview.isDummy &&
 |  | ||||||
| +                        this._settings.get_boolean('show-show-apps-button'))
 |  | ||||||
|                          this.dash.showShowAppsButton(); |  | ||||||
|                      else |  | ||||||
|                          this.dash.hideShowAppsButton(); |  | ||||||
| @@ -1681,6 +1689,9 @@ var DockManager = class DashToDock_DockManager {
 |  | ||||||
|          // set stored icon size  to the new dash |  | ||||||
|          Main.overview.dashIconSize = this._allDocks[0].dash.iconSize; |  | ||||||
|   |  | ||||||
| +        if (Main.overview.isDummy)
 |  | ||||||
| +            return;
 |  | ||||||
| +
 |  | ||||||
|          // Hide usual Dash |  | ||||||
|          Main.overview._controls.dash.actor.hide(); |  | ||||||
|   |  | ||||||
| @@ -1707,6 +1718,9 @@ var DockManager = class DashToDock_DockManager {
 |  | ||||||
|      } |  | ||||||
|   |  | ||||||
|      _restoreDash() { |  | ||||||
| +        if (Main.overview.isDummy)
 |  | ||||||
| +            return;
 |  | ||||||
| +
 |  | ||||||
|          Main.overview._controls.dash.actor.show(); |  | ||||||
|          Main.overview._controls.dash.actor.set_width(-1); //reset default dash size |  | ||||||
|          // This force the recalculation of the icon size |  | ||||||
| -- 
 |  | ||||||
| 2.25.0 |  | ||||||
| 
 |  | ||||||
| @ -1,33 +0,0 @@ | |||||||
| From 0f13a5e00d9115b4d1518c91aa45c88f3e7e7c27 Mon Sep 17 00:00:00 2001 |  | ||||||
| From: =?UTF-8?q?Florian=20M=C3=BCllner?= <fmuellner@gnome.org> |  | ||||||
| Date: Thu, 2 Nov 2023 20:51:45 +0100 |  | ||||||
| Subject: [PATCH] desktop-icons: Don't try spawn with non-existent workdir |  | ||||||
| 
 |  | ||||||
| g_spawn_async() will fail if the specified workdir doesn't exist. |  | ||||||
| That means that opening a terminal from the context menu will fail |  | ||||||
| when the desktop directory doesn't exist. |  | ||||||
| 
 |  | ||||||
| The extension doesn't really make sense in that case, but when we |  | ||||||
| show an "Open in Terminal" menu item even then, users expect it |  | ||||||
| to work. |  | ||||||
| ---
 |  | ||||||
|  extensions/desktop-icons/desktopIconsUtil.js | 3 +++ |  | ||||||
|  1 file changed, 3 insertions(+) |  | ||||||
| 
 |  | ||||||
| diff --git a/extensions/desktop-icons/desktopIconsUtil.js b/extensions/desktop-icons/desktopIconsUtil.js
 |  | ||||||
| index 0aea6542..c1a0dda3 100644
 |  | ||||||
| --- a/extensions/desktop-icons/desktopIconsUtil.js
 |  | ||||||
| +++ b/extensions/desktop-icons/desktopIconsUtil.js
 |  | ||||||
| @@ -49,6 +49,9 @@ function launchTerminal(workdir) {
 |  | ||||||
|       * https://gitlab.gnome.org/GNOME/gnome-shell/blob/gnome-3-30/js/misc/util.js |  | ||||||
|       */ |  | ||||||
|   |  | ||||||
| +    if (!GLib.file_test(workdir, GLib.FileTest.EXISTS))
 |  | ||||||
| +        workdir = null;
 |  | ||||||
| +
 |  | ||||||
|      var success, pid; |  | ||||||
|      try { |  | ||||||
|          [success, pid] = GLib.spawn_async(workdir, argv, null, |  | ||||||
| -- 
 |  | ||||||
| 2.41.0 |  | ||||||
| 
 |  | ||||||
| @ -1,76 +0,0 @@ | |||||||
| From 93e3e938b322433aff862bbc46f80c60ab7dc2ab Mon Sep 17 00:00:00 2001 |  | ||||||
| From: =?UTF-8?q?Florian=20M=C3=BCllner?= <fmuellner@gnome.org> |  | ||||||
| Date: Tue, 17 Jan 2023 20:31:21 +0100 |  | ||||||
| Subject: [PATCH] desktop-icons: Don't use blocking IO |  | ||||||
| 
 |  | ||||||
| ---
 |  | ||||||
|  extensions/desktop-icons/desktopManager.js | 35 +++++++++++++++++----- |  | ||||||
|  1 file changed, 28 insertions(+), 7 deletions(-) |  | ||||||
| 
 |  | ||||||
| diff --git a/extensions/desktop-icons/desktopManager.js b/extensions/desktop-icons/desktopManager.js
 |  | ||||||
| index 399aee03..2ce6eefb 100644
 |  | ||||||
| --- a/extensions/desktop-icons/desktopManager.js
 |  | ||||||
| +++ b/extensions/desktop-icons/desktopManager.js
 |  | ||||||
| @@ -53,6 +53,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: { |  | ||||||
| @@ -221,9 +236,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); |  | ||||||
|          } |  | ||||||
| @@ -268,14 +281,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); |  | ||||||
| -- 
 |  | ||||||
| 2.38.1 |  | ||||||
| 
 |  | ||||||
| @ -1,179 +0,0 @@ | |||||||
| From b334c8c248f849be996963cdafb1b0b69476bdf1 Mon Sep 17 00:00:00 2001 |  | ||||||
| From: =?UTF-8?q?Jonas=20=C3=85dahl?= <jadahl@redhat.com> |  | ||||||
| Date: Tue, 2 Nov 2021 09:20:11 +0100 |  | ||||||
| Subject: [PATCH] desktop-icons: Fix stuck grab issue with rubber banding |  | ||||||
| 
 |  | ||||||
| The desktop icons extension can get into a state where the desktop no longer |  | ||||||
| takes mouse input. |  | ||||||
| 
 |  | ||||||
| This happens if a user starts a rubber banding operation and then drags |  | ||||||
| the mouse to somewhere on screen that has a pop up menu, and then pops |  | ||||||
| the menu up. |  | ||||||
| 
 |  | ||||||
| This commit addresses the bug by limiting the grab actor to the |  | ||||||
| backgrounds, and by explicitly ending the rubber banding operation |  | ||||||
| when one of the icons own menus is shown. |  | ||||||
| 
 |  | ||||||
| One side effect of limiting the grab actor to the backgrounds, is the |  | ||||||
| rubber banding code never gets to see motion outside of the backgrounds |  | ||||||
| anymore. In order to keep drag operations feeling fluid when the user moves |  | ||||||
| toward the edge of the screen, this commit also overrides the |  | ||||||
| grab helpers captured-event handler so those motion events keep coming. |  | ||||||
| 
 |  | ||||||
| We also start to end the rubber band if for any reason the grab it had |  | ||||||
| was released. |  | ||||||
| ---
 |  | ||||||
|  extensions/desktop-icons/desktopGrid.js    |  1 + |  | ||||||
|  extensions/desktop-icons/desktopManager.js | 75 ++++++++++++++-------- |  | ||||||
|  extensions/desktop-icons/fileItem.js       |  1 + |  | ||||||
|  3 files changed, 49 insertions(+), 28 deletions(-) |  | ||||||
| 
 |  | ||||||
| diff --git a/extensions/desktop-icons/desktopGrid.js b/extensions/desktop-icons/desktopGrid.js
 |  | ||||||
| index 602fa7f..bd27e2a 100644
 |  | ||||||
| --- a/extensions/desktop-icons/desktopGrid.js
 |  | ||||||
| +++ b/extensions/desktop-icons/desktopGrid.js
 |  | ||||||
| @@ -365,6 +365,7 @@ var DesktopGrid = class {
 |  | ||||||
|      } |  | ||||||
|   |  | ||||||
|      _openMenu(x, y) { |  | ||||||
| +        Extension.desktopManager.endRubberBand();
 |  | ||||||
|          Main.layoutManager.setDummyCursorGeometry(x, y, 0, 0); |  | ||||||
|          this.actor._desktopBackgroundMenu.open(BoxPointer.PopupAnimation.NONE); |  | ||||||
|          /* Since the handler is in the press event it needs to ignore the release event |  | ||||||
| diff --git a/extensions/desktop-icons/desktopManager.js b/extensions/desktop-icons/desktopManager.js
 |  | ||||||
| index a70cd98..c37e1e7 100644
 |  | ||||||
| --- a/extensions/desktop-icons/desktopManager.js
 |  | ||||||
| +++ b/extensions/desktop-icons/desktopManager.js
 |  | ||||||
| @@ -79,6 +79,7 @@ var DesktopManager = GObject.registerClass({
 |  | ||||||
|          this._queryFileInfoCancellable = null; |  | ||||||
|          this._unixMode = null; |  | ||||||
|          this._writableByOthers = null; |  | ||||||
| +        this._rubberBandActive = false;
 |  | ||||||
|   |  | ||||||
|          this._monitorsChangedId = Main.layoutManager.connect('monitors-changed', () => this._recreateDesktopIcons()); |  | ||||||
|          this._rubberBand = new St.Widget({ style_class: 'rubber-band' }); |  | ||||||
| @@ -86,6 +87,20 @@ var DesktopManager = GObject.registerClass({
 |  | ||||||
|          Main.layoutManager._backgroundGroup.add_child(this._rubberBand); |  | ||||||
|          this._grabHelper = new GrabHelper.GrabHelper(global.stage); |  | ||||||
|   |  | ||||||
| +        let origCapturedEvent = this._grabHelper.onCapturedEvent;
 |  | ||||||
| +        this._grabHelper.onCapturedEvent = (event) => {
 |  | ||||||
| +            if (event.type() === Clutter.EventType.MOTION) {
 |  | ||||||
| +                /* We handle motion events from a captured event handler so we
 |  | ||||||
| +                 * we can see motion over actors that are on other parts of the
 |  | ||||||
| +                 * stage.
 |  | ||||||
| +                 */
 |  | ||||||
| +                this._handleMotion(event);
 |  | ||||||
| +                return Clutter.EVENT_STOP;
 |  | ||||||
| +            }
 |  | ||||||
| +
 |  | ||||||
| +            return origCapturedEvent.bind(this._grabHelper)(event);
 |  | ||||||
| +        };
 |  | ||||||
| +
 |  | ||||||
|          this._addDesktopIcons(); |  | ||||||
|          this._monitorDesktopFolder(); |  | ||||||
|   |  | ||||||
| @@ -108,30 +123,15 @@ var DesktopManager = GObject.registerClass({
 |  | ||||||
|          this._initRubberBandColor(); |  | ||||||
|          this._updateRubberBand(x, y); |  | ||||||
|          this._rubberBand.show(); |  | ||||||
| -        this._grabHelper.grab({ actor: global.stage });
 |  | ||||||
| +        this._rubberBandActive = true;
 |  | ||||||
| +        this._grabHelper.grab({
 |  | ||||||
| +            actor: Main.layoutManager._backgroundGroup,
 |  | ||||||
| +            onUngrab: () => this.endRubberBand(false),
 |  | ||||||
| +        });
 |  | ||||||
|          Extension.lockActivitiesButton = true; |  | ||||||
|          this._stageReleaseEventId = global.stage.connect('button-release-event', (actor, event) => { |  | ||||||
|              this.endRubberBand(); |  | ||||||
|          }); |  | ||||||
| -        this._rubberBandId = global.stage.connect('motion-event', (actor, event) => {
 |  | ||||||
| -            /* In some cases, when the user starts a rubberband selection and ends it
 |  | ||||||
| -             * (by releasing the left button) over a window instead of doing it over
 |  | ||||||
| -             * the desktop, the stage doesn't receive the "button-release" event.
 |  | ||||||
| -             * This happens currently with, at least, Dash to Dock extension, but
 |  | ||||||
| -             * it probably also happens with other applications or extensions.
 |  | ||||||
| -             * To fix this, we also end the rubberband selection if we detect mouse
 |  | ||||||
| -             * motion in the stage without the left button pressed during a
 |  | ||||||
| -             * rubberband selection.
 |  | ||||||
| -             *  */
 |  | ||||||
| -            let button = event.get_state();
 |  | ||||||
| -            if (!(button & Clutter.ModifierType.BUTTON1_MASK)) {
 |  | ||||||
| -                this.endRubberBand();
 |  | ||||||
| -                return;
 |  | ||||||
| -            }
 |  | ||||||
| -            [x, y] = event.get_coords();
 |  | ||||||
| -            this._updateRubberBand(x, y);
 |  | ||||||
| -            this._updateSelection(x, y);
 |  | ||||||
| -        });
 |  | ||||||
|          this._rubberBandTouchId = global.stage.connect('touch-event', (actor, event) => { |  | ||||||
|              // Let x11 pointer emulation do the job on X11 |  | ||||||
|              if (!Meta.is_wayland_compositor()) |  | ||||||
| @@ -175,14 +175,37 @@ var DesktopManager = GObject.registerClass({
 |  | ||||||
|          } |  | ||||||
|      } |  | ||||||
|   |  | ||||||
| -    endRubberBand() {
 |  | ||||||
| +    _handleMotion(event) {
 |  | ||||||
| +        /* In some cases, when the user starts a rubberband selection and ends it
 |  | ||||||
| +         * (by releasing the left button) over a window instead of doing it over
 |  | ||||||
| +         * the desktop, the stage doesn't receive the "button-release" event.
 |  | ||||||
| +         * This happens currently with, at least, Dash to Dock extension, but
 |  | ||||||
| +         * it probably also happens with other applications or extensions.
 |  | ||||||
| +         * To fix this, we also end the rubberband selection if we detect mouse
 |  | ||||||
| +         * motion in the stage without the left button pressed during a
 |  | ||||||
| +         * rubberband selection.
 |  | ||||||
| +         *  */
 |  | ||||||
| +        let button = event.get_state();
 |  | ||||||
| +        if (!(button & Clutter.ModifierType.BUTTON1_MASK)) {
 |  | ||||||
| +            this.endRubberBand();
 |  | ||||||
| +            return;
 |  | ||||||
| +        }
 |  | ||||||
| +        let [x, y] = event.get_coords();
 |  | ||||||
| +        this._updateRubberBand(x, y);
 |  | ||||||
| +        this._updateSelection(x, y);
 |  | ||||||
| +    }
 |  | ||||||
| +
 |  | ||||||
| +    endRubberBand(ungrab=true) {
 |  | ||||||
| +        if (!this._rubberBandActive)
 |  | ||||||
| +             return;
 |  | ||||||
| +
 |  | ||||||
| +        this._rubberBandActive = false;
 |  | ||||||
|          this._rubberBand.hide(); |  | ||||||
|          Extension.lockActivitiesButton = false; |  | ||||||
| -        this._grabHelper.ungrab();
 |  | ||||||
| -        global.stage.disconnect(this._rubberBandId);
 |  | ||||||
| +        if (ungrab)
 |  | ||||||
| +            this._grabHelper.ungrab();
 |  | ||||||
|          global.stage.disconnect(this._rubberBandTouchId); |  | ||||||
|          global.stage.disconnect(this._stageReleaseEventId); |  | ||||||
| -        this._rubberBandId = 0;
 |  | ||||||
|          this._rubberBandTouchId = 0; |  | ||||||
|          this._stageReleaseEventId = 0; |  | ||||||
|   |  | ||||||
| @@ -760,10 +783,6 @@ var DesktopManager = GObject.registerClass({
 |  | ||||||
|              global.stage.disconnect(this._stageReleaseEventId); |  | ||||||
|          this._stageReleaseEventId = 0; |  | ||||||
|   |  | ||||||
| -        if (this._rubberBandId)
 |  | ||||||
| -            global.stage.disconnect(this._rubberBandId);
 |  | ||||||
| -        this._rubberBandId = 0;
 |  | ||||||
| -
 |  | ||||||
|          if (this._rubberBandTouchId) |  | ||||||
|              global.stage.disconnect(this._rubberBandTouchId); |  | ||||||
|          this._rubberBandTouchId = 0; |  | ||||||
| diff --git a/extensions/desktop-icons/fileItem.js b/extensions/desktop-icons/fileItem.js
 |  | ||||||
| index 1cb47e8..90f326d 100644
 |  | ||||||
| --- a/extensions/desktop-icons/fileItem.js
 |  | ||||||
| +++ b/extensions/desktop-icons/fileItem.js
 |  | ||||||
| @@ -676,6 +676,7 @@ var FileItem = class {
 |  | ||||||
|      } |  | ||||||
|   |  | ||||||
|      _onPressButton(actor, event) { |  | ||||||
| +        Extension.desktopManager.endRubberBand();
 |  | ||||||
|          this._updateClickState(event); |  | ||||||
|          let button = this._eventButton(event); |  | ||||||
|          if (button == 3) { |  | ||||||
| -- 
 |  | ||||||
| 2.31.1 |  | ||||||
| 
 |  | ||||||
| @ -1,31 +0,0 @@ | |||||||
| From 0a7248c75c084a83852aa3d0e7a36ccebd365b81 Mon Sep 17 00:00:00 2001 |  | ||||||
| From: =?UTF-8?q?Florian=20M=C3=BCllner?= <fmuellner@gnome.org> |  | ||||||
| Date: Wed, 27 Jan 2021 11:51:28 +0100 |  | ||||||
| Subject: [PATCH] desktop-icons: Update Japanese translation |  | ||||||
| 
 |  | ||||||
| ---
 |  | ||||||
|  po/ja.po | 7 +------ |  | ||||||
|  1 file changed, 1 insertion(+), 6 deletions(-) |  | ||||||
| 
 |  | ||||||
| diff --git a/po/ja.po b/po/ja.po
 |  | ||||||
| index df5646d..4d780b9 100644
 |  | ||||||
| --- a/po/ja.po
 |  | ||||||
| +++ b/po/ja.po
 |  | ||||||
| @@ -293,13 +293,8 @@ msgid "Workspace %d"
 |  | ||||||
|  msgstr "ワークスペース %d" |  | ||||||
|   |  | ||||||
|  #: desktopGrid.js:334 |  | ||||||
| -#, fuzzy
 |  | ||||||
|  msgid "Display Settings" |  | ||||||
| -msgstr ""
 |  | ||||||
| -"#-#-#-#-#  ja.po (gnome-shell-extensions master)  #-#-#-#-#\n"
 |  | ||||||
| -"ディスプレイ設定\n"
 |  | ||||||
| -"#-#-#-#-#  ja.po (desktop-icons master)  #-#-#-#-#\n"
 |  | ||||||
| -"ディスプレイの設定"
 |  | ||||||
| +msgstr "ディスプレイ設定"
 |  | ||||||
|   |  | ||||||
|  #: schemas/org.gnome.shell.extensions.desktop-icons.gschema.xml:11 |  | ||||||
|  #, fuzzy |  | ||||||
| -- 
 |  | ||||||
| 2.29.2 |  | ||||||
| 
 |  | ||||||
| @ -1,28 +0,0 @@ | |||||||
| From 81b5163b43b7d45ca8bc9205476ef67789e283a4 Mon Sep 17 00:00:00 2001 |  | ||||||
| From: =?UTF-8?q?Florian=20M=C3=BCllner?= <fmuellner@gnome.org> |  | ||||||
| Date: Mon, 29 Nov 2021 16:48:53 +0100 |  | ||||||
| Subject: [PATCH] desktop-icons: Use a single unique name to access nautilus |  | ||||||
| 
 |  | ||||||
| ... otherwise dbus-daemon will assume that activating one of the |  | ||||||
| services failed, because its executable exits early (after activating |  | ||||||
| the primary instance). |  | ||||||
| ---
 |  | ||||||
|  extensions/desktop-icons/dbusUtils.js | 2 +- |  | ||||||
|  1 file changed, 1 insertion(+), 1 deletion(-) |  | ||||||
| 
 |  | ||||||
| diff --git a/extensions/desktop-icons/dbusUtils.js b/extensions/desktop-icons/dbusUtils.js
 |  | ||||||
| index 19fe9878..b44ffa59 100644
 |  | ||||||
| --- a/extensions/desktop-icons/dbusUtils.js
 |  | ||||||
| +++ b/extensions/desktop-icons/dbusUtils.js
 |  | ||||||
| @@ -64,7 +64,7 @@ function init() {
 |  | ||||||
|   |  | ||||||
|      FreeDesktopFileManagerProxy = new FreeDesktopFileManagerProxyInterface( |  | ||||||
|          Gio.DBus.session, |  | ||||||
| -        'org.freedesktop.FileManager1',
 |  | ||||||
| +        'org.gnome.Nautilus',
 |  | ||||||
|          '/org/freedesktop/FileManager1', |  | ||||||
|          (proxy, error) => { |  | ||||||
|              if (error) { |  | ||||||
| -- 
 |  | ||||||
| 2.33.1 |  | ||||||
| 
 |  | ||||||
| @ -1,83 +0,0 @@ | |||||||
| From f78b19068654412ca9e73a229e1537d080759c47 Mon Sep 17 00:00:00 2001 |  | ||||||
| From: Carlos Garnacho <carlosg@gnome.org> |  | ||||||
| Date: Wed, 27 Jan 2021 16:55:10 +0100 |  | ||||||
| Subject: [PATCH] fileItem: Ignore double click distance clicking on items |  | ||||||
| 
 |  | ||||||
| Imitate the behavior of Nautilus canvas WRT double clicks being |  | ||||||
| handled on all of the icon(s) without accounting for the double |  | ||||||
| click distance. As the extension does already lean on Nautilus |  | ||||||
| look & feel, it seems to make sense doing this. |  | ||||||
| 
 |  | ||||||
| This is not as crucial for mice as it is for touchscreens, where |  | ||||||
| the default 5px limit may be a bit on the short side depending |  | ||||||
| on device sensitivity. |  | ||||||
| ---
 |  | ||||||
|  extensions/desktop-icons/fileItem.js | 26 +++++++++++++++++++++++--- |  | ||||||
|  1 file changed, 23 insertions(+), 3 deletions(-) |  | ||||||
| 
 |  | ||||||
| diff --git a/extensions/desktop-icons/fileItem.js b/extensions/desktop-icons/fileItem.js
 |  | ||||||
| index d6d43c9..5d3195f 100644
 |  | ||||||
| --- a/extensions/desktop-icons/fileItem.js
 |  | ||||||
| +++ b/extensions/desktop-icons/fileItem.js
 |  | ||||||
| @@ -65,6 +65,9 @@ var FileItem = class {
 |  | ||||||
|          this._setMetadataCancellable = null; |  | ||||||
|          this._queryFileInfoCancellable = null; |  | ||||||
|          this._isSpecial = this._fileExtra != Prefs.FileType.NONE; |  | ||||||
| +        this._lastClickTime = 0;
 |  | ||||||
| +        this._lastClickButton = 0;
 |  | ||||||
| +        this._clickCount = 0;
 |  | ||||||
|   |  | ||||||
|          this._file = file; |  | ||||||
|   |  | ||||||
| @@ -642,7 +645,24 @@ var FileItem = class {
 |  | ||||||
|          DesktopIconsUtil.launchTerminal(this.file.get_path()); |  | ||||||
|      } |  | ||||||
|   |  | ||||||
| +    _updateClickState(event) {
 |  | ||||||
| +        let settings = Clutter.Settings.get_default();
 |  | ||||||
| +        if ((event.get_button() == this._lastClickButton) &&
 |  | ||||||
| +            ((event.get_time() - this._lastClickTime) < settings.double_click_time))
 |  | ||||||
| +            this._clickCount++;
 |  | ||||||
| +        else
 |  | ||||||
| +            this._clickCount = 1;
 |  | ||||||
| +
 |  | ||||||
| +        this._lastClickTime = event.get_time();
 |  | ||||||
| +        this._lastClickButton = event.get_button();
 |  | ||||||
| +    }
 |  | ||||||
| +
 |  | ||||||
| +    _getClickCount() {
 |  | ||||||
| +        return this._clickCount;
 |  | ||||||
| +    }
 |  | ||||||
| +
 |  | ||||||
|      _onPressButton(actor, event) { |  | ||||||
| +        this._updateClickState(event);
 |  | ||||||
|          let button = event.get_button(); |  | ||||||
|          if (button == 3) { |  | ||||||
|              if (!this.isSelected) |  | ||||||
| @@ -661,7 +681,7 @@ var FileItem = class {
 |  | ||||||
|                  this._actionTrash.setSensitive(!specialFilesSelected); |  | ||||||
|              return Clutter.EVENT_STOP; |  | ||||||
|          } else if (button == 1) { |  | ||||||
| -            if (event.get_click_count() == 1) {
 |  | ||||||
| +            if (this._getClickCount() == 1) {
 |  | ||||||
|                  let [x, y] = event.get_coords(); |  | ||||||
|                  this._primaryButtonPressed = true; |  | ||||||
|                  this._buttonPressInitialX = x; |  | ||||||
| @@ -710,12 +730,12 @@ var FileItem = class {
 |  | ||||||
|                  this._primaryButtonPressed = false; |  | ||||||
|                  let shiftPressed = !!(event.get_state() & Clutter.ModifierType.SHIFT_MASK); |  | ||||||
|                  let controlPressed = !!(event.get_state() & Clutter.ModifierType.CONTROL_MASK); |  | ||||||
| -                if ((event.get_click_count() == 1) && Prefs.CLICK_POLICY_SINGLE && !shiftPressed && !controlPressed)
 |  | ||||||
| +                if ((this._getClickCount() == 1) && Prefs.CLICK_POLICY_SINGLE && !shiftPressed && !controlPressed)
 |  | ||||||
|                      this.doOpen(); |  | ||||||
|                  this.emit('selected', shiftPressed || controlPressed, false, true); |  | ||||||
|                  return Clutter.EVENT_STOP; |  | ||||||
|              } |  | ||||||
| -            if ((event.get_click_count() == 2) && (!Prefs.CLICK_POLICY_SINGLE))
 |  | ||||||
| +            if ((this._getClickCount() == 2) && (!Prefs.CLICK_POLICY_SINGLE))
 |  | ||||||
|                  this.doOpen(); |  | ||||||
|          } |  | ||||||
|          return Clutter.EVENT_PROPAGATE; |  | ||||||
| -- 
 |  | ||||||
| 2.29.2 |  | ||||||
| 
 |  | ||||||
| @ -1,31 +0,0 @@ | |||||||
| From 506c6d69eaa5e056d9580a28e9c200586b0e1fb0 Mon Sep 17 00:00:00 2001 |  | ||||||
| From: =?UTF-8?q?Florian=20M=C3=BCllner?= <fmuellner@gnome.org> |  | ||||||
| Date: Fri, 2 Dec 2022 15:20:40 +0100 |  | ||||||
| Subject: [PATCH] fileItem: Just destroy menus |  | ||||||
| 
 |  | ||||||
| The menu manager is smart enough to remove the menu automatically, |  | ||||||
| and the actor will be destroyed alongside the menu. Not doing those |  | ||||||
| actions explicitly allows the automatic handling to proceed without |  | ||||||
| confusing the grab state. |  | ||||||
| ---
 |  | ||||||
|  extensions/desktop-icons/fileItem.js | 4 ---- |  | ||||||
|  1 file changed, 4 deletions(-) |  | ||||||
| 
 |  | ||||||
| diff --git a/extensions/desktop-icons/fileItem.js b/extensions/desktop-icons/fileItem.js
 |  | ||||||
| index 44a93352..f2f03440 100644
 |  | ||||||
| --- a/extensions/desktop-icons/fileItem.js
 |  | ||||||
| +++ b/extensions/desktop-icons/fileItem.js
 |  | ||||||
| @@ -575,10 +575,6 @@ var FileItem = class {
 |  | ||||||
|   |  | ||||||
|      _removeMenu() { |  | ||||||
|          if (this._menu != null) { |  | ||||||
| -            if (this._menuManager != null)
 |  | ||||||
| -                this._menuManager.removeMenu(this._menu);
 |  | ||||||
| -
 |  | ||||||
| -            Main.layoutManager.uiGroup.remove_child(this._menu.actor);
 |  | ||||||
|              this._menu.destroy(); |  | ||||||
|              this._menu = null; |  | ||||||
|          } |  | ||||||
| -- 
 |  | ||||||
| 2.38.1 |  | ||||||
| 
 |  | ||||||
| @ -1,147 +0,0 @@ | |||||||
| From be4ab59a3f2bb9829dde390db3dd8868a08840eb Mon Sep 17 00:00:00 2001 |  | ||||||
| From: =?UTF-8?q?Florian=20M=C3=BCllner?= <fmuellner@gnome.org> |  | ||||||
| Date: Fri, 2 Dec 2022 19:28:54 +0100 |  | ||||||
| Subject: [PATCH] fileItem: Support .desktop files of type Link |  | ||||||
| 
 |  | ||||||
| Gio only has direct support for .desktop files of type Application. |  | ||||||
| 
 |  | ||||||
| However in the context of desktop icons (and file managers), shortcuts |  | ||||||
| of URLs are useful as well, so add explicit support for .desktop files |  | ||||||
| of type Link. |  | ||||||
| ---
 |  | ||||||
|  extensions/desktop-icons/fileItem.js | 71 +++++++++++++++++++++++----- |  | ||||||
|  1 file changed, 60 insertions(+), 11 deletions(-) |  | ||||||
| 
 |  | ||||||
| diff --git a/extensions/desktop-icons/fileItem.js b/extensions/desktop-icons/fileItem.js
 |  | ||||||
| index f2f03440..1c9a1e55 100644
 |  | ||||||
| --- a/extensions/desktop-icons/fileItem.js
 |  | ||||||
| +++ b/extensions/desktop-icons/fileItem.js
 |  | ||||||
| @@ -239,12 +239,32 @@ var FileItem = class {
 |  | ||||||
|              log(`desktop-icons: File ${this._displayName} is writable by others - will not allow launching`); |  | ||||||
|   |  | ||||||
|          if (this._isDesktopFile) { |  | ||||||
| -            this._desktopFile = Gio.DesktopAppInfo.new_from_filename(this._file.get_path());
 |  | ||||||
| -            if (!this._desktopFile) {
 |  | ||||||
| -                log(`Couldn’t parse ${this._displayName} as a desktop file, will treat it as a regular file.`);
 |  | ||||||
| +            try {
 |  | ||||||
| +                const keyFile = new GLib.KeyFile();
 |  | ||||||
| +                keyFile.load_from_file(this._file.get_path(), GLib.KeyFileFlags.NONE);
 |  | ||||||
| +
 |  | ||||||
| +                const type = keyFile.get_string(
 |  | ||||||
| +                    GLib.KEY_FILE_DESKTOP_GROUP, GLib.KEY_FILE_DESKTOP_KEY_TYPE);
 |  | ||||||
| +                switch (type) {
 |  | ||||||
| +                case GLib.KEY_FILE_DESKTOP_TYPE_APPLICATION:
 |  | ||||||
| +                    this._desktopFile = Gio.DesktopAppInfo.new_from_keyfile(keyFile);
 |  | ||||||
| +                    if (!this._desktopFile) {
 |  | ||||||
| +                        log(`Couldn’t parse ${this._displayName} as a desktop file, will treat it as a regular file.`);
 |  | ||||||
| +                        this._isValidDesktopFile = false;
 |  | ||||||
| +                    } else {
 |  | ||||||
| +                        this._isValidDesktopFile = true;
 |  | ||||||
| +                    }
 |  | ||||||
| +                    break;
 |  | ||||||
| +                case GLib.KEY_FILE_DESKTOP_TYPE_LINK:
 |  | ||||||
| +                    const url = keyFile.get_string(
 |  | ||||||
| +                        GLib.KEY_FILE_DESKTOP_GROUP, GLib.KEY_FILE_DESKTOP_KEY_URL);
 |  | ||||||
| +                    if (url)
 |  | ||||||
| +                        this._linkFile = keyFile;
 |  | ||||||
| +                default: // fall-through
 |  | ||||||
| +                    this._isValidDesktopFile = false;
 |  | ||||||
| +                }
 |  | ||||||
| +            } catch (e) {
 |  | ||||||
|                  this._isValidDesktopFile = false; |  | ||||||
| -            } else {
 |  | ||||||
| -                this._isValidDesktopFile = true;
 |  | ||||||
|              } |  | ||||||
|          } else { |  | ||||||
|              this._isValidDesktopFile = false; |  | ||||||
| @@ -356,8 +376,17 @@ var FileItem = class {
 |  | ||||||
|          if (this._isBrokenSymlink) { |  | ||||||
|              this._icon.child = this._createEmblemedStIcon(null, 'text-x-generic'); |  | ||||||
|          } else { |  | ||||||
| -            if (this.trustedDesktopFile && this._desktopFile.has_key('Icon'))
 |  | ||||||
| -                this._icon.child = this._createEmblemedStIcon(null, this._desktopFile.get_string('Icon'));
 |  | ||||||
| +            let iconName = null;
 |  | ||||||
| +
 |  | ||||||
| +            try {
 |  | ||||||
| +                if (this.trustedDesktopFile)
 |  | ||||||
| +                    iconName = this._desktopFile.get_string('Icon');
 |  | ||||||
| +                else if (this._linkFile)
 |  | ||||||
| +                    iconName = this._linkFile.get_string(GLib.KEY_FILE_DESKTOP_GROUP, GLib.KEY_FILE_DESKTOP_KEY_ICON);
 |  | ||||||
| +            } catch (e) {}
 |  | ||||||
| +
 |  | ||||||
| +            if (iconName)
 |  | ||||||
| +                this._icon.child = this._createEmblemedStIcon(null, iconName);
 |  | ||||||
|              else |  | ||||||
|                  this._icon.child = this._createEmblemedStIcon(this._fileInfo.get_icon(), null); |  | ||||||
|          } |  | ||||||
| @@ -411,7 +440,7 @@ var FileItem = class {
 |  | ||||||
|                  itemIcon.add_emblem(Gio.Emblem.new(Gio.ThemedIcon.new('emblem-unreadable'))); |  | ||||||
|              else |  | ||||||
|                  itemIcon.add_emblem(Gio.Emblem.new(Gio.ThemedIcon.new('emblem-symbolic-link'))); |  | ||||||
| -        } else if (this.trustedDesktopFile) {
 |  | ||||||
| +        } else if (this.trustedDesktopFile || this._linkFile) {
 |  | ||||||
|              itemIcon.add_emblem(Gio.Emblem.new(Gio.ThemedIcon.new('emblem-symbolic-link'))); |  | ||||||
|          } |  | ||||||
|   |  | ||||||
| @@ -440,6 +469,12 @@ var FileItem = class {
 |  | ||||||
|              return; |  | ||||||
|          } |  | ||||||
|   |  | ||||||
| +        if (this._linkFile) {
 |  | ||||||
| +            this._openUri(this._linkFile.get_string(
 |  | ||||||
| +                GLib.KEY_FILE_DESKTOP_GROUP, GLib.KEY_FILE_DESKTOP_KEY_URL));
 |  | ||||||
| +            return;
 |  | ||||||
| +        }
 |  | ||||||
| +
 |  | ||||||
|          if (this._attributeCanExecute && |  | ||||||
|             !this._isDirectory && |  | ||||||
|             !this._isValidDesktopFile && |  | ||||||
| @@ -449,13 +484,17 @@ var FileItem = class {
 |  | ||||||
|                  return; |  | ||||||
|          } |  | ||||||
|   |  | ||||||
| -        Gio.AppInfo.launch_default_for_uri_async(this.file.get_uri(),
 |  | ||||||
| +        this._openUri(this.file.get_uri());
 |  | ||||||
| +    }
 |  | ||||||
| +
 |  | ||||||
| +    _openUri(uri) {
 |  | ||||||
| +        Gio.AppInfo.launch_default_for_uri_async(uri,
 |  | ||||||
|              null, null, |  | ||||||
|              (source, result) => { |  | ||||||
|                  try { |  | ||||||
|                      Gio.AppInfo.launch_default_for_uri_finish(result); |  | ||||||
|                  } catch (e) { |  | ||||||
| -                    log('Error opening file ' + this.file.get_uri() + ': ' + e.message);
 |  | ||||||
| +                    log('Error opening file ' + uri + ': ' + e.message);
 |  | ||||||
|                  } |  | ||||||
|              } |  | ||||||
|          ); |  | ||||||
| @@ -555,7 +594,9 @@ var FileItem = class {
 |  | ||||||
|      } |  | ||||||
|   |  | ||||||
|      canRename() { |  | ||||||
| -        return !this.trustedDesktopFile && this._fileExtra == Prefs.FileType.NONE;
 |  | ||||||
| +        return !this.trustedDesktopFile &&
 |  | ||||||
| +               !this._linkFile &&
 |  | ||||||
| +               this._fileExtra == Prefs.FileType.NONE;
 |  | ||||||
|      } |  | ||||||
|   |  | ||||||
|      _doOpenWith() { |  | ||||||
| @@ -819,6 +860,14 @@ var FileItem = class {
 |  | ||||||
|          if (this.trustedDesktopFile) |  | ||||||
|              return this._desktopFile.get_name(); |  | ||||||
|   |  | ||||||
| +        if (this._linkFile) {
 |  | ||||||
| +            try {
 |  | ||||||
| +                const name = this._linkFile.get_string(
 |  | ||||||
| +                    GLib.KEY_FILE_DESKTOP_GROUP, GLib.KEY_FILE_DESKTOP_KEY_NAME);
 |  | ||||||
| +                return name;
 |  | ||||||
| +            } catch (e) {}
 |  | ||||||
| +        }
 |  | ||||||
| +
 |  | ||||||
|          return this._displayName || null; |  | ||||||
|      } |  | ||||||
|   |  | ||||||
| -- 
 |  | ||||||
| 2.38.1 |  | ||||||
| 
 |  | ||||||
| @ -1,45 +0,0 @@ | |||||||
| From ee89a91a9ac235b69ff3c47af14d702c0309e892 Mon Sep 17 00:00:00 2001 |  | ||||||
| From: Sergio Costas <raster@rastersoft.com> |  | ||||||
| Date: Thu, 25 Jul 2019 00:12:09 +0200 |  | ||||||
| Subject: [PATCH] general: launch only executable files |  | ||||||
| 
 |  | ||||||
| Until now, if a file has the "execute" flag, clicking on it will try |  | ||||||
| to execute it, no matter if it is really an executable. This means |  | ||||||
| that a non-executable file (like a JPEG picture, or a text file) |  | ||||||
| won't be opened with its desired application if it has set the |  | ||||||
| executable flag. |  | ||||||
| 
 |  | ||||||
| This patch fixes this, by ensuring that the only files that can be |  | ||||||
| executed when the "execute" flag is set, are the ones that makes |  | ||||||
| sense to execute. |  | ||||||
| 
 |  | ||||||
| Fixes https://gitlab.gnome.org/World/ShellExtensions/desktop-icons/issues/144 |  | ||||||
| ---
 |  | ||||||
|  extensions/desktop-icons/fileItem.js | 11 +++++++---- |  | ||||||
|  1 file changed, 7 insertions(+), 4 deletions(-) |  | ||||||
| 
 |  | ||||||
| diff --git a/extensions/desktop-icons/fileItem.js b/extensions/desktop-icons/fileItem.js
 |  | ||||||
| index d6d43c9f..44a93352 100644
 |  | ||||||
| --- a/extensions/desktop-icons/fileItem.js
 |  | ||||||
| +++ b/extensions/desktop-icons/fileItem.js
 |  | ||||||
| @@ -440,10 +440,13 @@ var FileItem = class {
 |  | ||||||
|              return; |  | ||||||
|          } |  | ||||||
|   |  | ||||||
| -        if (this._attributeCanExecute && !this._isDirectory && !this._isValidDesktopFile) {
 |  | ||||||
| -            if (this._execLine)
 |  | ||||||
| -                Util.spawnCommandLine(this._execLine);
 |  | ||||||
| -            return;
 |  | ||||||
| +        if (this._attributeCanExecute &&
 |  | ||||||
| +           !this._isDirectory &&
 |  | ||||||
| +           !this._isValidDesktopFile &&
 |  | ||||||
| +            Gio.content_type_can_be_executable(this._attributeContentType)) {
 |  | ||||||
| +                if (this._execLine)
 |  | ||||||
| +                    Util.spawnCommandLine(this._execLine);
 |  | ||||||
| +                return;
 |  | ||||||
|          } |  | ||||||
|   |  | ||||||
|          Gio.AppInfo.launch_default_for_uri_async(this.file.get_uri(), |  | ||||||
| -- 
 |  | ||||||
| 2.31.1 |  | ||||||
| 
 |  | ||||||
| @ -1,51 +0,0 @@ | |||||||
| From ce75829479b1e7bf99e74bf835174e91c8da2276 Mon Sep 17 00:00:00 2001 |  | ||||||
| From: =?UTF-8?q?Florian=20M=C3=BCllner?= <fmuellner@gnome.org> |  | ||||||
| Date: Fri, 9 Dec 2022 15:31:08 +0100 |  | ||||||
| Subject: [PATCH] gesture-inhibitor: Allow inhibiting workspace switch gesture |  | ||||||
| 
 |  | ||||||
| ---
 |  | ||||||
|  extensions/gesture-inhibitor/extension.js                    | 5 ++++- |  | ||||||
|  .../org.gnome.shell.extensions.gesture-inhibitor.gschema.xml | 4 ++++ |  | ||||||
|  2 files changed, 8 insertions(+), 1 deletion(-) |  | ||||||
| 
 |  | ||||||
| diff --git a/extensions/gesture-inhibitor/extension.js b/extensions/gesture-inhibitor/extension.js
 |  | ||||||
| index e74ede2f..bf02d075 100644
 |  | ||||||
| --- a/extensions/gesture-inhibitor/extension.js
 |  | ||||||
| +++ b/extensions/gesture-inhibitor/extension.js
 |  | ||||||
| @@ -37,6 +37,8 @@ class Extension {
 |  | ||||||
|  		this._showOverview = a; |  | ||||||
|  	    else if (a instanceof WindowManager.AppSwitchAction) |  | ||||||
|  		this._appSwitch = a; |  | ||||||
| +	    else if (a instanceof WindowManager.WorkspaceSwitchAction)
 |  | ||||||
| +		this._workspaceSwitch = a;
 |  | ||||||
|  	    else if (a instanceof EdgeDragAction.EdgeDragAction && |  | ||||||
|  		     a._side == St.Side.BOTTOM) |  | ||||||
|  		this._showOsk = a; |  | ||||||
| @@ -52,7 +54,8 @@ class Extension {
 |  | ||||||
|  	    { setting: 'app-switch', action: this._appSwitch }, |  | ||||||
|  	    { setting: 'show-osk', action: this._showOsk }, |  | ||||||
|  	    { setting: 'unfullscreen', action: this._unfullscreen }, |  | ||||||
| -	    { setting: 'show-app-grid', action: this._showAppGrid }
 |  | ||||||
| +	    { setting: 'show-app-grid', action: this._showAppGrid },
 |  | ||||||
| +	    { setting: 'workspace-switch', action: this._workspaceSwitch },
 |  | ||||||
|  	]; |  | ||||||
|      } |  | ||||||
|   |  | ||||||
| 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 1d67dcc0..a5e97a3d 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
 |  | ||||||
| @@ -16,6 +16,10 @@
 |  | ||||||
|        <default>true</default> |  | ||||||
|        <summary>Application switch gesture</summary> |  | ||||||
|      </key> |  | ||||||
| +    <key name="workspace-switch" type="b">
 |  | ||||||
| +      <default>true</default>
 |  | ||||||
| +      <summary>Workspace switch gesture</summary>
 |  | ||||||
| +    </key>
 |  | ||||||
|      <key name="unfullscreen" type="b"> |  | ||||||
|        <default>true</default> |  | ||||||
|        <summary>Unfullscreen gesture</summary> |  | ||||||
| -- 
 |  | ||||||
| 2.38.1 |  | ||||||
| 
 |  | ||||||
| @ -1,41 +0,0 @@ | |||||||
| From dfdd10b46d670674d5e0e38f7adcd007f5884822 Mon Sep 17 00:00:00 2001 |  | ||||||
| From: rpm-build <rpm-build> |  | ||||||
| Date: Wed, 29 Sep 2021 14:33:25 +0200 |  | ||||||
| Subject: [PATCH] gesture-inhibitor: Put a foot down with self-enabling |  | ||||||
|  gestures |  | ||||||
| 
 |  | ||||||
| If a gesture (unfullscreen, I'm looking at you) controls its 'enabled' |  | ||||||
| property, it will bypass the will of this extension. Make it sure that |  | ||||||
| gestures are forced-off if the extension says so. |  | ||||||
| ---
 |  | ||||||
|  extensions/gesture-inhibitor/extension.js | 8 +++++++- |  | ||||||
|  1 file changed, 7 insertions(+), 1 deletion(-) |  | ||||||
| 
 |  | ||||||
| diff --git a/extensions/gesture-inhibitor/extension.js b/extensions/gesture-inhibitor/extension.js
 |  | ||||||
| index e74ede2..66c706e 100644
 |  | ||||||
| --- a/extensions/gesture-inhibitor/extension.js
 |  | ||||||
| +++ b/extensions/gesture-inhibitor/extension.js
 |  | ||||||
| @@ -59,13 +59,19 @@ class Extension {
 |  | ||||||
|      enable() { |  | ||||||
|  	this._map.forEach(m => { |  | ||||||
|  	    this._settings.bind(m.setting, m.action, 'enabled', |  | ||||||
| -				Gio.SettingsBindFlags.DEFAULT);
 |  | ||||||
| +				Gio.SettingsBindFlags.GET);
 |  | ||||||
| +            m.handler = m.action.connect('notify::enabled', () => {
 |  | ||||||
| +                if (m.action.enabled && !this._settings.get_boolean(m.setting))
 |  | ||||||
| +                    m.action.enabled = this._settings.get_boolean(m.setting);
 |  | ||||||
| +            });
 |  | ||||||
|  	}); |  | ||||||
|      } |  | ||||||
|   |  | ||||||
|      disable() { |  | ||||||
|  	this._map.forEach(m => { |  | ||||||
|  	    m.action.enabled = true; |  | ||||||
| +            if (m.handler > 0)
 |  | ||||||
| +                m.action.disconnect(m.handler);
 |  | ||||||
|  	}); |  | ||||||
|      } |  | ||||||
|  } |  | ||||||
| -- 
 |  | ||||||
| 2.31.1 |  | ||||||
| 
 |  | ||||||
| @ -1,831 +0,0 @@ | |||||||
| From 8da1760af68496c6073be4d6b3c8266b64347925 Mon Sep 17 00:00:00 2001 |  | ||||||
| From: Ray Strode <rstrode@redhat.com> |  | ||||||
| Date: Tue, 24 Aug 2021 15:03:57 -0400 |  | ||||||
| Subject: [PATCH] heads-up-display: Add extension for showing persistent heads |  | ||||||
|  up display message |  | ||||||
| 
 |  | ||||||
| ---
 |  | ||||||
|  extensions/heads-up-display/extension.js      | 320 ++++++++++++++++++ |  | ||||||
|  extensions/heads-up-display/headsUpMessage.js | 150 ++++++++ |  | ||||||
|  extensions/heads-up-display/meson.build       |   8 + |  | ||||||
|  extensions/heads-up-display/metadata.json.in  |  11 + |  | ||||||
|  ...ll.extensions.heads-up-display.gschema.xml |  54 +++ |  | ||||||
|  extensions/heads-up-display/prefs.js          | 175 ++++++++++ |  | ||||||
|  extensions/heads-up-display/stylesheet.css    |  32 ++ |  | ||||||
|  meson.build                                   |   1 + |  | ||||||
|  8 files changed, 751 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..e4ef9e85
 |  | ||||||
| --- /dev/null
 |  | ||||||
| +++ b/extensions/heads-up-display/extension.js
 |  | ||||||
| @@ -0,0 +1,320 @@
 |  | ||||||
| +/* exported init enable disable */
 |  | ||||||
| +
 |  | ||||||
| +
 |  | ||||||
| +const Signals = imports.signals;
 |  | ||||||
| +
 |  | ||||||
| +const {
 |  | ||||||
| +    Atk, Clutter, Gio, GLib, GObject, Gtk, Meta, Shell, St,
 |  | ||||||
| +} = imports.gi;
 |  | ||||||
| +
 |  | ||||||
| +const ExtensionUtils = imports.misc.extensionUtils;
 |  | ||||||
| +const Me = ExtensionUtils.getCurrentExtension();
 |  | ||||||
| +
 |  | ||||||
| +const Main = imports.ui.main;
 |  | ||||||
| +const HeadsUpMessage = Me.imports.headsUpMessage;
 |  | ||||||
| +
 |  | ||||||
| +const Gettext = imports.gettext.domain('gnome-shell-extensions');
 |  | ||||||
| +const _ = Gettext.gettext;
 |  | ||||||
| +
 |  | ||||||
| +class Extension {
 |  | ||||||
| +    constructor() {
 |  | ||||||
| +        ExtensionUtils.initTranslations();
 |  | ||||||
| +    }
 |  | ||||||
| +
 |  | ||||||
| +    enable() {
 |  | ||||||
| +        this._settings = ExtensionUtils.getSettings('org.gnome.shell.extensions.heads-up-display');
 |  | ||||||
| +        this._idleTimeoutChangedId = this._settings.connect('changed::idle-timeout', this._onIdleTimeoutChanged.bind(this));
 |  | ||||||
| +        this._settingsChangedId = this._settings.connect('changed', this._updateMessage.bind(this));
 |  | ||||||
| +
 |  | ||||||
| +        this._idleMonitor = Meta.IdleMonitor.get_core();
 |  | ||||||
| +        this._messageInhibitedUntilIdle = false;
 |  | ||||||
| +        this._oldMapWindow = Main.wm._mapWindow;
 |  | ||||||
| +        Main.wm._mapWindow = this._mapWindow;
 |  | ||||||
| +        this._windowManagerMapId = global.window_manager.connect('map', this._onWindowMap.bind(this));
 |  | ||||||
| +
 |  | ||||||
| +        if (Main.layoutManager._startingUp)
 |  | ||||||
| +            this._startupCompleteId = Main.layoutManager.connect('startup-complete', this._onStartupComplete.bind(this));
 |  | ||||||
| +        else
 |  | ||||||
| +            this._onStartupComplete(this);
 |  | ||||||
| +    }
 |  | ||||||
| +
 |  | ||||||
| +    disable() {
 |  | ||||||
| +        this._dismissMessage();
 |  | ||||||
| +
 |  | ||||||
| +        if (this._idleWatchId) {
 |  | ||||||
| +            this._idleMonitor.remove_watch(this._idleWatchId);
 |  | ||||||
| +            this._idleWatchId = 0;
 |  | ||||||
| +        }
 |  | ||||||
| +
 |  | ||||||
| +        if (this._sessionModeUpdatedId) {
 |  | ||||||
| +            Main.sessionMode.disconnect(this._sessionModeUpdatedId);
 |  | ||||||
| +            this._sessionModeUpdatedId = 0;
 |  | ||||||
| +        }
 |  | ||||||
| +
 |  | ||||||
| +        if (this._overviewShowingId) {
 |  | ||||||
| +            Main.overview.disconnect(this._overviewShowingId);
 |  | ||||||
| +            this._overviewShowingId = 0;
 |  | ||||||
| +        }
 |  | ||||||
| +
 |  | ||||||
| +        if (this._overviewHiddenId) {
 |  | ||||||
| +            Main.overview.disconnect(this._overviewHiddenId);
 |  | ||||||
| +            this._overviewHiddenId = 0;
 |  | ||||||
| +        }
 |  | ||||||
| +
 |  | ||||||
| +        if (this._panelConnectionId) {
 |  | ||||||
| +            Main.layoutManager.panelBox.disconnect(this._panelConnectionId);
 |  | ||||||
| +            this._panelConnectionId = 0;
 |  | ||||||
| +        }
 |  | ||||||
| +
 |  | ||||||
| +        if (this._oldMapWindow) {
 |  | ||||||
| +            Main.wm._mapWindow = this._oldMapWindow;
 |  | ||||||
| +            this._oldMapWindow = null;
 |  | ||||||
| +        }
 |  | ||||||
| +
 |  | ||||||
| +        if (this._windowManagerMapId) {
 |  | ||||||
| +            global.window_manager.disconnect(this._windowManagerMapId);
 |  | ||||||
| +            this._windowManagerMapId = 0;
 |  | ||||||
| +        }
 |  | ||||||
| +
 |  | ||||||
| +        if (this._startupCompleteId) {
 |  | ||||||
| +            Main.layoutManager.disconnect(this._startupCompleteId);
 |  | ||||||
| +            this._startupCompleteId = 0;
 |  | ||||||
| +        }
 |  | ||||||
| +
 |  | ||||||
| +        if (this._settingsChangedId) {
 |  | ||||||
| +            this._settings.disconnect(this._settingsChangedId);
 |  | ||||||
| +            this._settingsChangedId = 0;
 |  | ||||||
| +        }
 |  | ||||||
| +    }
 |  | ||||||
| +
 |  | ||||||
| +    _onWindowMap(shellwm, actor) {
 |  | ||||||
| +        let windowObject = actor.meta_window;
 |  | ||||||
| +        let windowType = windowObject.get_window_type();
 |  | ||||||
| +
 |  | ||||||
| +        if (windowType != Meta.WindowType.NORMAL)
 |  | ||||||
| +            return;
 |  | ||||||
| +
 |  | ||||||
| +        if (!this._message || !this._message.visible)
 |  | ||||||
| +            return;
 |  | ||||||
| +
 |  | ||||||
| +        let messageRect = new Meta.Rectangle({ x: this._message.x, y: this._message.y, width: this._message.width, height: this._message.height });
 |  | ||||||
| +        let windowRect = windowObject.get_frame_rect();
 |  | ||||||
| +
 |  | ||||||
| +        if (windowRect.intersect(messageRect)) {
 |  | ||||||
| +            windowObject.move_frame(false, windowRect.x, this._message.y + this._message.height);
 |  | ||||||
| +        }
 |  | ||||||
| +    }
 |  | ||||||
| +
 |  | ||||||
| +    _onStartupComplete() {
 |  | ||||||
| +        this._overviewShowingId = Main.overview.connect('showing', this._updateMessage.bind(this));
 |  | ||||||
| +        this._overviewHiddenId = Main.overview.connect('hidden', this._updateMessage.bind(this));
 |  | ||||||
| +        this._panelConnectionId = Main.layoutManager.panelBox.connect('notify::visible', this._updateMessage.bind(this));
 |  | ||||||
| +        this._sessionModeUpdatedId = Main.sessionMode.connect('updated', this._onSessionModeUpdated.bind(this));
 |  | ||||||
| +
 |  | ||||||
| +        this._updateMessage();
 |  | ||||||
| +    }
 |  | ||||||
| +
 |  | ||||||
| +    _onSessionModeUpdated() {
 |  | ||||||
| +         if (!Main.sessionMode.hasWindows)
 |  | ||||||
| +             this._messageInhibitedUntilIdle = false;
 |  | ||||||
| +         this._updateMessage();
 |  | ||||||
| +    }
 |  | ||||||
| +
 |  | ||||||
| +    _onIdleTimeoutChanged() {
 |  | ||||||
| +        if (this._idleWatchId) {
 |  | ||||||
| +            this._idleMonitor.remove_watch(this._idleWatchId);
 |  | ||||||
| +            this._idleWatchId = 0;
 |  | ||||||
| +        }
 |  | ||||||
| +        this._messageInhibitedUntilIdle = false;
 |  | ||||||
| +    }
 |  | ||||||
| +
 |  | ||||||
| +    _updateMessage() {
 |  | ||||||
| +        if (this._messageInhibitedUntilIdle) {
 |  | ||||||
| +            if (this._message)
 |  | ||||||
| +                this._dismissMessage();
 |  | ||||||
| +            return;
 |  | ||||||
| +        }
 |  | ||||||
| +
 |  | ||||||
| +        if (this._idleWatchId) {
 |  | ||||||
| +            this._idleMonitor.remove_watch(this._idleWatchId);
 |  | ||||||
| +            this._idleWatchId = 0;
 |  | ||||||
| +        }
 |  | ||||||
| +
 |  | ||||||
| +        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'))
 |  | ||||||
| +            supportedModes.push('unlock-dialog');
 |  | ||||||
| +
 |  | ||||||
| +        if (this._settings.get_boolean('show-when-locked'))
 |  | ||||||
| +            supportedModes.push('lock-screen');
 |  | ||||||
| +
 |  | ||||||
| +        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;
 |  | ||||||
| +        }
 |  | ||||||
| +
 |  | ||||||
| +        let heading = this._settings.get_string('message-heading');
 |  | ||||||
| +        let body = this._settings.get_string('message-body');
 |  | ||||||
| +
 |  | ||||||
| +        if (!heading && !body) {
 |  | ||||||
| +            this._dismissMessage();
 |  | ||||||
| +            return;
 |  | ||||||
| +        }
 |  | ||||||
| +
 |  | ||||||
| +        if (!this._message) {
 |  | ||||||
| +            this._message = new HeadsUpMessage.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;
 |  | ||||||
| +
 |  | ||||||
| +        if (this._idleWatchId) {
 |  | ||||||
| +            this._idleMonitor.remove_watch(this._idleWatchId);
 |  | ||||||
| +            this._idleWatchId = 0;
 |  | ||||||
| +        }
 |  | ||||||
| +
 |  | ||||||
| +        let idleTimeout = this._settings.get_uint('idle-timeout');
 |  | ||||||
| +        this._idleWatchId = this._idleMonitor.add_idle_watch(idleTimeout * 1000, this._onUserIdle.bind(this));
 |  | ||||||
| +        this._messageInhibitedUntilIdle = true;
 |  | ||||||
| +        this._updateMessage();
 |  | ||||||
| +    }
 |  | ||||||
| +
 |  | ||||||
| +    _onUserIdle() {
 |  | ||||||
| +        this._messageInhibitedUntilIdle = false;
 |  | ||||||
| +        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;
 |  | ||||||
| +
 |  | ||||||
| +        Main.messageTray.actor.set_translation(0, 0, 0);
 |  | ||||||
| +    }
 |  | ||||||
| +
 |  | ||||||
| +    _alignMessageTray() {
 |  | ||||||
| +        if (!Main.messageTray)
 |  | ||||||
| +            return;
 |  | ||||||
| +
 |  | ||||||
| +        if (!this._message || !this._message.visible) {
 |  | ||||||
| +            this._resetMessageTray()
 |  | ||||||
| +            return;
 |  | ||||||
| +        }
 |  | ||||||
| +
 |  | ||||||
| +        let panelBottom = Main.layoutManager.panelBox.y + Main.layoutManager.panelBox.height;
 |  | ||||||
| +        let messageBottom = this._message.y + this._message.height;
 |  | ||||||
| +
 |  | ||||||
| +        Main.messageTray.actor.set_translation(0, messageBottom - panelBottom, 0);
 |  | ||||||
| +    }
 |  | ||||||
| +
 |  | ||||||
| +    _resetLoginDialog() {
 |  | ||||||
| +        if (!Main.sessionMode.isGreeter)
 |  | ||||||
| +            return;
 |  | ||||||
| +
 |  | ||||||
| +        if (!Main.screenShield || !Main.screenShield._dialog)
 |  | ||||||
| +            return;
 |  | ||||||
| +
 |  | ||||||
| +        let dialog = Main.screenShield._dialog;
 |  | ||||||
| +
 |  | ||||||
| +        if (this._authPromptAllocatedId) {
 |  | ||||||
| +            dialog.disconnect(this._authPromptAllocatedId);
 |  | ||||||
| +            this._authPromptAllocatedId = 0;
 |  | ||||||
| +        }
 |  | ||||||
| +
 |  | ||||||
| +        dialog.style = null;
 |  | ||||||
| +        dialog._bannerView.style = null;
 |  | ||||||
| +    }
 |  | ||||||
| +
 |  | ||||||
| +    _adaptLoginDialogForMessage() {
 |  | ||||||
| +        if (!Main.sessionMode.isGreeter)
 |  | ||||||
| +            return;
 |  | ||||||
| +
 |  | ||||||
| +        if (!Main.screenShield || !Main.screenShield._dialog)
 |  | ||||||
| +            return;
 |  | ||||||
| +
 |  | ||||||
| +        if (!this._message || !this._message.visible) {
 |  | ||||||
| +            this._resetLoginDialog()
 |  | ||||||
| +            return;
 |  | ||||||
| +        }
 |  | ||||||
| +
 |  | ||||||
| +        let dialog = Main.screenShield._dialog;
 |  | ||||||
| +
 |  | ||||||
| +        let messageHeight = this._message.y + this._message.height;
 |  | ||||||
| +        if (dialog._logoBin.visible)
 |  | ||||||
| +            messageHeight -= dialog._logoBin.height;
 |  | ||||||
| +
 |  | ||||||
| +        if (messageHeight <= 0) {
 |  | ||||||
| +            dialog.style = null;
 |  | ||||||
| +            dialog._bannerView.style = null;
 |  | ||||||
| +        } else {
 |  | ||||||
| +            dialog.style = `margin-top: ${messageHeight}px;`;
 |  | ||||||
| +
 |  | ||||||
| +            let bannerOnSide = dialog._bannerView.x + dialog._bannerView.width < dialog._authPrompt.actor.x;
 |  | ||||||
| +
 |  | ||||||
| +            if (bannerOnSide)
 |  | ||||||
| +               dialog._bannerView.style = `margin-bottom: ${messageHeight}px;`;
 |  | ||||||
| +            else
 |  | ||||||
| +               dialog._bannerView.style = `margin-top: ${messageHeight}px`;
 |  | ||||||
| +        }
 |  | ||||||
| +    }
 |  | ||||||
| +
 |  | ||||||
| +    _adaptSessionForMessage() {
 |  | ||||||
| +        this._alignMessageTray();
 |  | ||||||
| +
 |  | ||||||
| +        if (Main.sessionMode.isGreeter) {
 |  | ||||||
| +            this._adaptLoginDialogForMessage();
 |  | ||||||
| +            if (!this._authPromptAllocatedId) {
 |  | ||||||
| +                let dialog = Main.screenShield._dialog;
 |  | ||||||
| +                this._authPromptAllocatedId = dialog._authPrompt.actor.connect("notify::allocation", this._adaptLoginDialogForMessage.bind(this));
 |  | ||||||
| +            }
 |  | ||||||
| +        }
 |  | ||||||
| +    }
 |  | ||||||
| +}
 |  | ||||||
| +
 |  | ||||||
| +function init() {
 |  | ||||||
| +    return new Extension();
 |  | ||||||
| +}
 |  | ||||||
| diff --git a/extensions/heads-up-display/headsUpMessage.js b/extensions/heads-up-display/headsUpMessage.js
 |  | ||||||
| new file mode 100644 |  | ||||||
| index 00000000..d828d8c9
 |  | ||||||
| --- /dev/null
 |  | ||||||
| +++ b/extensions/heads-up-display/headsUpMessage.js
 |  | ||||||
| @@ -0,0 +1,150 @@
 |  | ||||||
| +const { Atk, Clutter, GObject, Pango, St } = imports.gi;
 |  | ||||||
| +const Layout = imports.ui.layout;
 |  | ||||||
| +const Main = imports.ui.main;
 |  | ||||||
| +const Signals = imports.signals;
 |  | ||||||
| +
 |  | ||||||
| +var HeadsUpMessageBodyLabel = GObject.registerClass({
 |  | ||||||
| +}, class HeadsUpMessageBodyLabel extends St.Label {
 |  | ||||||
| +    _init(params) {
 |  | ||||||
| +        super._init(params);
 |  | ||||||
| +
 |  | ||||||
| +        this.clutter_text.single_line_mode = false;
 |  | ||||||
| +        this.clutter_text.line_wrap = true;
 |  | ||||||
| +    }
 |  | ||||||
| +
 |  | ||||||
| +    vfunc_get_preferred_width(forHeight) {
 |  | ||||||
| +        let workArea = Main.layoutManager.getWorkAreaForMonitor(Main.layoutManager.primaryIndex);
 |  | ||||||
| +
 |  | ||||||
| +        let [labelMinimumWidth, labelNaturalWidth] = super.vfunc_get_preferred_width(forHeight);
 |  | ||||||
| +
 |  | ||||||
| +        labelMinimumWidth = Math.min(labelMinimumWidth, .75 * workArea.width);
 |  | ||||||
| +        labelNaturalWidth = Math.min(labelNaturalWidth, .75 * workArea.width);
 |  | ||||||
| +
 |  | ||||||
| +        return [labelMinimumWidth, labelNaturalWidth];
 |  | ||||||
| +    }
 |  | ||||||
| +
 |  | ||||||
| +    vfunc_get_preferred_height(forWidth) {
 |  | ||||||
| +        let workArea = Main.layoutManager.getWorkAreaForMonitor(Main.layoutManager.primaryIndex);
 |  | ||||||
| +        let labelHeightUpperBound = .25 * workArea.height;
 |  | ||||||
| +
 |  | ||||||
| +        this.clutter_text.single_line_mode = true;
 |  | ||||||
| +        this.clutter_text.line_wrap = false;
 |  | ||||||
| +        let [lineHeight] = super.vfunc_get_preferred_height(-1);
 |  | ||||||
| +        let numberOfLines = Math.floor(labelHeightUpperBound / lineHeight);
 |  | ||||||
| +        numberOfLines = Math.max(numberOfLines, 1);
 |  | ||||||
| +
 |  | ||||||
| +        let labelHeight = lineHeight * numberOfLines;
 |  | ||||||
| +
 |  | ||||||
| +        this.clutter_text.single_line_mode = false;
 |  | ||||||
| +        this.clutter_text.line_wrap = true;
 |  | ||||||
| +        let [labelMinimumHeight, labelNaturalHeight] = super.vfunc_get_preferred_height(forWidth);
 |  | ||||||
| +
 |  | ||||||
| +        labelMinimumHeight = Math.min(labelMinimumHeight, labelHeight);
 |  | ||||||
| +        labelNaturalHeight = Math.min(labelNaturalHeight, labelHeight);
 |  | ||||||
| +
 |  | ||||||
| +        return [labelMinimumHeight, labelNaturalHeight];
 |  | ||||||
| +    }
 |  | ||||||
| +
 |  | ||||||
| +    vfunc_allocate(box, flags) {
 |  | ||||||
| +        if (!this.visible)
 |  | ||||||
| +            return;
 |  | ||||||
| +
 |  | ||||||
| +        super.vfunc_allocate(box, flags);
 |  | ||||||
| +    }
 |  | ||||||
| +});
 |  | ||||||
| +
 |  | ||||||
| +var HeadsUpMessage = GObject.registerClass({
 |  | ||||||
| +}, class HeadsUpMessage extends St.Button {
 |  | ||||||
| +    _init(heading, body) {
 |  | ||||||
| +        super._init({
 |  | ||||||
| +            style_class: 'message',
 |  | ||||||
| +            accessible_role: Atk.Role.NOTIFICATION,
 |  | ||||||
| +            can_focus: false,
 |  | ||||||
| +        });
 |  | ||||||
| +
 |  | ||||||
| +        Main.layoutManager.addChrome(this, { affectsInputRegion: true });
 |  | ||||||
| +
 |  | ||||||
| +        this.add_style_class_name('heads-up-display-message');
 |  | ||||||
| +
 |  | ||||||
| +        this._panelAllocationId = Main.layoutManager.panelBox.connect ("notify::allocation", this._alignWithPanel.bind(this));
 |  | ||||||
| +        this.connect("notify::allocation", this._alignWithPanel.bind(this));
 |  | ||||||
| +
 |  | ||||||
| +        this._messageTraySnappingId = Main.messageTray.connect ("notify::y", () => {
 |  | ||||||
| +            if (!this.visible)
 |  | ||||||
| +                return;
 |  | ||||||
| +
 |  | ||||||
| +            if (!Main.messageTray.visible)
 |  | ||||||
| +                return;
 |  | ||||||
| +
 |  | ||||||
| +            if (Main.messageTray.y >= this.y && Main.messageTray.y < this.y + this.height)
 |  | ||||||
| +                Main.messageTray.y = this.y + this.height;
 |  | ||||||
| +        });
 |  | ||||||
| +
 |  | ||||||
| +
 |  | ||||||
| +        let contentsBox = new St.BoxLayout({ style_class: 'heads-up-message-content',
 |  | ||||||
| +                                             vertical: true,
 |  | ||||||
| +                                             x_align: Clutter.ActorAlign.CENTER });
 |  | ||||||
| +        this.add_actor(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_actor(this.headingLabel);
 |  | ||||||
| +        this.contentsBox = contentsBox;
 |  | ||||||
| +
 |  | ||||||
| +        this.bodyLabel = new HeadsUpMessageBodyLabel({ style_class: 'heads-up-message-body',
 |  | ||||||
| +                                                       x_expand: true,
 |  | ||||||
| +                                                       y_expand: true });
 |  | ||||||
| +        contentsBox.add_actor(this.bodyLabel);
 |  | ||||||
| +
 |  | ||||||
| +        this.setBody(body);
 |  | ||||||
| +        this.bodyLabel.clutter_text.label = this.bodyLabel;
 |  | ||||||
| +    }
 |  | ||||||
| +
 |  | ||||||
| +    _alignWithPanel() {
 |  | ||||||
| +        if (!this.visible)
 |  | ||||||
| +            return;
 |  | ||||||
| +
 |  | ||||||
| +        this.x = Main.panel.actor.x;
 |  | ||||||
| +        this.x += Main.panel.actor.width / 2;
 |  | ||||||
| +        this.x -= this.width / 2;
 |  | ||||||
| +        this.x = Math.floor(this.x);
 |  | ||||||
| +        this.y = Main.panel.actor.y + Main.panel.actor.height;
 |  | ||||||
| +        this.queue_relayout();
 |  | ||||||
| +    }
 |  | ||||||
| +
 |  | ||||||
| +    setHeading(text) {
 |  | ||||||
| +        if (text) {
 |  | ||||||
| +            let 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;
 |  | ||||||
| +        }
 |  | ||||||
| +    }
 |  | ||||||
| +
 |  | ||||||
| +    destroy() {
 |  | ||||||
| +        if (this._panelAllocationId) {
 |  | ||||||
| +            Main.layoutManager.panelBox.disconnect(this._panelAllocationId);
 |  | ||||||
| +            this._panelAllocationId = 0;
 |  | ||||||
| +        }
 |  | ||||||
| +
 |  | ||||||
| +        if (this._messageTraySnappingId) {
 |  | ||||||
| +            Main.messageTray.disconnect(this._messageTraySnappingId);
 |  | ||||||
| +            this._messageTraySnappingId = 0;
 |  | ||||||
| +        }
 |  | ||||||
| +
 |  | ||||||
| +        super.destroy();
 |  | ||||||
| +    }
 |  | ||||||
| +});
 |  | ||||||
| diff --git a/extensions/heads-up-display/meson.build b/extensions/heads-up-display/meson.build
 |  | ||||||
| new file mode 100644 |  | ||||||
| index 00000000..40c3de0a
 |  | ||||||
| --- /dev/null
 |  | ||||||
| +++ b/extensions/heads-up-display/meson.build
 |  | ||||||
| @@ -0,0 +1,8 @@
 |  | ||||||
| +extension_data += configure_file(
 |  | ||||||
| +  input: metadata_name + '.in',
 |  | ||||||
| +  output: metadata_name,
 |  | ||||||
| +  configuration: metadata_conf
 |  | ||||||
| +)
 |  | ||||||
| +
 |  | ||||||
| +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..e7ab71aa
 |  | ||||||
| --- /dev/null
 |  | ||||||
| +++ b/extensions/heads-up-display/metadata.json.in
 |  | ||||||
| @@ -0,0 +1,11 @@
 |  | ||||||
| +{
 |  | ||||||
| +"extension-id": "@extension_id@",
 |  | ||||||
| +"uuid": "@uuid@",
 |  | ||||||
| +"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..ea1f3774
 |  | ||||||
| --- /dev/null
 |  | ||||||
| +++ b/extensions/heads-up-display/org.gnome.shell.extensions.heads-up-display.gschema.xml
 |  | ||||||
| @@ -0,0 +1,54 @@
 |  | ||||||
| +<schemalist gettext-domain="gnome-shell-extensions">
 |  | ||||||
| +  <schema id="org.gnome.shell.extensions.heads-up-display"
 |  | ||||||
| +          path="/org/gnome/shell/extensions/heads-up-display/">
 |  | ||||||
| +    <key name="idle-timeout" type="u">
 |  | ||||||
| +      <default>30</default>
 |  | ||||||
| +      <summary>Idle Timeout</summary>
 |  | ||||||
| +      <description>
 |  | ||||||
| +        Number of seconds until message is reshown after user goes idle.
 |  | ||||||
| +      </description>
 |  | ||||||
| +    </key>
 |  | ||||||
| +    <key name="message-heading" type="s">
 |  | ||||||
| +      <default>""</default>
 |  | ||||||
| +      <summary>Message to show at top of display</summary>
 |  | ||||||
| +      <description>
 |  | ||||||
| +        The top line of the heads up display message.
 |  | ||||||
| +      </description>
 |  | ||||||
| +    </key>
 |  | ||||||
| +    <key name="message-body" type="s">
 |  | ||||||
| +      <default>""</default>
 |  | ||||||
| +      <summary>Banner message</summary>
 |  | ||||||
| +      <description>
 |  | ||||||
| +        A message to always show at the top of the screen.
 |  | ||||||
| +      </description>
 |  | ||||||
| +    </key>
 |  | ||||||
| +    <key name="show-on-login-screen" type="b">
 |  | ||||||
| +      <default>true</default>
 |  | ||||||
| +      <summary>Show on login screen</summary>
 |  | ||||||
| +      <description>
 |  | ||||||
| +        Whether or not the message should display on the login screen
 |  | ||||||
| +      </description>
 |  | ||||||
| +    </key>
 |  | ||||||
| +    <key name="show-when-locked" type="b">
 |  | ||||||
| +      <default>false</default>
 |  | ||||||
| +      <summary>Show on screen shield</summary>
 |  | ||||||
| +      <description>
 |  | ||||||
| +        Whether or not the message should display when the screen is locked
 |  | ||||||
| +      </description>
 |  | ||||||
| +    </key>
 |  | ||||||
| +    <key name="show-when-unlocking" type="b">
 |  | ||||||
| +      <default>false</default>
 |  | ||||||
| +      <summary>Show on unlock screen</summary>
 |  | ||||||
| +      <description>
 |  | ||||||
| +        Whether or not the message should display on the unlock screen.
 |  | ||||||
| +      </description>
 |  | ||||||
| +    </key>
 |  | ||||||
| +    <key name="show-when-unlocked" type="b">
 |  | ||||||
| +      <default>false</default>
 |  | ||||||
| +      <summary>Show in user session</summary>
 |  | ||||||
| +      <description>
 |  | ||||||
| +        Whether or not the message should display when the screen is unlocked.
 |  | ||||||
| +      </description>
 |  | ||||||
| +    </key>
 |  | ||||||
| +  </schema>
 |  | ||||||
| +</schemalist>
 |  | ||||||
| diff --git a/extensions/heads-up-display/prefs.js b/extensions/heads-up-display/prefs.js
 |  | ||||||
| new file mode 100644 |  | ||||||
| index 00000000..b4b6f94c
 |  | ||||||
| --- /dev/null
 |  | ||||||
| +++ b/extensions/heads-up-display/prefs.js
 |  | ||||||
| @@ -0,0 +1,175 @@
 |  | ||||||
| +
 |  | ||||||
| +/* Desktop Icons GNOME Shell extension
 |  | ||||||
| + *
 |  | ||||||
| + * Copyright (C) 2017 Carlos Soriano <csoriano@redhat.com>
 |  | ||||||
| + *
 |  | ||||||
| + * 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 <http://www.gnu.org/licenses/>.
 |  | ||||||
| + */
 |  | ||||||
| +
 |  | ||||||
| +const { Gio, GObject, Gdk, Gtk } = imports.gi;
 |  | ||||||
| +const ExtensionUtils = imports.misc.extensionUtils;
 |  | ||||||
| +const Gettext = imports.gettext.domain('gnome-shell-extensions');
 |  | ||||||
| +const _ = Gettext.gettext;
 |  | ||||||
| +const N_ = e => e;
 |  | ||||||
| +const cssData = `
 |  | ||||||
| +   .no-border {
 |  | ||||||
| +       border: none;
 |  | ||||||
| +   }
 |  | ||||||
| +
 |  | ||||||
| +   .border {
 |  | ||||||
| +       border: 1px solid;
 |  | ||||||
| +       border-radius: 3px;
 |  | ||||||
| +       border-color: #b6b6b3;
 |  | ||||||
| +       box-shadow: inset 0 0 0 1px rgba(74, 144, 217, 0);
 |  | ||||||
| +       background-color: white;
 |  | ||||||
| +   }
 |  | ||||||
| +
 |  | ||||||
| +   .margins {
 |  | ||||||
| +       padding-left: 8px;
 |  | ||||||
| +       padding-right: 8px;
 |  | ||||||
| +       padding-bottom: 8px;
 |  | ||||||
| +   }
 |  | ||||||
| +
 |  | ||||||
| +   .message-label {
 |  | ||||||
| +       font-weight: bold;
 |  | ||||||
| +   }
 |  | ||||||
| +`;
 |  | ||||||
| +
 |  | ||||||
| +var settings;
 |  | ||||||
| +
 |  | ||||||
| +function init() {
 |  | ||||||
| +    settings = ExtensionUtils.getSettings("org.gnome.shell.extensions.heads-up-display");
 |  | ||||||
| +    let cssProvider = new Gtk.CssProvider();
 |  | ||||||
| +    cssProvider.load_from_data(cssData);
 |  | ||||||
| +
 |  | ||||||
| +    let screen = Gdk.Screen.get_default();
 |  | ||||||
| +    Gtk.StyleContext.add_provider_for_screen(screen, cssProvider, Gtk.STYLE_PROVIDER_PRIORITY_APPLICATION);
 |  | ||||||
| +}
 |  | ||||||
| +
 |  | ||||||
| +function buildPrefsWidget() {
 |  | ||||||
| +    ExtensionUtils.initTranslations();
 |  | ||||||
| +
 |  | ||||||
| +    let contents = new Gtk.Box({
 |  | ||||||
| +        orientation: Gtk.Orientation.VERTICAL,
 |  | ||||||
| +        border_width: 20,
 |  | ||||||
| +        spacing: 10,
 |  | ||||||
| +    });
 |  | ||||||
| +
 |  | ||||||
| +    contents.add(buildSwitch('show-when-locked', _("Show message when screen is locked")));
 |  | ||||||
| +    contents.add(buildSwitch('show-when-unlocking', _("Show message on unlock screen")));
 |  | ||||||
| +    contents.add(buildSwitch('show-when-unlocked', _("Show message when screen is unlocked")));
 |  | ||||||
| +    contents.add(buildSpinButton('idle-timeout', _("Seconds after user goes idle before reshowing message")));
 |  | ||||||
| +
 |  | ||||||
| +    let outerMessageBox = new Gtk.Box({
 |  | ||||||
| +        orientation: Gtk.Orientation.VERTICAL,
 |  | ||||||
| +        border_width: 0,
 |  | ||||||
| +        spacing: 5,
 |  | ||||||
| +    });
 |  | ||||||
| +    contents.add(outerMessageBox);
 |  | ||||||
| +
 |  | ||||||
| +    let messageLabel = new Gtk.Label({
 |  | ||||||
| +        label: 'Message',
 |  | ||||||
| +        halign: Gtk.Align.START,
 |  | ||||||
| +    });
 |  | ||||||
| +    messageLabel.get_style_context().add_class("message-label");
 |  | ||||||
| +    outerMessageBox.add(messageLabel);
 |  | ||||||
| +
 |  | ||||||
| +    let innerMessageBox = new Gtk.Box({
 |  | ||||||
| +        orientation: Gtk.Orientation.VERTICAL,
 |  | ||||||
| +        border_width: 0,
 |  | ||||||
| +        spacing: 0,
 |  | ||||||
| +    });
 |  | ||||||
| +    innerMessageBox.get_style_context().add_class("border");
 |  | ||||||
| +    outerMessageBox.add(innerMessageBox);
 |  | ||||||
| +
 |  | ||||||
| +    innerMessageBox.add(buildEntry('message-heading', _("Message Heading")));
 |  | ||||||
| +    innerMessageBox.add(buildTextView('message-body', _("Message Body")));
 |  | ||||||
| +    contents.show_all();
 |  | ||||||
| +    return contents;
 |  | ||||||
| +}
 |  | ||||||
| +
 |  | ||||||
| +function buildTextView(key, labelText) {
 |  | ||||||
| +    let textView = new Gtk.TextView({
 |  | ||||||
| +        accepts_tab: false,
 |  | ||||||
| +        wrap_mode: Gtk.WrapMode.WORD,
 |  | ||||||
| +    });
 |  | ||||||
| +    settings.bind(key, textView.get_buffer(), 'text', Gio.SettingsBindFlags.DEFAULT);
 |  | ||||||
| +
 |  | ||||||
| +    let scrolledWindow = new Gtk.ScrolledWindow({
 |  | ||||||
| +        expand: true,
 |  | ||||||
| +    });
 |  | ||||||
| +    let styleContext = scrolledWindow.get_style_context();
 |  | ||||||
| +    styleContext.add_class("margins");
 |  | ||||||
| +
 |  | ||||||
| +    scrolledWindow.add(textView);
 |  | ||||||
| +    return scrolledWindow;
 |  | ||||||
| +}
 |  | ||||||
| +function buildEntry(key, labelText) {
 |  | ||||||
| +    let entry = new Gtk.Entry({ placeholder_text: labelText });
 |  | ||||||
| +    let styleContext = entry.get_style_context();
 |  | ||||||
| +    styleContext.add_class("no-border");
 |  | ||||||
| +    settings.bind(key, entry, 'text', Gio.SettingsBindFlags.DEFAULT);
 |  | ||||||
| +
 |  | ||||||
| +    entry.get_settings()['gtk-entry-select-on-focus'] = false;
 |  | ||||||
| +
 |  | ||||||
| +    return entry;
 |  | ||||||
| +}
 |  | ||||||
| +
 |  | ||||||
| +function buildSpinButton(key, labelText) {
 |  | ||||||
| +    let hbox = new Gtk.Box({
 |  | ||||||
| +        orientation: Gtk.Orientation.HORIZONTAL,
 |  | ||||||
| +        spacing: 10,
 |  | ||||||
| +    });
 |  | ||||||
| +    let label = new Gtk.Label({
 |  | ||||||
| +        label: labelText,
 |  | ||||||
| +        xalign: 0,
 |  | ||||||
| +    });
 |  | ||||||
| +    let adjustment = new Gtk.Adjustment({
 |  | ||||||
| +        value: 0,
 |  | ||||||
| +        lower: 0,
 |  | ||||||
| +        upper: 2147483647,
 |  | ||||||
| +        step_increment: 1,
 |  | ||||||
| +        page_increment: 60,
 |  | ||||||
| +        page_size: 60,
 |  | ||||||
| +    });
 |  | ||||||
| +    let spinButton = new Gtk.SpinButton({
 |  | ||||||
| +        adjustment: adjustment,
 |  | ||||||
| +        climb_rate: 1.0,
 |  | ||||||
| +        digits: 0,
 |  | ||||||
| +        max_width_chars: 3,
 |  | ||||||
| +        width_chars: 3,
 |  | ||||||
| +    });
 |  | ||||||
| +    settings.bind(key, spinButton, 'value', Gio.SettingsBindFlags.DEFAULT);
 |  | ||||||
| +    hbox.pack_start(label, true, true, 0);
 |  | ||||||
| +    hbox.add(spinButton);
 |  | ||||||
| +    return hbox;
 |  | ||||||
| +}
 |  | ||||||
| +
 |  | ||||||
| +function buildSwitch(key, labelText) {
 |  | ||||||
| +    let hbox = new Gtk.Box({
 |  | ||||||
| +        orientation: Gtk.Orientation.HORIZONTAL,
 |  | ||||||
| +        spacing: 10,
 |  | ||||||
| +    });
 |  | ||||||
| +    let label = new Gtk.Label({
 |  | ||||||
| +        label: labelText,
 |  | ||||||
| +        xalign: 0,
 |  | ||||||
| +    });
 |  | ||||||
| +    let switcher = new Gtk.Switch({
 |  | ||||||
| +        active: settings.get_boolean(key),
 |  | ||||||
| +    });
 |  | ||||||
| +    settings.bind(key, switcher, 'active', Gio.SettingsBindFlags.DEFAULT);
 |  | ||||||
| +    hbox.pack_start(label, true, true, 0);
 |  | ||||||
| +    hbox.add(switcher);
 |  | ||||||
| +    return hbox;
 |  | ||||||
| +}
 |  | ||||||
| diff --git a/extensions/heads-up-display/stylesheet.css b/extensions/heads-up-display/stylesheet.css
 |  | ||||||
| new file mode 100644 |  | ||||||
| index 00000000..93034469
 |  | ||||||
| --- /dev/null
 |  | ||||||
| +++ b/extensions/heads-up-display/stylesheet.css
 |  | ||||||
| @@ -0,0 +1,32 @@
 |  | ||||||
| +.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 ba84f8f3..c5fc86ef 100644
 |  | ||||||
| --- a/meson.build
 |  | ||||||
| +++ b/meson.build
 |  | ||||||
| @@ -44,6 +44,7 @@ classic_extensions = [
 |  | ||||||
|  default_extensions = classic_extensions |  | ||||||
|  default_extensions += [ |  | ||||||
|    'drive-menu', |  | ||||||
| +  'heads-up-display',
 |  | ||||||
|    'screenshot-window-sizer', |  | ||||||
|    'windowsNavigator', |  | ||||||
|    'workspace-indicator' |  | ||||||
| -- 
 |  | ||||||
| 2.32.0 |  | ||||||
| 
 |  | ||||||
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							| @ -1,27 +0,0 @@ | |||||||
| From ce48dc2f4fba6a7084540df256cb5b3eb0da43da Mon Sep 17 00:00:00 2001 |  | ||||||
| From: =?UTF-8?q?Florian=20M=C3=BCllner?= <fmuellner@gnome.org> |  | ||||||
| Date: Wed, 2 Jun 2021 17:32:21 +0200 |  | ||||||
| Subject: [PATCH] top-icons: Don't use wm_class as role |  | ||||||
| 
 |  | ||||||
| This prevents adding icons for multiple instances of the same app, |  | ||||||
| which may be desirable in some circumstances. |  | ||||||
| ---
 |  | ||||||
|  extensions/top-icons/extension.js | 2 +- |  | ||||||
|  1 file changed, 1 insertion(+), 1 deletion(-) |  | ||||||
| 
 |  | ||||||
| diff --git a/extensions/top-icons/extension.js b/extensions/top-icons/extension.js
 |  | ||||||
| index 79e2f423..3dfba469 100644
 |  | ||||||
| --- a/extensions/top-icons/extension.js
 |  | ||||||
| +++ b/extensions/top-icons/extension.js
 |  | ||||||
| @@ -63,7 +63,7 @@ class SysTray {
 |  | ||||||
|              button.destroy(); |  | ||||||
|          }); |  | ||||||
|   |  | ||||||
| -        let role = wmClass || `${icon}`;
 |  | ||||||
| +        const role = `${icon}`;
 |  | ||||||
|          Main.panel.addToStatusArea(role, button); |  | ||||||
|      } |  | ||||||
|   |  | ||||||
| -- 
 |  | ||||||
| 2.31.1 |  | ||||||
| 
 |  | ||||||
| @ -1,55 +0,0 @@ | |||||||
| From b4eeaf7ea12fa7d9713e80371490d8060396b3cb Mon Sep 17 00:00:00 2001 |  | ||||||
| From: Milan Crha <mcrha@redhat.com> |  | ||||||
| Date: Fri, 17 Apr 2020 09:21:42 +0200 |  | ||||||
| Subject: [PATCH] window-list: Invalid current mode selected in Preferences |  | ||||||
| 
 |  | ||||||
| It seems that gtk+ resets the active radio whenever a new radio button |  | ||||||
| is added into the group, thus rather restore the current mode after |  | ||||||
| the group is fully populated. |  | ||||||
| 
 |  | ||||||
| https://gitlab.gnome.org/GNOME/gnome-shell-extensions/-/merge_requests/119 |  | ||||||
| ---
 |  | ||||||
|  extensions/window-list/prefs.js | 9 ++++++++- |  | ||||||
|  1 file changed, 8 insertions(+), 1 deletion(-) |  | ||||||
| 
 |  | ||||||
| diff --git a/extensions/window-list/prefs.js b/extensions/window-list/prefs.js
 |  | ||||||
| index 78792b5..17e9799 100644
 |  | ||||||
| --- a/extensions/window-list/prefs.js
 |  | ||||||
| +++ b/extensions/window-list/prefs.js
 |  | ||||||
| @@ -50,6 +50,7 @@ class WindowListPrefsWidget extends Gtk.Grid {
 |  | ||||||
|          }; |  | ||||||
|   |  | ||||||
|          let radio = null; |  | ||||||
| +        let currentRadio = null;
 |  | ||||||
|          for (let i = 0; i < modes.length; i++) { |  | ||||||
|              let mode = modes[i]; |  | ||||||
|              let label = modeLabels[mode]; |  | ||||||
| @@ -59,18 +60,24 @@ class WindowListPrefsWidget extends Gtk.Grid {
 |  | ||||||
|              } |  | ||||||
|   |  | ||||||
|              radio = new Gtk.RadioButton({ |  | ||||||
| -                active: currentMode == mode,
 |  | ||||||
| +                active: !i,
 |  | ||||||
|                  label: label, |  | ||||||
|                  group: radio |  | ||||||
|              }); |  | ||||||
|              grid.add(radio); |  | ||||||
|   |  | ||||||
| +            if (currentMode === mode)
 |  | ||||||
| +                currentRadio = radio;
 |  | ||||||
| +
 |  | ||||||
|              radio.connect('toggled', button => { |  | ||||||
|                  if (button.active) |  | ||||||
|                      this._settings.set_string('grouping-mode', mode); |  | ||||||
|              }); |  | ||||||
|          } |  | ||||||
|   |  | ||||||
| +        if (currentRadio)
 |  | ||||||
| +            currentRadio.active = true;
 |  | ||||||
| +
 |  | ||||||
|          let check = new Gtk.CheckButton({ |  | ||||||
|              label: _('Show on all monitors'), |  | ||||||
|              margin_top: 6 |  | ||||||
| -- 
 |  | ||||||
| 2.26.2 |  | ||||||
| 
 |  | ||||||
| @ -1,30 +0,0 @@ | |||||||
| From ee25c2aac70b86f31c91f6491dad4c67a59bc261 Mon Sep 17 00:00:00 2001 |  | ||||||
| From: =?UTF-8?q?Florian=20M=C3=BCllner?= <fmuellner@gnome.org> |  | ||||||
| Date: Tue, 26 Jan 2021 21:14:47 +0100 |  | ||||||
| Subject: [PATCH] window-list: Leave "fake overview" when destroyed |  | ||||||
| 
 |  | ||||||
| Otherwise we leave an incomplete overview-like state around, which |  | ||||||
| can cause issues later when the extension is re-enabled (for example |  | ||||||
| when coming back from screen lock). |  | ||||||
| 
 |  | ||||||
| https://bugzilla.redhat.com/show_bug.cgi?id=1904371 |  | ||||||
| ---
 |  | ||||||
|  extensions/window-list/windowPicker.js | 2 ++ |  | ||||||
|  1 file changed, 2 insertions(+) |  | ||||||
| 
 |  | ||||||
| diff --git a/extensions/window-list/windowPicker.js b/extensions/window-list/windowPicker.js
 |  | ||||||
| index 12a7627..afb5d27 100644
 |  | ||||||
| --- a/extensions/window-list/windowPicker.js
 |  | ||||||
| +++ b/extensions/window-list/windowPicker.js
 |  | ||||||
| @@ -210,6 +210,8 @@ var WindowPicker = class {
 |  | ||||||
|      } |  | ||||||
|   |  | ||||||
|      _onDestroy() { |  | ||||||
| +        this._fakeOverviewVisible(false);
 |  | ||||||
| +
 |  | ||||||
|          if (this._monitorsChangedId) |  | ||||||
|              Main.layoutManager.disconnect(this._monitorsChangedId); |  | ||||||
|          this._monitorsChangedId = 0; |  | ||||||
| -- 
 |  | ||||||
| 2.31.1 |  | ||||||
| 
 |  | ||||||
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							| @ -1,176 +0,0 @@ | |||||||
| From b87a0085342b9828e7e57e8db892b79e345bfbc3 Mon Sep 17 00:00:00 2001 |  | ||||||
| From: =?UTF-8?q?Florian=20M=C3=BCllner?= <fmuellner@gnome.org> |  | ||||||
| Date: Wed, 20 Jan 2021 20:18:39 +0100 |  | ||||||
| Subject: [PATCH 1/2] workspace-indicator: Add tooltips to workspace thumbnails |  | ||||||
| 
 |  | ||||||
| When showing previews instead of the menu, the workspace names from |  | ||||||
| our preferences don't appear anywhere. Some users care strongly about |  | ||||||
| those, so expose them as tooltip on hover. |  | ||||||
| ---
 |  | ||||||
|  extensions/workspace-indicator/extension.js | 40 +++++++++++++++++++++ |  | ||||||
|  1 file changed, 40 insertions(+) |  | ||||||
| 
 |  | ||||||
| diff --git a/extensions/workspace-indicator/extension.js b/extensions/workspace-indicator/extension.js
 |  | ||||||
| index 69eef88c..d377f288 100644
 |  | ||||||
| --- a/extensions/workspace-indicator/extension.js
 |  | ||||||
| +++ b/extensions/workspace-indicator/extension.js
 |  | ||||||
| @@ -8,6 +8,7 @@ const ExtensionUtils = imports.misc.extensionUtils;
 |  | ||||||
|  const Main = imports.ui.main; |  | ||||||
|  const PanelMenu = imports.ui.panelMenu; |  | ||||||
|  const PopupMenu = imports.ui.popupMenu; |  | ||||||
| +const Tweener = imports.ui.tweener;
 |  | ||||||
|   |  | ||||||
|  const Gettext = imports.gettext.domain('gnome-shell-extensions'); |  | ||||||
|  const _ = Gettext.gettext; |  | ||||||
| @@ -15,6 +16,9 @@ const _ = Gettext.gettext;
 |  | ||||||
|  const WORKSPACE_SCHEMA = 'org.gnome.desktop.wm.preferences'; |  | ||||||
|  const WORKSPACE_KEY = 'workspace-names'; |  | ||||||
|   |  | ||||||
| +const TOOLTIP_OFFSET = 6;
 |  | ||||||
| +const TOOLTIP_ANIMATION_TIME = 150;
 |  | ||||||
| +
 |  | ||||||
|  let WindowPreview = GObject.registerClass({ |  | ||||||
|      GTypeName: 'WorkspaceIndicatorWindowPreview' |  | ||||||
|  }, class WindowPreview extends St.Button { |  | ||||||
| @@ -117,7 +121,14 @@ let WorkspaceThumbnail = GObject.registerClass({
 |  | ||||||
|              y_fill: true |  | ||||||
|          }); |  | ||||||
|   |  | ||||||
| +        this._tooltip = new St.Label({
 |  | ||||||
| +            style_class: 'dash-label',
 |  | ||||||
| +            visible: false,
 |  | ||||||
| +        });
 |  | ||||||
| +        Main.uiGroup.add_child(this._tooltip);
 |  | ||||||
| +
 |  | ||||||
|          this.connect('destroy', this._onDestroy.bind(this)); |  | ||||||
| +        this.connect('notify::hover', this._syncTooltip.bind(this));
 |  | ||||||
|   |  | ||||||
|          this._index = index; |  | ||||||
|          this._delegate = this; // needed for DND |  | ||||||
| @@ -204,7 +215,36 @@ let WorkspaceThumbnail = GObject.registerClass({
 |  | ||||||
|              ws.activate(global.get_current_time()); |  | ||||||
|      } |  | ||||||
|   |  | ||||||
| +    _syncTooltip() {
 |  | ||||||
| +        if (this.hover) {
 |  | ||||||
| +            this._tooltip.text = Meta.prefs_get_workspace_name(this._index);
 |  | ||||||
| +            this._tooltip.opacity = 0;
 |  | ||||||
| +            this._tooltip.show();
 |  | ||||||
| +
 |  | ||||||
| +            const [stageX, stageY] = this.get_transformed_position();
 |  | ||||||
| +            const thumbWidth = this.allocation.get_width();
 |  | ||||||
| +            const thumbHeight = this.allocation.get_height();
 |  | ||||||
| +            const tipWidth = this._tooltip.width;
 |  | ||||||
| +            const xOffset = Math.floor((thumbWidth - tipWidth) / 2);
 |  | ||||||
| +            const monitor = Main.layoutManager.findMonitorForActor(this);
 |  | ||||||
| +            const x = Math.min(
 |  | ||||||
| +                Math.max(stageX + xOffset, monitor.x),
 |  | ||||||
| +                monitor.x + monitor.width - tipWidth);
 |  | ||||||
| +            const y = stageY + thumbHeight + TOOLTIP_OFFSET;
 |  | ||||||
| +            this._tooltip.set_position(x, y);
 |  | ||||||
| +        }
 |  | ||||||
| +
 |  | ||||||
| +        Tweener.addTween(this._tooltip, {
 |  | ||||||
| +            opacity: this.hover ? 255 : 0,
 |  | ||||||
| +            time: TOOLTIP_ANIMATION_TIME / 1000,
 |  | ||||||
| +            transition: 'easeOutQuad',
 |  | ||||||
| +            onComplete: () => (this._tooltip.visible = this.hover),
 |  | ||||||
| +        });
 |  | ||||||
| +    }
 |  | ||||||
| +
 |  | ||||||
|      _onDestroy() { |  | ||||||
| +        this._tooltip.destroy();
 |  | ||||||
| +
 |  | ||||||
|          this._workspace.disconnect(this._windowAddedId); |  | ||||||
|          this._workspace.disconnect(this._windowRemovedId); |  | ||||||
|          global.display.disconnect(this._restackedId); |  | ||||||
| -- 
 |  | ||||||
| 2.44.0 |  | ||||||
| 
 |  | ||||||
| 
 |  | ||||||
| From 36f2762c8c6cda512f164ea22b62d10d03a369b6 Mon Sep 17 00:00:00 2001 |  | ||||||
| From: =?UTF-8?q?Florian=20M=C3=BCllner?= <fmuellner@gnome.org> |  | ||||||
| Date: Wed, 20 Jan 2021 20:29:01 +0100 |  | ||||||
| Subject: [PATCH 2/2] window-list: Add tooltips to workspace thumbnails |  | ||||||
| 
 |  | ||||||
| When showing previews instead of the menu, the workspace names |  | ||||||
| don't appear anywhere. Some users care strongly about those, so |  | ||||||
| expose them as tooltip on hover. |  | ||||||
| ---
 |  | ||||||
|  extensions/window-list/workspaceIndicator.js | 40 ++++++++++++++++++++ |  | ||||||
|  1 file changed, 40 insertions(+) |  | ||||||
| 
 |  | ||||||
| diff --git a/extensions/window-list/workspaceIndicator.js b/extensions/window-list/workspaceIndicator.js
 |  | ||||||
| index ca476111..8ae9b288 100644
 |  | ||||||
| --- a/extensions/window-list/workspaceIndicator.js
 |  | ||||||
| +++ b/extensions/window-list/workspaceIndicator.js
 |  | ||||||
| @@ -5,10 +5,14 @@ const DND = imports.ui.dnd;
 |  | ||||||
|  const Main = imports.ui.main; |  | ||||||
|  const PanelMenu = imports.ui.panelMenu; |  | ||||||
|  const PopupMenu = imports.ui.popupMenu; |  | ||||||
| +const Tweener = imports.ui.tweener;
 |  | ||||||
|   |  | ||||||
|  const Gettext = imports.gettext.domain('gnome-shell-extensions'); |  | ||||||
|  const _ = Gettext.gettext; |  | ||||||
|   |  | ||||||
| +const TOOLTIP_OFFSET = 6;
 |  | ||||||
| +const TOOLTIP_ANIMATION_TIME = 150;
 |  | ||||||
| +
 |  | ||||||
|  let WindowPreview = GObject.registerClass({ |  | ||||||
|      GTypeName: 'WindowListWindowPreview' |  | ||||||
|  }, class WindowPreview extends St.Button { |  | ||||||
| @@ -111,7 +115,14 @@ let WorkspaceThumbnail = GObject.registerClass({
 |  | ||||||
|              y_fill: true |  | ||||||
|          }); |  | ||||||
|   |  | ||||||
| +        this._tooltip = new St.Label({
 |  | ||||||
| +            style_class: 'dash-label',
 |  | ||||||
| +            visible: false,
 |  | ||||||
| +        });
 |  | ||||||
| +        Main.uiGroup.add_child(this._tooltip);
 |  | ||||||
| +
 |  | ||||||
|          this.connect('destroy', this._onDestroy.bind(this)); |  | ||||||
| +        this.connect('notify::hover', this._syncTooltip.bind(this));
 |  | ||||||
|   |  | ||||||
|          this._index = index; |  | ||||||
|          this._delegate = this; // needed for DND |  | ||||||
| @@ -198,7 +209,36 @@ let WorkspaceThumbnail = GObject.registerClass({
 |  | ||||||
|              ws.activate(global.get_current_time()); |  | ||||||
|      } |  | ||||||
|   |  | ||||||
| +    _syncTooltip() {
 |  | ||||||
| +        if (this.hover) {
 |  | ||||||
| +            this._tooltip.text = Meta.prefs_get_workspace_name(this._index);
 |  | ||||||
| +            this._tooltip.opacity = 0;
 |  | ||||||
| +            this._tooltip.show();
 |  | ||||||
| +
 |  | ||||||
| +            const [stageX, stageY] = this.get_transformed_position();
 |  | ||||||
| +            const thumbWidth = this.allocation.get_width();
 |  | ||||||
| +            const tipWidth = this._tooltip.width;
 |  | ||||||
| +            const tipHeight = this._tooltip.height;
 |  | ||||||
| +            const xOffset = Math.floor((thumbWidth - tipWidth) / 2);
 |  | ||||||
| +            const monitor = Main.layoutManager.findMonitorForActor(this);
 |  | ||||||
| +            const x = Math.min(
 |  | ||||||
| +                Math.max(stageX + xOffset, monitor.x),
 |  | ||||||
| +                monitor.x + monitor.width - tipWidth);
 |  | ||||||
| +            const y = stageY - tipHeight - TOOLTIP_OFFSET;
 |  | ||||||
| +            this._tooltip.set_position(x, y);
 |  | ||||||
| +        }
 |  | ||||||
| +
 |  | ||||||
| +        Tweener.addTween(this._tooltip, {
 |  | ||||||
| +            opacity: this.hover ? 255 : 0,
 |  | ||||||
| +            time: TOOLTIP_ANIMATION_TIME / 1000,
 |  | ||||||
| +            transition: 'easeOutQuad',
 |  | ||||||
| +            onComplete: () => (this._tooltip.visible = this.hover),
 |  | ||||||
| +        });
 |  | ||||||
| +    }
 |  | ||||||
| +
 |  | ||||||
|      _onDestroy() { |  | ||||||
| +        this._tooltip.destroy();
 |  | ||||||
| +
 |  | ||||||
|          this._workspace.disconnect(this._windowAddedId); |  | ||||||
|          this._workspace.disconnect(this._windowRemovedId); |  | ||||||
|          global.display.disconnect(this._restackedId); |  | ||||||
| -- 
 |  | ||||||
| 2.44.0 |  | ||||||
| 
 |  | ||||||
| @ -1,267 +0,0 @@ | |||||||
| From bcbf9709802e7644c5911615dabdee7d8ca07719 Mon Sep 17 00:00:00 2001 |  | ||||||
| From: Carlos Garnacho <carlosg@gnome.org> |  | ||||||
| Date: Mon, 31 May 2021 19:29:34 +0200 |  | ||||||
| Subject: [PATCH 1/3] desktopManager: Handle TOUCH_UPDATE/END events explicitly |  | ||||||
|  for rubberband |  | ||||||
| 
 |  | ||||||
| These events need specific handling for Wayland, as we do not get emulated |  | ||||||
| pointer events in that platform. Handle these for rubberband selection. |  | ||||||
| ---
 |  | ||||||
|  extensions/desktop-icons/desktopManager.js | 67 ++++++++++++++++------ |  | ||||||
|  1 file changed, 48 insertions(+), 19 deletions(-) |  | ||||||
| 
 |  | ||||||
| diff --git a/extensions/desktop-icons/desktopManager.js b/extensions/desktop-icons/desktopManager.js
 |  | ||||||
| index 399aee0..a70cd98 100644
 |  | ||||||
| --- a/extensions/desktop-icons/desktopManager.js
 |  | ||||||
| +++ b/extensions/desktop-icons/desktopManager.js
 |  | ||||||
| @@ -130,26 +130,49 @@ var DesktopManager = GObject.registerClass({
 |  | ||||||
|              } |  | ||||||
|              [x, y] = event.get_coords(); |  | ||||||
|              this._updateRubberBand(x, y); |  | ||||||
| -            let x0, y0, x1, y1;
 |  | ||||||
| -            if (x >= this._rubberBandInitialX) {
 |  | ||||||
| -                x0 = this._rubberBandInitialX;
 |  | ||||||
| -                x1 = x;
 |  | ||||||
| -            } else {
 |  | ||||||
| -                x1 = this._rubberBandInitialX;
 |  | ||||||
| -                x0 = x;
 |  | ||||||
| -            }
 |  | ||||||
| -            if (y >= this._rubberBandInitialY) {
 |  | ||||||
| -                y0 = this._rubberBandInitialY;
 |  | ||||||
| -                y1 = y;
 |  | ||||||
| -            } else {
 |  | ||||||
| -                y1 = this._rubberBandInitialY;
 |  | ||||||
| -                y0 = y;
 |  | ||||||
| -            }
 |  | ||||||
| -            for (let [fileUri, fileItem] of this._fileItems) {
 |  | ||||||
| -                fileItem.emit('selected', true, true,
 |  | ||||||
| -                              fileItem.intersectsWith(x0, y0, x1 - x0, y1 - y0));
 |  | ||||||
| -            }
 |  | ||||||
| +            this._updateSelection(x, y);
 |  | ||||||
|          }); |  | ||||||
| +        this._rubberBandTouchId = global.stage.connect('touch-event', (actor, event) => {
 |  | ||||||
| +            // Let x11 pointer emulation do the job on X11
 |  | ||||||
| +            if (!Meta.is_wayland_compositor())
 |  | ||||||
| +                return Clutter.EVENT_PROPAGATE;
 |  | ||||||
| +            if (!global.display.is_pointer_emulating_sequence(event.get_event_sequence()))
 |  | ||||||
| +                return Clutter.EVENT_PROPAGATE;
 |  | ||||||
| +
 |  | ||||||
| +            if (event.type() == Clutter.EventType.TOUCH_END) {
 |  | ||||||
| +                this.endRubberBand();
 |  | ||||||
| +                return Clutter.EVENT_STOP;
 |  | ||||||
| +            } else if (event.type() == Clutter.EventType.TOUCH_UPDATE) {
 |  | ||||||
| +                [x, y] = event.get_coords();
 |  | ||||||
| +                this._updateRubberBand(x, y);
 |  | ||||||
| +                this._updateSelection(x, y);
 |  | ||||||
| +                return Clutter.EVENT_STOP;
 |  | ||||||
| +            }
 |  | ||||||
| +
 |  | ||||||
| +            return Clutter.EVENT_PROPAGATE;
 |  | ||||||
| +	});
 |  | ||||||
| +    }
 |  | ||||||
| +
 |  | ||||||
| +    _updateSelection(x, y) {
 |  | ||||||
| +        let x0, y0, x1, y1;
 |  | ||||||
| +        if (x >= this._rubberBandInitialX) {
 |  | ||||||
| +            x0 = this._rubberBandInitialX;
 |  | ||||||
| +            x1 = x;
 |  | ||||||
| +        } else {
 |  | ||||||
| +            x1 = this._rubberBandInitialX;
 |  | ||||||
| +            x0 = x;
 |  | ||||||
| +        }
 |  | ||||||
| +        if (y >= this._rubberBandInitialY) {
 |  | ||||||
| +            y0 = this._rubberBandInitialY;
 |  | ||||||
| +            y1 = y;
 |  | ||||||
| +        } else {
 |  | ||||||
| +            y1 = this._rubberBandInitialY;
 |  | ||||||
| +            y0 = y;
 |  | ||||||
| +        }
 |  | ||||||
| +        for (let [fileUri, fileItem] of this._fileItems) {
 |  | ||||||
| +            fileItem.emit('selected', true, true,
 |  | ||||||
| +                          fileItem.intersectsWith(x0, y0, x1 - x0, y1 - y0));
 |  | ||||||
| +        }
 |  | ||||||
|      } |  | ||||||
|   |  | ||||||
|      endRubberBand() { |  | ||||||
| @@ -157,8 +180,10 @@ var DesktopManager = GObject.registerClass({
 |  | ||||||
|          Extension.lockActivitiesButton = false; |  | ||||||
|          this._grabHelper.ungrab(); |  | ||||||
|          global.stage.disconnect(this._rubberBandId); |  | ||||||
| +        global.stage.disconnect(this._rubberBandTouchId);
 |  | ||||||
|          global.stage.disconnect(this._stageReleaseEventId); |  | ||||||
|          this._rubberBandId = 0; |  | ||||||
| +        this._rubberBandTouchId = 0;
 |  | ||||||
|          this._stageReleaseEventId = 0; |  | ||||||
|   |  | ||||||
|          this._selection = new Set([...this._selection, ...this._currentSelection]); |  | ||||||
| @@ -739,6 +764,10 @@ var DesktopManager = GObject.registerClass({
 |  | ||||||
|              global.stage.disconnect(this._rubberBandId); |  | ||||||
|          this._rubberBandId = 0; |  | ||||||
|   |  | ||||||
| +        if (this._rubberBandTouchId)
 |  | ||||||
| +            global.stage.disconnect(this._rubberBandTouchId);
 |  | ||||||
| +        this._rubberBandTouchId = 0;
 |  | ||||||
| +
 |  | ||||||
|          this._rubberBand.destroy(); |  | ||||||
|   |  | ||||||
|          if (this._queryFileInfoCancellable) |  | ||||||
| -- 
 |  | ||||||
| 2.31.1 |  | ||||||
| 
 |  | ||||||
| 
 |  | ||||||
| From 0733004ffeb517f7a80ff41e7181027e8b92b17e Mon Sep 17 00:00:00 2001 |  | ||||||
| From: Carlos Garnacho <carlosg@gnome.org> |  | ||||||
| Date: Mon, 31 May 2021 19:31:03 +0200 |  | ||||||
| Subject: [PATCH 2/3] desktopGrid: Handle TOUCH_BEGIN events explicitly |  | ||||||
| 
 |  | ||||||
| We do not get pointer emulated events on Wayland, so touch events should |  | ||||||
| be handled explicitly there. Handle starting rubberband selection via |  | ||||||
| touch. |  | ||||||
| ---
 |  | ||||||
|  extensions/desktop-icons/desktopGrid.js | 19 +++++++++++++++++++ |  | ||||||
|  1 file changed, 19 insertions(+) |  | ||||||
| 
 |  | ||||||
| diff --git a/extensions/desktop-icons/desktopGrid.js b/extensions/desktop-icons/desktopGrid.js
 |  | ||||||
| index 94d2dfd..602fa7f 100644
 |  | ||||||
| --- a/extensions/desktop-icons/desktopGrid.js
 |  | ||||||
| +++ b/extensions/desktop-icons/desktopGrid.js
 |  | ||||||
| @@ -21,6 +21,7 @@ const Clutter = imports.gi.Clutter;
 |  | ||||||
|  const St = imports.gi.St; |  | ||||||
|  const Gio = imports.gi.Gio; |  | ||||||
|  const GLib = imports.gi.GLib; |  | ||||||
| +const Meta = imports.gi.Meta;
 |  | ||||||
|  const Shell = imports.gi.Shell; |  | ||||||
|   |  | ||||||
|  const Signals = imports.signals; |  | ||||||
| @@ -123,6 +124,7 @@ var DesktopGrid = class {
 |  | ||||||
|              () => this._backgroundDestroyed()); |  | ||||||
|   |  | ||||||
|          this._grid.connect('button-press-event', (actor, event) => this._onPressButton(actor, event)); |  | ||||||
| +        this._grid.connect('touch-event', (actor, event) => this._onTouchEvent(actor, event));
 |  | ||||||
|   |  | ||||||
|          this._grid.connect('key-press-event', this._onKeyPress.bind(this)); |  | ||||||
|   |  | ||||||
| @@ -506,6 +508,23 @@ var DesktopGrid = class {
 |  | ||||||
|          return Clutter.EVENT_PROPAGATE; |  | ||||||
|      } |  | ||||||
|   |  | ||||||
| +    _onTouchEvent(actor, event) {
 |  | ||||||
| +        // Let x11 pointer emulation do the job on X11
 |  | ||||||
| +        if (!Meta.is_wayland_compositor())
 |  | ||||||
| +            return Clutter.EVENT_PROPAGATE;
 |  | ||||||
| +
 |  | ||||||
| +	if (event.type() == Clutter.EventType.TOUCH_BEGIN &&
 |  | ||||||
| +	    global.display.is_pointer_emulating_sequence(event.get_event_sequence())) {
 |  | ||||||
| +            Extension.desktopManager.clearSelection();
 |  | ||||||
| +            let [x, y] = event.get_coords();
 |  | ||||||
| +            let [gridX, gridY] = this._grid.get_transformed_position();
 |  | ||||||
| +            Extension.desktopManager.startRubberBand(x, y, gridX, gridY);
 |  | ||||||
| +            return Clutter.EVENT_STOP;
 |  | ||||||
| +        }
 |  | ||||||
| +
 |  | ||||||
| +        return Clutter.EVENT_PROPAGATE;
 |  | ||||||
| +    }
 |  | ||||||
| +
 |  | ||||||
|      _addDesktopBackgroundMenu() { |  | ||||||
|          this.actor._desktopBackgroundMenu = this._createDesktopBackgroundMenu(); |  | ||||||
|          this.actor._desktopBackgroundManager = new PopupMenu.PopupMenuManager({ actor: this.actor }); |  | ||||||
| -- 
 |  | ||||||
| 2.31.1 |  | ||||||
| 
 |  | ||||||
| 
 |  | ||||||
| From 2d978ffc58562c4f4d00b1afb03da58be3102e29 Mon Sep 17 00:00:00 2001 |  | ||||||
| From: Carlos Garnacho <carlosg@gnome.org> |  | ||||||
| Date: Mon, 31 May 2021 19:31:50 +0200 |  | ||||||
| Subject: [PATCH 3/3] fileItem: Handle (multi) touch explicitly via touch |  | ||||||
|  events |  | ||||||
| 
 |  | ||||||
| Wayland does not get pointer emulated events, so we must handle TOUCH_BEGIN/ |  | ||||||
| END here for file clicking/tapping to work there. |  | ||||||
| ---
 |  | ||||||
|  extensions/desktop-icons/fileItem.js | 34 ++++++++++++++++++++++++---- |  | ||||||
|  1 file changed, 30 insertions(+), 4 deletions(-) |  | ||||||
| 
 |  | ||||||
| diff --git a/extensions/desktop-icons/fileItem.js b/extensions/desktop-icons/fileItem.js
 |  | ||||||
| index 143cb9b..1cb47e8 100644
 |  | ||||||
| --- a/extensions/desktop-icons/fileItem.js
 |  | ||||||
| +++ b/extensions/desktop-icons/fileItem.js
 |  | ||||||
| @@ -117,6 +117,7 @@ var FileItem = class {
 |  | ||||||
|          this._container.connect('motion-event', (actor, event) => this._onMotion(actor, event)); |  | ||||||
|          this._container.connect('leave-event', (actor, event) => this._onLeave(actor, event)); |  | ||||||
|          this._container.connect('button-release-event', (actor, event) => this._onReleaseButton(actor, event)); |  | ||||||
| +        this._container.connect('touch-event', (actor, event) => this._onTouchEvent(actor, event));
 |  | ||||||
|   |  | ||||||
|          /* Set the metadata and update relevant UI */ |  | ||||||
|          this._updateMetadataFromFileInfo(fileInfo); |  | ||||||
| @@ -648,16 +649,26 @@ var FileItem = class {
 |  | ||||||
|          DesktopIconsUtil.launchTerminal(this.file.get_path()); |  | ||||||
|      } |  | ||||||
|   |  | ||||||
| +    _eventButton(event) {
 |  | ||||||
| +	// Emulate button1 press on touch events
 |  | ||||||
| +	if (event.type() == Clutter.EventType.TOUCH_BEGIN ||
 |  | ||||||
| +	    event.type() == Clutter.EventType.TOUCH_END ||
 |  | ||||||
| +	    event.type() == Clutter.EventType.TOUCH_UPDATE)
 |  | ||||||
| +	    return 1;
 |  | ||||||
| +
 |  | ||||||
| +	return event.get_button();
 |  | ||||||
| +    }
 |  | ||||||
| +
 |  | ||||||
|      _updateClickState(event) { |  | ||||||
|          let settings = Clutter.Settings.get_default(); |  | ||||||
| -        if ((event.get_button() == this._lastClickButton) &&
 |  | ||||||
| +        if ((this._eventButton(event) == this._lastClickButton) &&
 |  | ||||||
|              ((event.get_time() - this._lastClickTime) < settings.double_click_time)) |  | ||||||
|              this._clickCount++; |  | ||||||
|          else |  | ||||||
|              this._clickCount = 1; |  | ||||||
|   |  | ||||||
|          this._lastClickTime = event.get_time(); |  | ||||||
| -        this._lastClickButton = event.get_button();
 |  | ||||||
| +        this._lastClickButton = this._eventButton(event);
 |  | ||||||
|      } |  | ||||||
|   |  | ||||||
|      _getClickCount() { |  | ||||||
| @@ -666,7 +677,7 @@ var FileItem = class {
 |  | ||||||
|   |  | ||||||
|      _onPressButton(actor, event) { |  | ||||||
|          this._updateClickState(event); |  | ||||||
| -        let button = event.get_button();
 |  | ||||||
| +        let button = this._eventButton(event);
 |  | ||||||
|          if (button == 3) { |  | ||||||
|              if (!this.isSelected) |  | ||||||
|                  this.emit('selected', false, false, true); |  | ||||||
| @@ -725,7 +736,7 @@ var FileItem = class {
 |  | ||||||
|      } |  | ||||||
|   |  | ||||||
|      _onReleaseButton(actor, event) { |  | ||||||
| -        let button = event.get_button();
 |  | ||||||
| +        let button = this._eventButton(event);
 |  | ||||||
|          if (button == 1) { |  | ||||||
|              // primaryButtonPressed is TRUE only if the user has pressed the button |  | ||||||
|              // over an icon, and if (s)he has not started a drag&drop operation |  | ||||||
| @@ -744,6 +755,21 @@ var FileItem = class {
 |  | ||||||
|          return Clutter.EVENT_PROPAGATE; |  | ||||||
|      } |  | ||||||
|   |  | ||||||
| +    _onTouchEvent(actor, event) {
 |  | ||||||
| +        // Let x11 pointer emulation do the job on X11
 |  | ||||||
| +        if (!Meta.is_wayland_compositor())
 |  | ||||||
| +            return Clutter.EVENT_PROPAGATE;
 |  | ||||||
| +        if (!global.display.is_pointer_emulating_sequence(event.get_event_sequence()))
 |  | ||||||
| +            return Clutter.EVENT_PROPAGATE;
 |  | ||||||
| +
 |  | ||||||
| +        if (event.type() == Clutter.EventType.TOUCH_BEGIN)
 |  | ||||||
| +            this._onPressButton(actor, event);
 |  | ||||||
| +        else if (event.type() == Clutter.EventType.TOUCH_UPDATE)
 |  | ||||||
| +            this._onMotion(actor, event);
 |  | ||||||
| +        else if (event.type() == Clutter.EventType.TOUCH_END)
 |  | ||||||
| +            this._onReleaseButton(actor, event);
 |  | ||||||
| +    }
 |  | ||||||
| +
 |  | ||||||
|      get savedCoordinates() { |  | ||||||
|          return this._savedCoordinates; |  | ||||||
|      } |  | ||||||
| -- 
 |  | ||||||
| 2.31.1 |  | ||||||
| 
 |  | ||||||
| @ -1,27 +0,0 @@ | |||||||
| [Desktop Entry] |  | ||||||
| Name[de]=Klassisch (Wayland Anzeige-Server) |  | ||||||
| Name[es]=Clásico (servidor gráfico Wayland) |  | ||||||
| Name[fr]=Classic (serveur affichage Wayland) |  | ||||||
| Name[it]=Classico (server grafico Wayland) |  | ||||||
| Name[ja]=クラシック (Wayland ディスプレイサーバー) |  | ||||||
| Name[ko]=클래식 (Wayland 디스플레이 서버) |  | ||||||
| Name[pt_BR]=Clássico (servidor de exibição Wayland) |  | ||||||
| Name[ru]=Классический (дисплейный сервер Wayland) |  | ||||||
| Name[zh_CN]=经典(Wayland 显现服务器) |  | ||||||
| Name[zh_TW]=經典(Wayland顯示服務器) |  | ||||||
| Name=Classic (Wayland display server) |  | ||||||
| Comment[de]=Diese Sitzung meldet Sie in GNOME Classic an |  | ||||||
| Comment[es]=Esta sesión inicia GNOME clásico |  | ||||||
| Comment[fr]=Cette session vous connnecte à GNOME Classique |  | ||||||
| Comment[it]=Questa sessione si avvia con GNOME classico |  | ||||||
| Comment[ja]=GNOME クラシックモードでログインします |  | ||||||
| Comment[ko]=이 세션을 사용하면 그놈 클래식에 로그인합니다 |  | ||||||
| Comment[pt_BR]=Essa sessão se inicia como GNOME Clássico |  | ||||||
| Comment[ru]=Данный сеанс использует классический рабочий стол GNOME |  | ||||||
| Comment[zh_CN]=该会话将登录到“GNOME 经典模式” |  | ||||||
| Comment[zh_TW]=這個作業階段讓您登入 GNOME Classic |  | ||||||
| Comment=This session logs you into GNOME Classic |  | ||||||
| Exec=env GNOME_SHELL_SESSION_MODE=classic gnome-session --session gnome-classic |  | ||||||
| TryExec=gnome-session |  | ||||||
| Type=Application |  | ||||||
| DesktopNames=GNOME-Classic;GNOME; |  | ||||||
| @ -1,27 +0,0 @@ | |||||||
| [Desktop Entry] |  | ||||||
| Name[de]=Klassisch (X11 Anzeige-Server) |  | ||||||
| Name[es]=Clásico (servidor gráfico X11) |  | ||||||
| Name[fr]=Classic (serveur affichage X11) |  | ||||||
| Name[it]=Classico (server grafico X11) |  | ||||||
| Name[ja]=クラシック  (X11 ディスプレイサーバー) |  | ||||||
| Name[ko]=클래식 (X11 디스플레이 서버) |  | ||||||
| Name[pt_BR]=Clássico (servidor de exibição X11) |  | ||||||
| Name[ru]=Классический (дисплейный сервер X11) |  | ||||||
| Name[zh_CN]=经典(X11 显示服务器) |  | ||||||
| Name[zh_TW]=經典(X11顯示服務器) |  | ||||||
| Name=Classic (X11 display server) |  | ||||||
| Comment[de]=Diese Sitzung meldet Sie in GNOME Classic an |  | ||||||
| Comment[es]=Esta sesión inicia GNOME clásico |  | ||||||
| Comment[fr]=Cette session vous connnecte à GNOME Classique |  | ||||||
| Comment[it]=Questa sessione si avvia con GNOME classico |  | ||||||
| Comment[ja]=GNOME クラシックモードでログインします |  | ||||||
| Comment[ko]=이 세션을 사용하면 그놈 클래식에 로그인합니다 |  | ||||||
| Comment[pt_BR]=Essa sessão se inicia como GNOME Clássico |  | ||||||
| Comment[ru]=Данный сеанс использует классический рабочий стол GNOME |  | ||||||
| Comment[zh_CN]=该会话将登录到“GNOME 经典模式” |  | ||||||
| Comment[zh_TW]=這個作業階段讓您登入 GNOME Classic |  | ||||||
| Comment=This session logs you into GNOME Classic |  | ||||||
| Exec=env GNOME_SHELL_SESSION_MODE=classic gnome-session --session gnome-classic |  | ||||||
| TryExec=gnome-session |  | ||||||
| Type=Application |  | ||||||
| DesktopNames=GNOME-Classic;GNOME; |  | ||||||
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							| @ -1,126 +0,0 @@ | |||||||
| From f8ec838485ae81cf2e8ab2b899ad4154c7c06fbd Mon Sep 17 00:00:00 2001 |  | ||||||
| From: =?UTF-8?q?Florian=20M=C3=BCllner?= <fmuellner@gnome.org> |  | ||||||
| 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: <https://gitlab.gnome.org/GNOME/gnome-shell-extensions/-/merge_requests/233> |  | ||||||
| ---
 |  | ||||||
|  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 1f854aa2..fedc4195 100644
 |  | ||||||
| --- a/extensions/window-list/extension.js
 |  | ||||||
| +++ b/extensions/window-list/extension.js
 |  | ||||||
| @@ -358,7 +358,7 @@ class WindowButton extends BaseButton {
 |  | ||||||
|              return; |  | ||||||
|          } |  | ||||||
|   |  | ||||||
| -        if (button == 1)
 |  | ||||||
| +        if (!button || button === 1)
 |  | ||||||
|              _minimizeOrActivateWindow(this.metaWindow); |  | ||||||
|          else |  | ||||||
|              _openMenu(this._contextMenu); |  | ||||||
| @@ -601,7 +601,7 @@ class AppButton extends BaseButton {
 |  | ||||||
|          if (contextMenuWasOpen) |  | ||||||
|              this._contextMenu.close(); |  | ||||||
|   |  | ||||||
| -        if (button == 1) {
 |  | ||||||
| +        if (!button || button === 1) {
 |  | ||||||
|              if (menuWasOpen) |  | ||||||
|                  return; |  | ||||||
|   |  | ||||||
| -- 
 |  | ||||||
| 2.36.1 |  | ||||||
| 
 |  | ||||||
| 
 |  | ||||||
| From d3cf07f8065935736e8a79d06ec79c971c453453 Mon Sep 17 00:00:00 2001 |  | ||||||
| From: =?UTF-8?q?Florian=20M=C3=BCllner?= <fmuellner@gnome.org> |  | ||||||
| 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: <https://gitlab.gnome.org/GNOME/gnome-shell-extensions/-/merge_requests/233> |  | ||||||
| ---
 |  | ||||||
|  extensions/window-list/extension.js | 45 +++++++++++++++++++++++++++++ |  | ||||||
|  1 file changed, 45 insertions(+) |  | ||||||
| 
 |  | ||||||
| diff --git a/extensions/window-list/extension.js b/extensions/window-list/extension.js
 |  | ||||||
| index fedc4195..0baaeecb 100644
 |  | ||||||
| --- a/extensions/window-list/extension.js
 |  | ||||||
| +++ b/extensions/window-list/extension.js
 |  | ||||||
| @@ -229,6 +229,9 @@ class BaseButton {
 |  | ||||||
|          this.actor.connect('clicked', this._onClicked.bind(this)); |  | ||||||
|          this.actor.connect('destroy', this._onDestroy.bind(this)); |  | ||||||
|          this.actor.connect('popup-menu', this._onPopupMenu.bind(this)); |  | ||||||
| +        this.actor.connect('button-press-event', this._onButtonPress.bind(this));
 |  | ||||||
| +        this.actor.connect('button-release-event', this._onButtonRelease.bind(this));
 |  | ||||||
| +        this.actor.connect('touch-event', this._onTouch.bind(this));
 |  | ||||||
|   |  | ||||||
|          this._contextMenuManager = new PopupMenu.PopupMenuManager(this); |  | ||||||
|   |  | ||||||
| @@ -250,6 +253,48 @@ class BaseButton {
 |  | ||||||
|          return this.actor.has_style_class_name('focused'); |  | ||||||
|      } |  | ||||||
|   |  | ||||||
| +    _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;
 |  | ||||||
| +    }
 |  | ||||||
| +
 |  | ||||||
| +    _onButtonPress(button, event) {
 |  | ||||||
| +        if (event.get_button() === 1)
 |  | ||||||
| +            this._setLongPressTimeout();
 |  | ||||||
| +        return Clutter.EVENT_PROPAGATE;
 |  | ||||||
| +    }
 |  | ||||||
| +
 |  | ||||||
| +    _onButtonRelease() {
 |  | ||||||
| +        this._removeLongPressTimeout();
 |  | ||||||
| +        return Clutter.EVENT_PROPAGATE;
 |  | ||||||
| +    }
 |  | ||||||
| +
 |  | ||||||
| +    _onTouch(event) {
 |  | ||||||
| +        const type = event.get_type();
 |  | ||||||
| +        if (type === Clutter.EventType.TOUCH_BEGIN)
 |  | ||||||
| +            this._setLongPressTimeout();
 |  | ||||||
| +        else if (type === Clutter.EventType.TOUCH_END)
 |  | ||||||
| +            this._removeLongPressTimeout();
 |  | ||||||
| +        return Clutter.EVENT_PROPAGATE;
 |  | ||||||
| +    }
 |  | ||||||
| +
 |  | ||||||
|      activate() { |  | ||||||
|          if (this.active) |  | ||||||
|              return; |  | ||||||
| -- 
 |  | ||||||
| 2.36.1 |  | ||||||
| 
 |  | ||||||
							
								
								
									
										158
									
								
								extra-extensions-0001-Add-top-icons-extension.patch
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										158
									
								
								extra-extensions-0001-Add-top-icons-extension.patch
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,158 @@ | |||||||
|  | From 778e3f5ec9b8897af89af1919381a14e2e3494f6 Mon Sep 17 00:00:00 2001 | ||||||
|  | From: =?UTF-8?q?Florian=20M=C3=BCllner?= <fmuellner@gnome.org> | ||||||
|  | Date: Wed, 20 May 2015 17:44:50 +0200 | ||||||
|  | Subject: [PATCH 1/5] Add top-icons extension | ||||||
|  | 
 | ||||||
|  | ---
 | ||||||
|  |  extensions/top-icons/extension.js     | 91 +++++++++++++++++++++++++++ | ||||||
|  |  extensions/top-icons/meson.build      |  9 +++ | ||||||
|  |  extensions/top-icons/metadata.json.in | 10 +++ | ||||||
|  |  meson.build                           |  1 + | ||||||
|  |  4 files changed, 111 insertions(+) | ||||||
|  |  create mode 100644 extensions/top-icons/extension.js | ||||||
|  |  create mode 100644 extensions/top-icons/meson.build | ||||||
|  |  create mode 100644 extensions/top-icons/metadata.json.in | ||||||
|  | 
 | ||||||
|  | diff --git a/extensions/top-icons/extension.js b/extensions/top-icons/extension.js
 | ||||||
|  | new file mode 100644 | ||||||
|  | index 00000000..c28f1386
 | ||||||
|  | --- /dev/null
 | ||||||
|  | +++ b/extensions/top-icons/extension.js
 | ||||||
|  | @@ -0,0 +1,91 @@
 | ||||||
|  | +// SPDX-FileCopyrightText: 2018 Adel Gadllah <adel.gadllah@gmail.com>
 | ||||||
|  | +// SPDX-FileCopyrightText: 2018 Florian Müllner <fmuellner@gnome.org>
 | ||||||
|  | +//
 | ||||||
|  | +// SPDX-License-Identifier: GPL-2.0-or-later
 | ||||||
|  | +
 | ||||||
|  | +import Clutter from 'gi://Clutter';
 | ||||||
|  | +import Shell from 'gi://Shell';
 | ||||||
|  | +import St from 'gi://St';
 | ||||||
|  | +
 | ||||||
|  | +import * as Main from 'resource:///org/gnome/shell/ui/main.js';
 | ||||||
|  | +import {Button as PanelButton} from 'resource:///org/gnome/shell/ui/panelMenu.js';
 | ||||||
|  | +
 | ||||||
|  | +const PANEL_ICON_SIZE = 16;
 | ||||||
|  | +
 | ||||||
|  | +const STANDARD_TRAY_ICON_IMPLEMENTATIONS = [
 | ||||||
|  | +    'bluetooth-applet',
 | ||||||
|  | +    'gnome-sound-applet',
 | ||||||
|  | +    'nm-applet',
 | ||||||
|  | +    'gnome-power-manager',
 | ||||||
|  | +    'keyboard',
 | ||||||
|  | +    'a11y-keyboard',
 | ||||||
|  | +    'kbd-scrolllock',
 | ||||||
|  | +    'kbd-numlock',
 | ||||||
|  | +    'kbd-capslock',
 | ||||||
|  | +    'ibus-ui-gtk',
 | ||||||
|  | +];
 | ||||||
|  | +
 | ||||||
|  | +export default class SysTray {
 | ||||||
|  | +    constructor() {
 | ||||||
|  | +        this._icons = new Map();
 | ||||||
|  | +        this._tray = null;
 | ||||||
|  | +    }
 | ||||||
|  | +
 | ||||||
|  | +    _onTrayIconAdded(o, icon) {
 | ||||||
|  | +        let wmClass = icon.wm_class ? icon.wm_class.toLowerCase() : '';
 | ||||||
|  | +        if (STANDARD_TRAY_ICON_IMPLEMENTATIONS.includes(wmClass))
 | ||||||
|  | +            return;
 | ||||||
|  | +
 | ||||||
|  | +        let button = new PanelButton(0.5, null, true);
 | ||||||
|  | +
 | ||||||
|  | +        let scaleFactor = St.ThemeContext.get_for_stage(global.stage).scale_factor;
 | ||||||
|  | +        let iconSize = PANEL_ICON_SIZE * scaleFactor;
 | ||||||
|  | +
 | ||||||
|  | +        icon.set({
 | ||||||
|  | +            width: iconSize,
 | ||||||
|  | +            height: iconSize,
 | ||||||
|  | +            x_align: Clutter.ActorAlign.CENTER,
 | ||||||
|  | +            y_align: Clutter.ActorAlign.CENTER,
 | ||||||
|  | +        });
 | ||||||
|  | +
 | ||||||
|  | +        let iconBin = new St.Widget({
 | ||||||
|  | +            layout_manager: new Clutter.BinLayout(),
 | ||||||
|  | +            style_class: 'system-status-icon',
 | ||||||
|  | +        });
 | ||||||
|  | +        iconBin.add_child(icon);
 | ||||||
|  | +        button.add_child(iconBin);
 | ||||||
|  | +
 | ||||||
|  | +        this._icons.set(icon, button);
 | ||||||
|  | +
 | ||||||
|  | +        button.connect('button-release-event',
 | ||||||
|  | +            (actor, event) => icon.click(event));
 | ||||||
|  | +        button.connect('key-press-event',
 | ||||||
|  | +            (actor, event) => icon.click(event));
 | ||||||
|  | +
 | ||||||
|  | +        const role = `${icon}`;
 | ||||||
|  | +        Main.panel.addToStatusArea(role, button);
 | ||||||
|  | +    }
 | ||||||
|  | +
 | ||||||
|  | +    _onTrayIconRemoved(o, icon) {
 | ||||||
|  | +        const button = this._icons.get(icon);
 | ||||||
|  | +        button?.destroy();
 | ||||||
|  | +        this._icons.delete(icon);
 | ||||||
|  | +    }
 | ||||||
|  | +
 | ||||||
|  | +    enable() {
 | ||||||
|  | +        this._tray = new Shell.TrayManager();
 | ||||||
|  | +        this._tray.connect('tray-icon-added',
 | ||||||
|  | +            this._onTrayIconAdded.bind(this));
 | ||||||
|  | +        this._tray.connect('tray-icon-removed',
 | ||||||
|  | +            this._onTrayIconRemoved.bind(this));
 | ||||||
|  | +        this._tray.manage_screen(Main.panel);
 | ||||||
|  | +    }
 | ||||||
|  | +
 | ||||||
|  | +    disable() {
 | ||||||
|  | +        this._icons.forEach(button => button.destroy());
 | ||||||
|  | +        this._icons.clear();
 | ||||||
|  | +
 | ||||||
|  | +        this._tray.unmanage_screen();
 | ||||||
|  | +        this._tray = null;
 | ||||||
|  | +    }
 | ||||||
|  | +}
 | ||||||
|  | diff --git a/extensions/top-icons/meson.build b/extensions/top-icons/meson.build
 | ||||||
|  | new file mode 100644 | ||||||
|  | index 00000000..b30272ad
 | ||||||
|  | --- /dev/null
 | ||||||
|  | +++ b/extensions/top-icons/meson.build
 | ||||||
|  | @@ -0,0 +1,9 @@
 | ||||||
|  | +# SPDX-FileCopyrightText: 2018 Florian Müllner <fmuellner@gnome.org>
 | ||||||
|  | +#
 | ||||||
|  | +# 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/top-icons/metadata.json.in b/extensions/top-icons/metadata.json.in
 | ||||||
|  | new file mode 100644 | ||||||
|  | index 00000000..1d2e0bc2
 | ||||||
|  | --- /dev/null
 | ||||||
|  | +++ b/extensions/top-icons/metadata.json.in
 | ||||||
|  | @@ -0,0 +1,10 @@
 | ||||||
|  | +{
 | ||||||
|  | +"extension-id": "@extension_id@",
 | ||||||
|  | +"uuid": "@uuid@",
 | ||||||
|  | +"settings-schema": "@gschemaname@",
 | ||||||
|  | +"gettext-domain": "@gettext_domain@",
 | ||||||
|  | +"name": "Top Icons",
 | ||||||
|  | +"description": "Show legacy tray icons on top",
 | ||||||
|  | +"shell-version": [ "@shell_current@" ],
 | ||||||
|  | +"url": "@url@"
 | ||||||
|  | +}
 | ||||||
|  | diff --git a/meson.build b/meson.build
 | ||||||
|  | index 536efe49..a7294c18 100644
 | ||||||
|  | --- a/meson.build
 | ||||||
|  | +++ b/meson.build
 | ||||||
|  | @@ -43,6 +43,7 @@ default_extensions += [
 | ||||||
|  |    'light-style', | ||||||
|  |    'screenshot-window-sizer', | ||||||
|  |    'system-monitor', | ||||||
|  | +  'top-icons',
 | ||||||
|  |    'windowsNavigator', | ||||||
|  |    'workspace-indicator' | ||||||
|  |  ] | ||||||
|  | -- 
 | ||||||
|  | 2.45.2 | ||||||
|  | 
 | ||||||
							
								
								
									
										181
									
								
								extra-extensions-0002-Add-gesture-inhibitor-extension.patch
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										181
									
								
								extra-extensions-0002-Add-gesture-inhibitor-extension.patch
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,181 @@ | |||||||
|  | From ff5063cb006a3723f422017d44787cfd908bb147 Mon Sep 17 00:00:00 2001 | ||||||
|  | From: Carlos Garnacho <carlosg@gnome.org> | ||||||
|  | Date: Thu, 28 Jan 2021 00:06:12 +0100 | ||||||
|  | Subject: [PATCH 2/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 <carlosg@gnome.org>
 | ||||||
|  | +//
 | ||||||
|  | +// 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 @@
 | ||||||
|  | +<schemalist>
 | ||||||
|  | +  <schema id="org.gnome.shell.extensions.gesture-inhibitor" path="/org/gnome/shell/extensions/gesture-inhibitor/">
 | ||||||
|  | +    <key name="show-osk" type="b">
 | ||||||
|  | +      <default>true</default>
 | ||||||
|  | +      <summary>Show OSK gesture</summary>
 | ||||||
|  | +    </key>
 | ||||||
|  | +    <key name="overview" type="b">
 | ||||||
|  | +      <default>true</default>
 | ||||||
|  | +      <summary>Show Overview gesture</summary>
 | ||||||
|  | +    </key>
 | ||||||
|  | +    <key name="app-switch" type="b">
 | ||||||
|  | +      <default>true</default>
 | ||||||
|  | +      <summary>Application switch gesture</summary>
 | ||||||
|  | +    </key>
 | ||||||
|  | +    <key name="workspace-switch" type="b">
 | ||||||
|  | +      <default>true</default>
 | ||||||
|  | +      <summary>Workspace switch gesture</summary>
 | ||||||
|  | +    </key>
 | ||||||
|  | +    <key name="unfullscreen" type="b">
 | ||||||
|  | +      <default>true</default>
 | ||||||
|  | +      <summary>Unfullscreen gesture</summary>
 | ||||||
|  | +    </key>
 | ||||||
|  | +  </schema>
 | ||||||
|  | +</schemalist>
 | ||||||
|  | +
 | ||||||
|  | diff --git a/meson.build b/meson.build
 | ||||||
|  | index a7294c18..e36d948d 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.45.2 | ||||||
|  | 
 | ||||||
							
								
								
									
										479
									
								
								extra-extensions-0003-Add-classification-banner.patch
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										479
									
								
								extra-extensions-0003-Add-classification-banner.patch
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,479 @@ | |||||||
|  | From 950f0fded26d8664bce5410db4c280674147cdd8 Mon Sep 17 00:00:00 2001 | ||||||
|  | From: =?UTF-8?q?Florian=20M=C3=BCllner?= <fmuellner@gnome.org> | ||||||
|  | Date: Thu, 2 Dec 2021 19:39:50 +0100 | ||||||
|  | Subject: [PATCH] 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 <fmuellner@gnome.org>
 | ||||||
|  | +// 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 @@
 | ||||||
|  | +<schemalist gettext-domain="gnome-shell-extensions">
 | ||||||
|  | +  <schema id="org.gnome.shell.extensions.classification-banner"
 | ||||||
|  | +          path="/org/gnome/shell/extensions/classification-banner/">
 | ||||||
|  | +    <key name="top-banner" type="b">
 | ||||||
|  | +      <default>true</default>
 | ||||||
|  | +      <summary>Show a banner at the top</summary>
 | ||||||
|  | +    </key>
 | ||||||
|  | +    <key name="bottom-banner" type="b">
 | ||||||
|  | +      <default>true</default>
 | ||||||
|  | +      <summary>Show a banner at the bottom</summary>
 | ||||||
|  | +    </key>
 | ||||||
|  | +    <key name="message" type="s">
 | ||||||
|  | +      <default>"UNCLASSIFIED"</default>
 | ||||||
|  | +      <summary>classification message</summary>
 | ||||||
|  | +    </key>
 | ||||||
|  | +    <key name="color" type="s">
 | ||||||
|  | +      <default>"#fff"</default>
 | ||||||
|  | +      <summary>text color</summary>
 | ||||||
|  | +    </key>
 | ||||||
|  | +    <key name="background-color" type="s">
 | ||||||
|  | +      <default>"rgba(0,122,51,0.75)"</default>
 | ||||||
|  | +      <summary>background color</summary>
 | ||||||
|  | +    </key>
 | ||||||
|  | +    <key name="system-info" type="b">
 | ||||||
|  | +      <default>false</default>
 | ||||||
|  | +      <summary>Include system info in top banner</summary>
 | ||||||
|  | +    </key>
 | ||||||
|  | +  </schema>
 | ||||||
|  | +</schemalist>
 | ||||||
|  | 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 e36d948d..63bd9ee0 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.45.2 | ||||||
|  | 
 | ||||||
							
								
								
									
										878
									
								
								extra-extensions-0004-Add-heads-up-display.patch
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										878
									
								
								extra-extensions-0004-Add-heads-up-display.patch
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,878 @@ | |||||||
|  | From 271cee0eab89dcbf7b6c307c55cfbbbf843e7a0e Mon Sep 17 00:00:00 2001 | ||||||
|  | From: Ray Strode <rstrode@redhat.com> | ||||||
|  | Date: Tue, 24 Aug 2021 15:03:57 -0400 | ||||||
|  | Subject: [PATCH 4/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 <rstrode@redhat.com>
 | ||||||
|  | +//
 | ||||||
|  | +// 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 <rstrode@redhat.com>
 | ||||||
|  | +//
 | ||||||
|  | +// 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 <rstrode@redhat.com>
 | ||||||
|  | +#
 | ||||||
|  | +# 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 @@
 | ||||||
|  | +<!--
 | ||||||
|  | +SPDX-FileCopyrightText: 2021 Ray Strode <rstrode@redhat.com>
 | ||||||
|  | +
 | ||||||
|  | +SPDX-License-Identifier: GPL-2.0-or-later
 | ||||||
|  | +-->
 | ||||||
|  | +
 | ||||||
|  | +<schemalist gettext-domain="gnome-shell-extensions">
 | ||||||
|  | +  <schema id="org.gnome.shell.extensions.heads-up-display"
 | ||||||
|  | +          path="/org/gnome/shell/extensions/heads-up-display/">
 | ||||||
|  | +    <key name="idle-timeout" type="u">
 | ||||||
|  | +      <default>30</default>
 | ||||||
|  | +      <summary>Idle Timeout</summary>
 | ||||||
|  | +      <description>
 | ||||||
|  | +        Number of seconds until message is reshown after user goes idle.
 | ||||||
|  | +      </description>
 | ||||||
|  | +    </key>
 | ||||||
|  | +    <key name="message-heading" type="s">
 | ||||||
|  | +      <default>""</default>
 | ||||||
|  | +      <summary>Message to show at top of display</summary>
 | ||||||
|  | +      <description>
 | ||||||
|  | +        The top line of the heads up display message.
 | ||||||
|  | +      </description>
 | ||||||
|  | +    </key>
 | ||||||
|  | +    <key name="message-body" type="s">
 | ||||||
|  | +      <default>""</default>
 | ||||||
|  | +      <summary>Banner message</summary>
 | ||||||
|  | +      <description>
 | ||||||
|  | +        A message to always show at the top of the screen.
 | ||||||
|  | +      </description>
 | ||||||
|  | +    </key>
 | ||||||
|  | +    <key name="show-on-login-screen" type="b">
 | ||||||
|  | +      <default>true</default>
 | ||||||
|  | +      <summary>Show on login screen</summary>
 | ||||||
|  | +      <description>
 | ||||||
|  | +        Whether or not the message should display on the login screen
 | ||||||
|  | +      </description>
 | ||||||
|  | +    </key>
 | ||||||
|  | +    <key name="show-when-locked" type="b">
 | ||||||
|  | +      <default>false</default>
 | ||||||
|  | +      <summary>Show on screen shield</summary>
 | ||||||
|  | +      <description>
 | ||||||
|  | +        Whether or not the message should display when the screen is locked
 | ||||||
|  | +      </description>
 | ||||||
|  | +    </key>
 | ||||||
|  | +    <key name="show-when-unlocking" type="b">
 | ||||||
|  | +      <default>false</default>
 | ||||||
|  | +      <summary>Show on unlock screen</summary>
 | ||||||
|  | +      <description>
 | ||||||
|  | +        Whether or not the message should display on the unlock screen.
 | ||||||
|  | +      </description>
 | ||||||
|  | +    </key>
 | ||||||
|  | +    <key name="show-when-unlocked" type="b">
 | ||||||
|  | +      <default>false</default>
 | ||||||
|  | +      <summary>Show in user session</summary>
 | ||||||
|  | +      <description>
 | ||||||
|  | +        Whether or not the message should display when the screen is unlocked.
 | ||||||
|  | +      </description>
 | ||||||
|  | +    </key>
 | ||||||
|  | +  </schema>
 | ||||||
|  | +</schemalist>
 | ||||||
|  | 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 <rstrode@redhat.com>
 | ||||||
|  | +//
 | ||||||
|  | +// 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 <rstrode@redhat.com>
 | ||||||
|  | + *
 | ||||||
|  | + * 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 63bd9ee0..82269ff5 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', | ||||||
|  |    'system-monitor', | ||||||
|  | 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.45.2 | ||||||
|  | 
 | ||||||
							
								
								
									
										719
									
								
								extra-extensions-0005-Add-custom-menu-extension.patch
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										719
									
								
								extra-extensions-0005-Add-custom-menu-extension.patch
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,719 @@ | |||||||
|  | From b146a94c18e9e9ddbc7f29e8d885768d19fd7d49 Mon Sep 17 00:00:00 2001 | ||||||
|  | From: =?UTF-8?q?Florian=20M=C3=BCllner?= <fmuellner@gnome.org> | ||||||
|  | Date: Thu, 12 Jan 2023 19:43:52 +0100 | ||||||
|  | Subject: [PATCH 5/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 <http://www.gnu.org/licenses/>.
 | ||||||
|  | + *
 | ||||||
|  | + * 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 82269ff5..dce1731c 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.45.2 | ||||||
|  | 
 | ||||||
							
								
								
									
										72
									
								
								extra-extensions-0006-Add-desktop-icons-extension.patch
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										72
									
								
								extra-extensions-0006-Add-desktop-icons-extension.patch
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,72 @@ | |||||||
|  | From d0f2273765ab61e55c5cf10e7283a545fcafa947 Mon Sep 17 00:00:00 2001 | ||||||
|  | From: =?UTF-8?q?Florian=20M=C3=BCllner?= <fmuellner@gnome.org> | ||||||
|  | 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 <fmuellner@gnome.org>
 | ||||||
|  | +//
 | ||||||
|  | +// 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 <fmuellner@gnome.org>
 | ||||||
|  | +#
 | ||||||
|  | +# 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 | ||||||
|  | 
 | ||||||
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							
		Loading…
	
		Reference in New Issue
	
	Block a user