diff --git a/SOURCES/0001-classification-banner-Hide-from-picks.patch b/SOURCES/0001-classification-banner-Hide-from-picks.patch new file mode 100644 index 0000000..60d87d8 --- /dev/null +++ b/SOURCES/0001-classification-banner-Hide-from-picks.patch @@ -0,0 +1,39 @@ +From b9ba6b8708c18fb14033150fdb02a508457e0a17 Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Florian=20M=C3=BCllner?= +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 + diff --git a/SOURCES/0001-desktop-icons-Don-t-try-spawn-with-non-existent-work.patch b/SOURCES/0001-desktop-icons-Don-t-try-spawn-with-non-existent-work.patch new file mode 100644 index 0000000..3e7c5b3 --- /dev/null +++ b/SOURCES/0001-desktop-icons-Don-t-try-spawn-with-non-existent-work.patch @@ -0,0 +1,33 @@ +From 0f13a5e00d9115b4d1518c91aa45c88f3e7e7c27 Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Florian=20M=C3=BCllner?= +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 + diff --git a/SOURCES/0001-panel-favorites-Update-to-upstream-version.patch b/SOURCES/0001-panel-favorites-Update-to-upstream-version.patch new file mode 100644 index 0000000..2b11cbc --- /dev/null +++ b/SOURCES/0001-panel-favorites-Update-to-upstream-version.patch @@ -0,0 +1,1086 @@ +From f954feefa637e3cd6f92450076c0ef0941fd6763 Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Florian=20M=C3=BCllner?= +Date: Tue, 12 Sep 2023 19:29:22 +0200 +Subject: [PATCH] panel-favorites: Update to upstream version + +--- + extensions/panel-favorites/extension.js | 559 ++++++++++++++---- + extensions/panel-favorites/meson.build | 6 + + extensions/panel-favorites/prefs.js | 293 +++++++++ + ...ell.extensions.panel-favorites.gschema.xml | 41 ++ + extensions/panel-favorites/stylesheet.css | 8 +- + 5 files changed, 797 insertions(+), 110 deletions(-) + create mode 100644 extensions/panel-favorites/prefs.js + create mode 100644 extensions/panel-favorites/schemas/org.gnome.shell.extensions.panel-favorites.gschema.xml + +diff --git a/extensions/panel-favorites/extension.js b/extensions/panel-favorites/extension.js +index b817dbb6..5495dc3f 100644 +--- a/extensions/panel-favorites/extension.js ++++ b/extensions/panel-favorites/extension.js +@@ -1,32 +1,38 @@ +-// Copyright (C) 2011-2013 R M Yorston ++// Copyright (C) 2011-2020 R M Yorston + // Licence: GPLv2+ + +-const Clutter = imports.gi.Clutter; +-const Gio = imports.gi.Gio; +-const GLib = imports.gi.GLib; +-const Lang = imports.lang; +-const Shell = imports.gi.Shell; ++const { Clutter, Gio, GLib, GObject, Shell, St } = imports.gi; + const Signals = imports.signals; +-const St = imports.gi.St; +-const Mainloop = imports.mainloop; + + const AppFavorites = imports.ui.appFavorites; + const Main = imports.ui.main; + const Panel = imports.ui.panel; ++const PanelMenu = imports.ui.panelMenu; ++const PopupMenu = imports.ui.popupMenu; + const Tweener = imports.ui.tweener; + ++const ExtensionUtils = imports.misc.extensionUtils; ++ ++const _f = imports.gettext.domain('frippery-panel-favorites').gettext; ++ + const PANEL_LAUNCHER_LABEL_SHOW_TIME = 0.15; + const PANEL_LAUNCHER_LABEL_HIDE_TIME = 0.1; + const PANEL_LAUNCHER_HOVER_TIMEOUT = 300; + +-const PanelLauncher = new Lang.Class({ +- Name: 'PanelLauncher', ++const SETTINGS_FAVORITES_ENABLED = 'favorites-enabled'; ++const SETTINGS_FAVORITES_POSITION = 'favorites-position'; ++const SETTINGS_OTHER_APPS_ENABLED = 'other-apps-enabled'; ++const SETTINGS_OTHER_APPS_POSITION = 'other-apps-position'; ++const SETTINGS_OTHER_APPS = 'other-apps'; + +- _init: function(app) { ++const PanelLauncher = ++class PanelLauncher { ++ constructor(app) { + this.actor = new St.Button({ style_class: 'panel-button', + reactive: true }); +- this.iconSize = 24; +- let icon = app.create_icon_texture(this.iconSize); ++ let gicon = app.app_info.get_icon(); ++ let icon = new St.Icon({ gicon: gicon, ++ style_class: 'panel-launcher-icon'}); + this.actor.set_child(icon); + this.actor._delegate = this; + let text = app.get_name(); +@@ -41,31 +47,66 @@ const PanelLauncher = new Lang.Class({ + this.actor.label_actor = this.label; + + this._app = app; +- this.actor.connect('clicked', Lang.bind(this, function() { ++ this._menu = null; ++ this._menuManager = new PopupMenu.PopupMenuManager(this); ++ ++ this.actor.connect('clicked', () => { + this._app.open_new_window(-1); +- })); ++ if ( Main.overview.visible ) { ++ Main.overview.hide(); ++ } ++ }); + this.actor.connect('notify::hover', +- Lang.bind(this, this._onHoverChanged)); ++ this._onHoverChanged.bind(this)); ++ this.actor.connect('button-press-event', ++ this._onButtonPress.bind(this)); + this.actor.opacity = 207; ++ } + +- this.actor.connect('notify::allocation', Lang.bind(this, this._alloc)); +- }, +- +- _onHoverChanged: function(actor) { ++ _onHoverChanged(actor) { + actor.opacity = actor.hover ? 255 : 207; +- }, +- +- _alloc: function() { +- let size = this.actor.allocation.y2 - this.actor.allocation.y1 - 3; +- if ( size >= 24 && size != this.iconSize ) { +- this.actor.get_child().destroy(); +- this.iconSize = size; +- let icon = this._app.create_icon_texture(this.iconSize); +- this.actor.set_child(icon); ++ } ++ ++ _onButtonPress(actor, event) { ++ let button = event.get_button(); ++ if (button == 3) { ++ this.popupMenu(); ++ return Clutter.EVENT_STOP; ++ } ++ return Clutter.EVENT_PROPAGATE; ++ } ++ ++ // this code stolen from appDisplay.js ++ popupMenu() { ++ if (!this._menu) { ++ this._menu = new AppIconMenu(this); ++ this._menu.connect('activate-window', (menu, window) => { ++ this.activateWindow(window); ++ }); ++ this._menu.connect('open-state-changed', (menu, isPoppedUp) => { ++ if (!isPoppedUp) ++ this.actor.sync_hover(); ++ }); ++ ++ this._menuManager.addMenu(this._menu); + } +- }, + +- showLabel: function() { ++ this.actor.set_hover(true); ++ this._menu.popup(); ++ this._menuManager.ignoreRelease(); ++ ++ return false; ++ } ++ ++ activateWindow(metaWindow) { ++ if (metaWindow) { ++ Main.activateWindow(metaWindow); ++ } else { ++ Main.overview.hide(); ++ } ++ } ++ ++ showLabel() { + this.label.opacity = 0; + this.label.show(); + +@@ -101,58 +142,105 @@ const PanelLauncher = new Lang.Class({ + time: PANEL_LAUNCHER_LABEL_SHOW_TIME, + transition: 'easeOutQuad', + }); +- }, ++ } + +- hideLabel: function() { ++ hideLabel() { + this.label.opacity = 255; + Tweener.addTween(this.label, + { opacity: 0, + time: PANEL_LAUNCHER_LABEL_HIDE_TIME, + transition: 'easeOutQuad', +- onComplete: Lang.bind(this, function() { +- this.label.hide(); +- }) ++ onComplete: () => this.label.hide() + }); +- }, ++ } + +- destroy: function() { ++ destroy() { + this.label.destroy(); + this.actor.destroy(); + } +-}); ++}; ++ ++const ApplicationMenuItem = ++class ApplicationMenuItem extends PopupMenu.PopupBaseMenuItem { ++ constructor(app, params) { ++ super(params); ++ ++ let box = new St.BoxLayout({ name: 'applicationMenuBox', ++ style_class: 'applications-menu-item-box'}); ++ this.actor.add_child(box); ++ ++ let icon = app.create_icon_texture(24); ++ box.add(icon, { x_fill: false, y_fill: false }); ++ ++ let name = app.get_name(); ++ ++ let matches = /^(OpenJDK Policy Tool) (.*)/.exec(name); ++ if ( matches && matches.length == 3 ) { ++ name = matches[1] + '\n' + matches[2]; ++ } + +-const PanelFavorites = new Lang.Class({ +- Name: 'PanelFavorites', ++ matches = /^(OpenJDK 8 Policy Tool) (.*)/.exec(name); ++ if ( matches && matches.length == 3 ) { ++ name = matches[1] + '\n' + matches[2]; ++ } ++ ++ matches = /^(OpenJDK Monitoring & Management Console) (.*)/.exec(name); ++ if ( matches && matches.length == 3 ) { ++ name = 'OpenJDK Console\n' + matches[2]; ++ } ++ ++ matches = /^(OpenJDK 8 Monitoring & Management Console) (.*)/.exec(name); ++ if ( matches && matches.length == 3 ) { ++ name = 'OpenJDK 8 Console\n' + matches[2]; ++ } ++ ++ let label = new St.Label({ text: name }); ++ box.add(label); ++ ++ this.app = app; + +- _init: function() { ++ this.connect('activate', () => { ++ let id = this.app.get_id(); ++ let app = Shell.AppSystem.get_default().lookup_app(id); ++ app.open_new_window(-1); ++ }); ++ } ++}; ++ ++const PanelAppsButton = GObject.registerClass( ++class PanelAppsButton extends PanelMenu.Button { ++ _init(details) { ++ super._init(0.5, details.description, false); + this._showLabelTimeoutId = 0; + this._resetHoverTimeoutId = 0; + this._labelShowing = false; + +- this.actor = new St.BoxLayout({ name: 'panelFavorites', ++ this.name = details.name; ++ this._details = details; ++ ++ this._box = new St.BoxLayout({ name: 'panelFavoritesBox', + x_expand: true, y_expand: true, + style_class: 'panel-favorites' }); +- this._display(); ++ this.add_actor(this._box); + +- this.container = new St.Bin({ y_fill: true, +- x_fill: true, +- child: this.actor }); ++ this.connect('destroy', this._onDestroy.bind(this)); ++ this._installChangedId = Shell.AppSystem.get_default().connect('installed-changed', this._redisplay.bind(this)); ++ this._changedId = details.change_object.connect(details.change_event, this._redisplay.bind(this)); + +- this.actor.connect('destroy', Lang.bind(this, this._onDestroy)); +- this._installChangedId = Shell.AppSystem.get_default().connect('installed-changed', Lang.bind(this, this._redisplay)); +- this._changedId = AppFavorites.getAppFavorites().connect('changed', Lang.bind(this, this._redisplay)); +- }, ++ this._display(); ++ } + +- _redisplay: function() { ++ _redisplay() { + for ( let i=0; i this._onHover(launcher)); + this._buttons[j] = launcher; ++ ++ let menuItem = new ApplicationMenuItem(app); ++ this.menu.addMenuItem(menuItem, j); + ++j; + } +- }, ++ } + + // this routine stolen from dash.js +- _onHover: function(launcher) { ++ _onHover(launcher) { + if ( launcher.actor.hover ) { + if (this._showLabelTimeoutId == 0) { + let timeout = this._labelShowing ? + 0 : PANEL_LAUNCHER_HOVER_TIMEOUT; +- this._showLabelTimeoutId = Mainloop.timeout_add(timeout, +- Lang.bind(this, function() { ++ this._showLabelTimeoutId = GLib.timeout_add( ++ GLib.PRIORITY_DEFAULT, timeout, ++ () => { + this._labelShowing = true; + launcher.showLabel(); + this._showLabelTimeoutId = 0; + return GLib.SOURCE_REMOVE; +- })); ++ }); + if (this._resetHoverTimeoutId > 0) { +- Mainloop.source_remove(this._resetHoverTimeoutId); ++ GLib.source_remove(this._resetHoverTimeoutId); + this._resetHoverTimeoutId = 0; + } + } + } else { + if (this._showLabelTimeoutId > 0) { +- Mainloop.source_remove(this._showLabelTimeoutId); ++ GLib.source_remove(this._showLabelTimeoutId); + this._showLabelTimeoutId = 0; + } + launcher.hideLabel(); + if (this._labelShowing) { +- this._resetHoverTimeoutId = Mainloop.timeout_add( +- PANEL_LAUNCHER_HOVER_TIMEOUT, +- Lang.bind(this, function() { ++ this._resetHoverTimeoutId = GLib.timeout_add( ++ GLib.PRIORITY_DEFAULT, PANEL_LAUNCHER_HOVER_TIMEOUT, ++ () => { + this._labelShowing = false; + this._resetHoverTimeoutId = 0; + return GLib.SOURCE_REMOVE; +- })); ++ }); + } + } +- }, ++ } + +- _onDestroy: function() { ++ _onDestroy() { + if ( this._installChangedId != 0 ) { + Shell.AppSystem.get_default().disconnect(this._installChangedId); + this._installChangedId = 0; + } + + if ( this._changedId != 0 ) { +- AppFavorites.getAppFavorites().disconnect(this._changedId); ++ this._details.change_object.disconnect(this._changedId); + this._changedId = 0; + } + } + }); +-Signals.addSignalMethods(PanelFavorites.prototype); + +-let myAddToStatusArea; +-let panelFavorites; ++// this code stolen from appDisplay.js ++const AppIconMenu = ++class AppIconMenu extends PopupMenu.PopupMenu { ++ constructor(source) { ++ super(source.actor, 0.5, St.Side.TOP); + +-function enable() { +- Panel.Panel.prototype.myAddToStatusArea = myAddToStatusArea; ++ // We want to keep the item hovered while the menu is up ++ this.blockSourceEvents = true; + +- // place panel to left of app menu, or failing that at right end of box +- let siblings = Main.panel._leftBox.get_children(); +- let appMenu = Main.panel.statusArea['appMenu']; +- let pos = appMenu ? siblings.indexOf(appMenu.container) : siblings.length; ++ this._source = source; + +- panelFavorites = new PanelFavorites(); +- Main.panel.myAddToStatusArea('panel-favorites', panelFavorites, +- pos, 'left'); +-} ++ this.actor.add_style_class_name('panel-menu'); + +-function disable() { +- delete Panel.Panel.prototype.myAddToStatusArea; ++ // Chain our visibility and lifecycle to that of the source ++ this._sourceMappedId = source.actor.connect('notify::mapped', () => { ++ if (!source.actor.mapped) ++ this.close(); ++ }); ++ source.actor.connect('destroy', () => { ++ source.actor.disconnect(this._sourceMappedId); ++ this.destroy(); ++ }); + +- panelFavorites.actor.destroy(); +- panelFavorites.emit('destroy'); +- panelFavorites = null; +-} ++ Main.uiGroup.add_actor(this.actor); ++ } ++ ++ _redisplay() { ++ this.removeAll(); ++ ++ // find windows on current and other workspaces ++ let activeWorkspace = global.workspace_manager.get_active_workspace(); ++ ++ let w_here = this._source._app.get_windows().filter(function(w) { ++ return !w.skip_taskbar && w.get_workspace() == activeWorkspace; ++ }); ++ ++ let w_there = this._source._app.get_windows().filter(function(w) { ++ return !w.skip_taskbar && w.get_workspace() != activeWorkspace; ++ }); ++ ++ // if we have lots of windows use submenus in both cases to ++ // avoid confusion ++ let use_submenu = w_here.length + w_there.length > 10; ++ ++ this._appendWindows(use_submenu, _f('This Workspace'), w_here); ++ ++ if (w_here.length && !use_submenu) { ++ this._appendSeparator(); ++ } ++ ++ this._appendWindows(use_submenu, _f('Other Workspaces'), w_there); ++ ++ if (!this._source._app.is_window_backed()) { ++ if (w_there.length && !use_submenu) { ++ this._appendSeparator(); ++ } ++ ++ let appInfo = this._source._app.get_app_info(); ++ let actions = appInfo.list_actions(); ++ if (this._source._app.can_open_new_window() && ++ actions.indexOf('new-window') == -1) { ++ let item = this._appendMenuItem(_('New Window')); ++ item.connect('activate', () => { ++ this._source._app.open_new_window(-1); ++ this.emit('activate-window', null); ++ }); ++ } ++ ++ for (let i = 0; i < actions.length; i++) { ++ let action = actions[i]; ++ let item = this._appendMenuItem(appInfo.get_action_name(action)); ++ item.connect('activate', (emitter, event) => { ++ this._source._app.launch_action(action, event.get_time(), -1); ++ this.emit('activate-window', null); ++ }); ++ } ++ ++ let canFavorite = global.settings.is_writable('favorite-apps'); ++ ++ if (canFavorite) { ++ let isFavorite = AppFavorites.getAppFavorites().isFavorite(this._source._app.get_id()); ++ ++ if (isFavorite) { ++ let item = this._appendMenuItem(_('Remove from Favorites')); ++ item.connect('activate', () => { ++ let favs = AppFavorites.getAppFavorites(); ++ favs.removeFavorite(this._source._app.get_id()); ++ }); ++ } else { ++ let item = this._appendMenuItem(_('Add to Favorites')); ++ item.connect('activate', () => { ++ let favs = AppFavorites.getAppFavorites(); ++ favs.addFavorite(this._source.app.get_id()); ++ }); ++ } ++ } ++ ++ if (Shell.AppSystem.get_default().lookup_app('org.gnome.Software.desktop')) { ++ let item = this._appendMenuItem(_('Show Details')); ++ item.connect('activate', () => { ++ let id = this._source._app.get_id(); ++ let args = GLib.Variant.new('(ss)', [id, '']); ++ Gio.DBus.get(Gio.BusType.SESSION, null, (o, res) => { ++ let bus = Gio.DBus.get_finish(res); ++ bus.call('org.gnome.Software', ++ '/org/gnome/Software', ++ 'org.gtk.Actions', 'Activate', ++ GLib.Variant.new('(sava{sv})', ++ ['details', [args], null]), ++ null, 0, -1, null, null); ++ Main.overview.hide(); ++ }); ++ }); ++ } ++ } ++ } ++ ++ _appendWindows(use_submenu, text, windows) { ++ let parent = this; ++ if (windows.length && use_submenu) { ++ // if we have lots of activatable windows create a submenu ++ let item = new PopupMenu.PopupSubMenuMenuItem(text); ++ this.addMenuItem(item); ++ parent = item.menu; ++ } ++ for (let i = 0; i < windows.length; i++) { ++ let window = windows[i]; ++ let item = new PopupMenu.PopupMenuItem(window.title); ++ parent.addMenuItem(item); ++ item.connect('activate', () => ++ this.emit('activate-window', window)); ++ } ++ } ++ ++ _appendSeparator() { ++ let separator = new PopupMenu.PopupSeparatorMenuItem(); ++ this.addMenuItem(separator); ++ } ++ ++ _appendMenuItem(labelText) { ++ let item = new PopupMenu.PopupMenuItem(labelText); ++ this.addMenuItem(item); ++ return item; ++ } ++ ++ popup(activatingButton) { ++ // this code stolen from PanelMenuButton ++ // limit height of menu: the menu should have scrollable submenus ++ // for this to make sense ++ let workArea = Main.layoutManager.getWorkAreaForMonitor( ++ Main.layoutManager.primaryIndex); ++ let verticalMargins = this.actor.margin_top + this.actor.margin_bottom; ++ this.actor.style = ('max-height: ' + Math.round(workArea.height - ++ verticalMargins) + 'px;'); ++ ++ this._source.label.hide(); ++ this._redisplay(); ++ this.open(); ++ } ++}; ++Signals.addSignalMethods(AppIconMenu.prototype); ++ ++const FAVORITES = 0; ++const OTHER_APPS = 1; ++ ++const PanelFavoritesExtension = ++class PanelFavoritesExtension { ++ constructor() { ++ ExtensionUtils.initTranslations(); ++ this._panelAppsButton = [ null, null ]; ++ this._settings = ExtensionUtils.getSettings(); ++ } ++ ++ _getPosition(key) { ++ let position, box; ++ // if key is false use left box, if true use right ++ if (!this._settings.get_boolean(key)) { ++ // place panel to left of app menu ++ let siblings = Main.panel._leftBox.get_children(); ++ let appMenu = Main.panel.statusArea['appMenu']; ++ position = appMenu ? ++ siblings.indexOf(appMenu.container) : siblings.length; ++ box = 'left'; ++ } ++ else { ++ // place panel to left of aggregate menu ++ let siblings = Main.panel._rightBox.get_children(); ++ let aggMenu = Main.panel.statusArea['aggregateMenu']; ++ position = aggMenu ? ++ siblings.indexOf(aggMenu.container) : siblings.length-1; ++ box = 'right'; ++ } ++ return [position, box]; ++ } ++ ++ _configureButtons() { ++ let details = [ ++ { ++ description: _f('Favorites'), ++ name: 'panelFavorites', ++ settings: global.settings, ++ key: AppFavorites.getAppFavorites().FAVORITE_APPS_KEY, ++ change_object: AppFavorites.getAppFavorites(), ++ change_event: 'changed' ++ }, ++ { ++ description: _f('Other Applications'), ++ name: 'panelOtherApps', ++ settings: ExtensionUtils.getSettings(), ++ key: SETTINGS_OTHER_APPS, ++ change_object: ExtensionUtils.getSettings(), ++ change_event: 'changed::' + SETTINGS_OTHER_APPS ++ } ++ ]; ++ let role = [ 'panel-favorites', 'panel-other-apps' ]; ++ let prefix = [ 'favorites-', 'other-apps-' ]; ++ ++ for ( let i=0; i= names.length) { ++ // fill with blanks ++ for (let i = names.length; i <= index; i++) ++ names[i] = ''; ++ } ++ ++ names[index] = this.get_value(iter, this.Columns.LABEL); ++ ++ this._settings.set_strv(SETTINGS_OTHER_APPS, names); ++ ++ this._preventChanges = false; ++ } ++ ++ _onRowInserted(self, path, iter) { ++ if (this._preventChanges) ++ return; ++ this._preventChanges = true; ++ ++ let index = path.get_indices()[0]; ++ let names = this._settings.get_strv(SETTINGS_OTHER_APPS); ++ let label = this.get_value(iter, this.Columns.LABEL) || ''; ++ names.splice(index, 0, label); ++ ++ this._settings.set_strv(SETTINGS_OTHER_APPS, names); ++ ++ this._preventChanges = false; ++ } ++ ++ _onRowDeleted(self, path) { ++ if (this._preventChanges) ++ return; ++ this._preventChanges = true; ++ ++ let index = path.get_indices()[0]; ++ let names = this._settings.get_strv(SETTINGS_OTHER_APPS); ++ ++ if (index >= names.length) ++ return; ++ ++ names.splice(index, 1); ++ ++ // compact the array ++ for (let i = names.length -1; i >= 0 && !names[i]; i++) ++ names.pop(); ++ ++ this._settings.set_strv(SETTINGS_OTHER_APPS, names); ++ ++ this._preventChanges = false; ++ } ++}); ++ ++const PanelFavoritesSettingsWidget = GObject.registerClass( ++class PanelFavoritesSettingsWidget extends Gtk.Grid { ++ _init(params) { ++ super._init(params); ++ this.margin = 12; ++ this.orientation = Gtk.Orientation.VERTICAL; ++ this._settings = ExtensionUtils.getSettings(); ++ ++ this.add(new Gtk.Label({ ++ label: '' + _f("Favorites") + '', ++ use_markup: true, margin_bottom: 6, ++ hexpand: true, halign: Gtk.Align.START })); ++ ++ let align = new Gtk.Alignment({ left_padding: 12 }); ++ this.add(align); ++ ++ let grid = new Gtk.Grid({ orientation: Gtk.Orientation.VERTICAL, ++ row_spacing: 6, ++ column_spacing: 6 }); ++ align.add(grid); ++ ++ let state = this._settings.get_boolean(SETTINGS_FAVORITES_ENABLED); ++ let check = new Gtk.CheckButton({ label: _f("Enable"), ++ active: state, ++ margin_top: 6 }); ++ this._settings.bind(SETTINGS_FAVORITES_ENABLED, check, 'active', Gio.SettingsBindFlags.DEFAULT); ++ grid.add(check); ++ ++ let box = new Gtk.HBox(); ++ grid.add(box); ++ ++ box.pack_start(new Gtk.Label({ label: _f("Position"), ++ halign: Gtk.Align.START }), ++ false, true, 6); ++ ++ state = this._settings.get_boolean(SETTINGS_FAVORITES_POSITION); ++ let radio = null; ++ radio = new Gtk.RadioButton({ active: !state, ++ label: _f('Left'), ++ group: radio }); ++ box.pack_start(radio, false, true, 6); ++ ++ radio = new Gtk.RadioButton({ active: state, ++ label: _f('Right'), ++ group: radio }); ++ this._settings.bind(SETTINGS_FAVORITES_POSITION, radio, 'active', ++ Gio.SettingsBindFlags.DEFAULT); ++ radio.set_active(state); ++ box.pack_start(radio, false, true, 6); ++ ++ ++ this.add(new Gtk.Label({ ++ label: '' + _f("Other Applications") + '', ++ use_markup: true, margin_bottom: 6, margin_top: 12, ++ hexpand: true, halign: Gtk.Align.START })); ++ ++ align = new Gtk.Alignment({ left_padding: 12 }); ++ this.add(align); ++ ++ grid = new Gtk.Grid({ orientation: Gtk.Orientation.VERTICAL, ++ row_spacing: 6, ++ column_spacing: 6 }); ++ align.add(grid); ++ ++ state = this._settings.get_boolean(SETTINGS_OTHER_APPS_ENABLED); ++ check = new Gtk.CheckButton({ label: _f("Enable"), ++ active: state, ++ margin_top: 6 }); ++ this._settings.bind(SETTINGS_OTHER_APPS_ENABLED, check, 'active', ++ Gio.SettingsBindFlags.DEFAULT); ++ grid.add(check); ++ ++ box = new Gtk.HBox(); ++ grid.add(box); ++ ++ box.pack_start(new Gtk.Label({ ++ label: _f("Position"), ++ halign: Gtk.Align.START }), ++ false, true, 6); ++ ++ state = this._settings.get_boolean(SETTINGS_OTHER_APPS_POSITION); ++ radio = null; ++ radio = new Gtk.RadioButton({ active: !state, ++ label: _f('Left'), ++ group: radio }); ++ box.pack_start(radio, false, true, 6); ++ ++ radio = new Gtk.RadioButton({ active: state, ++ label: _f('Right'), ++ group: radio }); ++ this._settings.bind(SETTINGS_OTHER_APPS_POSITION, radio, 'active', ++ Gio.SettingsBindFlags.DEFAULT); ++ radio.set_active(state); ++ box.pack_start(radio, false, true, 6); ++ ++ let scrolled = new Gtk.ScrolledWindow({ shadow_type: Gtk.ShadowType.IN }); ++ scrolled.set_policy(Gtk.PolicyType.NEVER, Gtk.PolicyType.AUTOMATIC); ++ grid.add(scrolled); ++ ++ this._store = new AppsModel(); ++ this._treeView = new Gtk.TreeView({ model: this._store, ++ headers_visible: false, ++ reorderable: true, ++ hexpand: true, ++ vexpand: true ++ }); ++ ++ let column = new Gtk.TreeViewColumn({ title: _f("Launcher") }); ++ let renderer = new Gtk.CellRendererText({ editable: true }); ++ renderer.connect('edited', this._cellEdited.bind(this)); ++ column.pack_start(renderer, true); ++ column.add_attribute(renderer, 'text', this._store.Columns.LABEL); ++ this._treeView.append_column(column); ++ ++ scrolled.add(this._treeView); ++ ++ let toolbar = new Gtk.Toolbar({ icon_size: Gtk.IconSize.SMALL_TOOLBAR }); ++ toolbar.get_style_context().add_class(Gtk.STYLE_CLASS_INLINE_TOOLBAR); ++ ++ let newButton = new Gtk.ToolButton({ icon_name: 'list-add-symbolic' }); ++ newButton.connect('clicked', this._newClicked.bind(this)); ++ toolbar.add(newButton); ++ ++ let delButton = new Gtk.ToolButton({ icon_name: 'list-remove-symbolic' }); ++ delButton.connect('clicked', this._delClicked.bind(this)); ++ toolbar.add(delButton); ++ ++ let selection = this._treeView.get_selection(); ++ selection.connect('changed', ++ function() { ++ delButton.sensitive = selection.count_selected_rows() > 0; ++ }); ++ delButton.sensitive = selection.count_selected_rows() > 0; ++ ++ grid.add(toolbar); ++ } ++ ++ _cellEdited(renderer, path, new_text) { ++ let [ok, iter] = this._store.get_iter_from_string(path); ++ ++ if (ok) ++ this._store.set(iter, [this._store.Columns.LABEL], [new_text]); ++ } ++ ++ _newClicked() { ++ let iter = this._store.append(); ++ let index = this._store.get_path(iter).get_indices()[0]; ++ ++ let label = "dummy.desktop"; ++ this._store.set(iter, [this._store.Columns.LABEL], [label]); ++ } ++ ++ _delClicked() { ++ let [any, model, iter] = this._treeView.get_selection().get_selected(); ++ ++ if (any) ++ this._store.remove(iter); ++ } ++}); ++ ++function init() { ++ ExtensionUtils.initTranslations(); ++} ++ ++function buildPrefsWidget() { ++ let widget = new PanelFavoritesSettingsWidget(); ++ widget.show_all(); ++ ++ return widget; ++} +diff --git a/extensions/panel-favorites/schemas/org.gnome.shell.extensions.panel-favorites.gschema.xml b/extensions/panel-favorites/schemas/org.gnome.shell.extensions.panel-favorites.gschema.xml +new file mode 100644 +index 00000000..ca1228c1 +--- /dev/null ++++ b/extensions/panel-favorites/schemas/org.gnome.shell.extensions.panel-favorites.gschema.xml +@@ -0,0 +1,41 @@ ++ ++ ++ ++ true ++ Display Favorites ++ ++ Whether to show the launchers for Favorite applications. ++ ++ ++ ++ false ++ Where to display Favorites ++ ++ Whether to show the launchers for Favorite applications on the ++ left or right of the panel. ++ ++ ++ ++ false ++ Display Other Apps ++ ++ Whether to show the launchers for other applications. ++ ++ ++ ++ true ++ Where to display Other Apps ++ ++ Whether to show the launchers for other applications on the ++ left or right of the panel. ++ ++ ++ ++ [] ++ Launchers for Other Apps ++ ++ An array of desktop files for other applications to display. ++ ++ ++ ++ +diff --git a/extensions/panel-favorites/stylesheet.css b/extensions/panel-favorites/stylesheet.css +index 120adacb..8d24a827 100644 +--- a/extensions/panel-favorites/stylesheet.css ++++ b/extensions/panel-favorites/stylesheet.css +@@ -2,13 +2,15 @@ + spacing: 6px; + } + ++.panel-launcher-icon { ++ icon-size: 1.5em; ++} ++ + .panel-launcher-label { + border-radius: 7px; + padding: 4px 12px; +- background-color: rgba(0,0,0,0.9); ++ background-color: rgba(24,24,24,0.8); + color: white; + text-align: center; +- font-size: 9pt; +- font-weight: bold; + -y-offset: 6px; + } +-- +2.41.0 + diff --git a/SPECS/gnome-shell-extensions.spec b/SPECS/gnome-shell-extensions.spec index 513a0c6..8ba5df7 100644 --- a/SPECS/gnome-shell-extensions.spec +++ b/SPECS/gnome-shell-extensions.spec @@ -6,7 +6,7 @@ Name: gnome-shell-extensions Version: 3.32.1 -Release: 35%{?dist} +Release: 38%{?dist} Summary: Modify and extend GNOME Shell functionality and behavior Group: User Interface/Desktops @@ -56,6 +56,9 @@ Patch0027: 0001-fileItem-Support-.desktop-files-of-type-Link.patch Patch0028: 0001-classification-banner-Handle-fullscreen-monitors.patch Patch0029: 0001-gesture-inhibitor-Allow-inhibiting-workspace-switch-.patch Patch0030: 0001-desktop-icons-Don-t-use-blocking-IO.patch +Patch0031: 0001-panel-favorites-Update-to-upstream-version.patch +Patch0032: 0001-desktop-icons-Don-t-try-spawn-with-non-existent-work.patch +Patch0033: 0001-classification-banner-Hide-from-picks.patch %description GNOME Shell Extensions is a collection of extensions providing additional and @@ -520,6 +523,7 @@ cp $RPM_SOURCE_DIR/gnome-classic.desktop $RPM_BUILD_ROOT%{_datadir}/xsessions %files -n %{pkg_prefix}-panel-favorites +%{_datadir}/glib-2.0/schemas/org.gnome.shell.extensions.panel-favorites.gschema.xml %{_datadir}/gnome-shell/extensions/panel-favorites*/ @@ -569,6 +573,14 @@ cp $RPM_SOURCE_DIR/gnome-classic.desktop $RPM_BUILD_ROOT%{_datadir}/xsessions %changelog +* Wed Feb 07 2024 Florian Müllner - 3.32.1-38 +- Hide classification banners from picks + Resolves: RHEL-24438 + +* Fri Sep 15 2023 Florian Müllner - 3.32.1-36 +- Update panel-favorites to matching upstream release + Resolves: RHEL-3536 + * Wed Aug 16 2023 Florian Müllner - 3.32.1-35 - Rebuild for custom context menu Resolves: #2232333