diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..0fcaf39 --- /dev/null +++ b/.gitignore @@ -0,0 +1,3 @@ +SOURCES/3.4.1.tar.gz +SOURCES/3.4.5.tar.gz +SOURCES/gnome-shell-extensions-3.28.1.tar.xz diff --git a/.gnome-shell-extensions.metadata b/.gnome-shell-extensions.metadata new file mode 100644 index 0000000..73d481f --- /dev/null +++ b/.gnome-shell-extensions.metadata @@ -0,0 +1,3 @@ +97020dcf5d0a8f2cad8c6e8672c4a837c8fd9a05 SOURCES/3.4.1.tar.gz +dcb2d3fcf3b1f577c8cf4cff0d77d21819189ea1 SOURCES/3.4.5.tar.gz +51b02a3157aa4c36af145b0c57b8132203954fc2 SOURCES/gnome-shell-extensions-3.28.1.tar.xz diff --git a/SOURCES/0001-Add-extra-osk-keys-extension.patch b/SOURCES/0001-Add-extra-osk-keys-extension.patch new file mode 100644 index 0000000..27e754a --- /dev/null +++ b/SOURCES/0001-Add-extra-osk-keys-extension.patch @@ -0,0 +1,309 @@ +From 2ebc78ce92ae615d36ae80420b737b6f1583c7c8 Mon Sep 17 00:00:00 2001 +From: rpm-build +Date: Fri, 9 Aug 2019 14:46:44 +0200 +Subject: [PATCH] Add extra-osk-keys extension + +Originally improveosk by Simon Schumann, +https://extensions.gnome.org/extension/1631/improve-onscreen-keyboard/ +--- + extensions/extra-osk-keys/extension.js | 230 +++++++++++++++++++++ + extensions/extra-osk-keys/meson.build | 5 + + extensions/extra-osk-keys/metadata.json.in | 10 + + extensions/extra-osk-keys/stylesheet.css | 6 + + meson.build | 1 + + 5 files changed, 252 insertions(+) + create mode 100644 extensions/extra-osk-keys/extension.js + create mode 100644 extensions/extra-osk-keys/meson.build + create mode 100644 extensions/extra-osk-keys/metadata.json.in + create mode 100644 extensions/extra-osk-keys/stylesheet.css + +diff --git a/extensions/extra-osk-keys/extension.js b/extensions/extra-osk-keys/extension.js +new file mode 100644 +index 0000000..23221d7 +--- /dev/null ++++ b/extensions/extra-osk-keys/extension.js +@@ -0,0 +1,230 @@ ++ ++const St = imports.gi.St; ++const Main = imports.ui.main; ++const Keyboard = imports.ui.keyboard; ++const EdgeDragAction = imports.ui.edgeDragAction; ++const Shell = imports.gi.Shell; ++const Lang = imports.lang; ++const Clutter = imports.gi.Clutter; ++const Workspace = imports.ui.workspace ++const Tweener = imports.ui.tweener; ++const Overview = imports.ui.overview; ++const Layout = imports.ui.layout; ++ ++var defaultKeyboardDelay; ++var Backup_DefaultKeysForRow; ++var Backup_contructor; ++var Backup_keyvalPress; ++var Backup_keyvalRelease; ++ ++ ++function init() { ++ defaultKeyboardDelay = Layout.KEYBOARD_ANIMATION_TIME; ++ Backup_DefaultKeysForRow = Keyboard.Keyboard.prototype['_getDefaultKeysForRow']; ++ Backup_contructor = Keyboard.KeyboardController.prototype['constructor']; ++ Backup_keyvalPress = Keyboard.KeyboardController.prototype['keyvalPress']; ++ Backup_keyvalRelease = Keyboard.KeyboardController.prototype['keyvalRelease']; ++} ++ ++function enable() { ++ Main.layoutManager.removeChrome(Main.layoutManager.keyboardBox); ++ ++ var KeyboardIsSetup = true; ++ try { ++ Main.keyboard._destroyKeyboard(); ++ } catch (e) { ++ if(e instanceof TypeError) { ++ // In case the keyboard is currently disabled in accessability settings, attempting to _destroyKeyboard() yields a TypeError ("TypeError: this.actor is null") ++ // This doesn't affect functionality, so proceed as usual. The only difference is that we do not automatically _setupKeyboard at the end of this enable() (let the user enable the keyboard in accessability settings) ++ KeyboardIsSetup = false; ++ } else { ++ // Something different happened ++ throw e; ++ } ++ } ++ ++ ++ Keyboard.Keyboard.prototype['_getDefaultKeysForRow'] = function(row, numRows, level) { ++ ++ let defaultKeysPreMod = [ ++ [ [{ label: 'Esc', width: 1, keyval: Clutter.KEY_Escape }], [{ label: '↹', width: 1.5, keyval: Clutter.KEY_Tab }], [{ width: 1.5, level: 1, extraClassName: 'shift-key-lowercase' }], [{ label: 'Ctrl', width: 1, keyval: Clutter.KEY_Control_L, extraClassName: 'control-key' }, { label: '◆', width: 1, keyval: Clutter.KEY_Super_L, extraClassName: 'super-key' }, { label: 'Alt', width: 1, keyval: Clutter.KEY_Alt_L, extraClassName: 'alt-key' }] ], ++ [ [{ label: 'Esc', width: 1, keyval: Clutter.KEY_Escape }], [{ label: '↹', width: 1.5, keyval: Clutter.KEY_Tab }], [{ width: 1.5, level: 0, extraClassName: 'shift-key-uppercase' }], [{ label: 'Ctrl', width: 1, keyval: Clutter.KEY_Control_L, extraClassName: 'control-key' }, { label: '◆', width: 1, keyval: Clutter.KEY_Super_L, extraClassName: 'super-key' }, { label: 'Alt', width: 1, keyval: Clutter.KEY_Alt_L, extraClassName: 'alt-key' }] ], ++ [ [{ label: 'Esc', width: 1, keyval: Clutter.KEY_Escape }], [{ label: '↹', width: 1.5, keyval: Clutter.KEY_Tab }], [{ label: '=/= numRows - 2) { ++ let defaultRow = row - (numRows - 2) + 2; ++ return [defaultKeysPreMod[level][defaultRow], defaultKeysPostMod[level][defaultRow]]; ++ } else { ++ return [null, null]; ++ } ++ } ++ ++ Keyboard.KeyboardController.prototype['constructor'] = function() { ++ let deviceManager = Clutter.DeviceManager.get_default(); ++ this._virtualDevice = deviceManager.create_virtual_device(Clutter.InputDeviceType.KEYBOARD_DEVICE); ++ ++ this._inputSourceManager = InputSourceManager.getInputSourceManager(); ++ this._sourceChangedId = this._inputSourceManager.connect('current-source-changed', ++ this._onSourceChanged.bind(this)); ++ this._sourcesModifiedId = this._inputSourceManager.connect ('sources-changed', ++ this._onSourcesModified.bind(this)); ++ this._currentSource = this._inputSourceManager.currentSource; ++ ++ this._controlActive = false; ++ this._superActive = false; ++ this._altActive = false; ++ ++ Main.inputMethod.connect('notify::content-purpose', ++ this._onContentPurposeHintsChanged.bind(this)); ++ Main.inputMethod.connect('notify::content-hints', ++ this._onContentPurposeHintsChanged.bind(this)); ++ Main.inputMethod.connect('input-panel-state', (o, state) => { ++ this.emit('panel-state', state); ++ }); ++ } ++ ++ ++ Keyboard.KeyboardController.prototype['keyvalPress'] = function(keyval) { ++ if(keyval==Clutter.KEY_Control_L) { ++ this._controlActive = !this._controlActive; // This allows to revert an accidental tap on Ctrl by tapping on it again ++ } ++ if(keyval==Clutter.KEY_Super_L) { ++ this._superActive = !this._superActive; ++ } ++ if(keyval==Clutter.KEY_Alt_L) { ++ this._altActive = !this._altActive; ++ } ++ ++ if(this._controlActive) ++ { ++ this._virtualDevice.notify_keyval(Clutter.get_current_event_time(), ++ Clutter.KEY_Control_L, Clutter.KeyState.PRESSED); ++ Main.layoutManager.keyboardBox.add_style_class_name("control-key-latched"); ++ } else { ++ this._virtualDevice.notify_keyval(Clutter.get_current_event_time(), ++ Clutter.KEY_Control_L, Clutter.KeyState.RELEASED); ++ Main.layoutManager.keyboardBox.remove_style_class_name("control-key-latched"); ++ } ++ if(this._superActive) ++ { ++ this._virtualDevice.notify_keyval(Clutter.get_current_event_time(), ++ Clutter.KEY_Super_L, Clutter.KeyState.PRESSED); ++ Main.layoutManager.keyboardBox.add_style_class_name("super-key-latched"); ++ } else { ++ this._virtualDevice.notify_keyval(Clutter.get_current_event_time(), ++ Clutter.KEY_Super_L, Clutter.KeyState.RELEASED); ++ Main.layoutManager.keyboardBox.remove_style_class_name("super-key-latched"); ++ } ++ if(this._altActive) ++ { ++ this._virtualDevice.notify_keyval(Clutter.get_current_event_time(), ++ Clutter.KEY_Alt_L, Clutter.KeyState.PRESSED); ++ Main.layoutManager.keyboardBox.add_style_class_name("alt-key-latched"); ++ } else { ++ this._virtualDevice.notify_keyval(Clutter.get_current_event_time(), ++ Clutter.KEY_Alt_L, Clutter.KeyState.RELEASED); ++ Main.layoutManager.keyboardBox.remove_style_class_name("alt-key-latched"); ++ } ++ this._virtualDevice.notify_keyval(Clutter.get_current_event_time(), ++ keyval, Clutter.KeyState.PRESSED); ++ } ++ ++ ++ Keyboard.KeyboardController.prototype['keyvalRelease'] = function(keyval) { ++ if(keyval==Clutter.KEY_Control_L || keyval==Clutter.KEY_Alt_L || keyval==Clutter.KEY_Super_L) { ++ return; ++ } ++ ++ this._virtualDevice.notify_keyval(Clutter.get_current_event_time(), ++ keyval, Clutter.KeyState.RELEASED); ++ ++ if(this._controlActive) ++ { ++ this._virtualDevice.notify_keyval(Clutter.get_current_event_time(), ++ Clutter.KEY_Control_L, Clutter.KeyState.RELEASED); ++ this._controlActive = false; ++ Main.layoutManager.keyboardBox.remove_style_class_name("control-key-latched"); ++ } ++ if(this._superActive) ++ { ++ this._virtualDevice.notify_keyval(Clutter.get_current_event_time(), ++ Clutter.KEY_Super_L, Clutter.KeyState.RELEASED); ++ this._superActive = false; ++ Main.layoutManager.keyboardBox.remove_style_class_name("super-key-latched"); ++ } ++ if(this._altActive) ++ { ++ this._virtualDevice.notify_keyval(Clutter.get_current_event_time(), ++ Clutter.KEY_Alt_L, Clutter.KeyState.RELEASED); ++ this._altActive = false; ++ Main.layoutManager.keyboardBox.remove_style_class_name("alt-key-latched"); ++ } ++ ++ } ++ ++ Layout.KEYBOARD_ANIMATION_TIME = 0; ++ if(KeyboardIsSetup) { ++ Main.keyboard._setupKeyboard(); ++ } ++ Main.layoutManager.addChrome( Main.layoutManager.keyboardBox, { affectsStruts: true, trackFullscreen: false }); ++} ++ ++function disable() { ++ Main.layoutManager.removeChrome(Main.layoutManager.keyboardBox); ++ ++ var KeyboardIsSetup = true; ++ try { ++ Main.keyboard._destroyKeyboard(); ++ } catch (e) { ++ if(e instanceof TypeError) { ++ // In case the keyboard is currently disabled in accessability settings, attempting to _destroyKeyboard() yields a TypeError ("TypeError: this.actor is null") ++ // This doesn't affect functionality, so proceed as usual. The only difference is that we do not automatically _setupKeyboard at the end of this enable() (let the user enable the keyboard in accessability settings) ++ KeyboardIsSetup = false; ++ } else { ++ // Something different happened ++ throw e; ++ } ++ } ++ ++ Keyboard.Keyboard.prototype['_getDefaultKeysForRow'] = Backup_DefaultKeysForRow; ++ Keyboard.KeyboardController.prototype['constructor'] = Backup_contructor; ++ Keyboard.KeyboardController.prototype['keyvalPress'] = Backup_keyvalPress; ++ Keyboard.KeyboardController.prototype['keyvalRelease'] = Backup_keyvalRelease; ++ Layout.KEYBOARD_ANIMATION_TIME = defaultKeyboardDelay; ++ if(KeyboardIsSetup) { ++ Main.keyboard._setupKeyboard(); ++ } ++ Main.layoutManager.addChrome( Main.layoutManager.keyboardBox); ++} +diff --git a/extensions/extra-osk-keys/meson.build b/extensions/extra-osk-keys/meson.build +new file mode 100644 +index 0000000..48504f6 +--- /dev/null ++++ b/extensions/extra-osk-keys/meson.build +@@ -0,0 +1,5 @@ ++extension_data += configure_file( ++ input: metadata_name + '.in', ++ output: metadata_name, ++ configuration: metadata_conf ++) +diff --git a/extensions/extra-osk-keys/metadata.json.in b/extensions/extra-osk-keys/metadata.json.in +new file mode 100644 +index 0000000..39dfdd3 +--- /dev/null ++++ b/extensions/extra-osk-keys/metadata.json.in +@@ -0,0 +1,10 @@ ++{ ++"extension-id": "@extension_id@", ++"uuid": "@uuid@", ++"gettext-domain": "@gettext_domain@", ++"name": "Extra Onscreen Keyboard Keys", ++"description": "Adds extra keys to gnome-shell onscreen keyboard.", ++"original-authors": [ "simon.schumann@web.de" ], ++"shell-version": [ "@shell_current@" ], ++"url": "@url@" ++} +diff --git a/extensions/extra-osk-keys/stylesheet.css b/extensions/extra-osk-keys/stylesheet.css +new file mode 100644 +index 0000000..010cfb3 +--- /dev/null ++++ b/extensions/extra-osk-keys/stylesheet.css +@@ -0,0 +1,6 @@ ++.control-key-latched .control-key, ++.super-key-latched .super-key, ++.alt-key-latched .alt-key { ++ background-color: #55a54c; ++} ++ +diff --git a/meson.build b/meson.build +index df2be33..fa4aa9c 100644 +--- a/meson.build ++++ b/meson.build +@@ -56,6 +56,7 @@ all_extensions += [ + 'dash-to-dock', + 'disable-screenshield', + 'example', ++ 'extra-osk-keys', + 'native-window-placement', + 'no-hot-corner', + 'panel-favorites', +-- +2.23.0.rc1 + diff --git a/SOURCES/0001-Include-top-icons-in-classic-session.patch b/SOURCES/0001-Include-top-icons-in-classic-session.patch new file mode 100644 index 0000000..2133918 --- /dev/null +++ b/SOURCES/0001-Include-top-icons-in-classic-session.patch @@ -0,0 +1,32 @@ +From 13beaff6f3c5fe10381b5795163867522435bbcb Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Florian=20M=C3=BCllner?= +Date: Fri, 23 Feb 2018 16:56:46 +0100 +Subject: [PATCH] Include top-icons in classic session + +--- + meson.build | 2 +- + 1 file changed, 1 insertion(+), 1 deletion(-) + +diff --git a/meson.build b/meson.build +index 955b5ee..df2be33 100644 +--- a/meson.build ++++ b/meson.build +@@ -38,6 +38,7 @@ classic_extensions = [ + 'apps-menu', + 'places-menu', + 'launch-new-instance', ++ 'top-icons', + 'window-list' + ] + +@@ -59,7 +60,6 @@ all_extensions += [ + 'no-hot-corner', + 'panel-favorites', + 'systemMonitor', +- 'top-icons', + 'updates-dialog', + 'user-theme', + 'window-grouper' +-- +2.21.0 + diff --git a/SOURCES/0001-Revert-data-Remove-nautilus-classic.patch b/SOURCES/0001-Revert-data-Remove-nautilus-classic.patch new file mode 100644 index 0000000..84bb163 --- /dev/null +++ b/SOURCES/0001-Revert-data-Remove-nautilus-classic.patch @@ -0,0 +1,22 @@ +From de72f146aa090957352e1bfb431e5965e20a9127 Mon Sep 17 00:00:00 2001 +From: Ray Strode +Date: Tue, 31 Jul 2018 14:34:22 -0400 +Subject: [PATCH] Revert "data: Remove nautilus classic" + +This reverts commit 0e625bedbadbf28d23cf0e0f1a53512785016789. +--- + data/gnome-classic.session.desktop.in | 2 +- + 1 file changed, 1 insertion(+), 1 deletion(-) + +diff --git a/data/gnome-classic.session.desktop.in b/data/gnome-classic.session.desktop.in +index f359a77..c26a887 100644 +--- a/data/gnome-classic.session.desktop.in ++++ b/data/gnome-classic.session.desktop.in +@@ -1,3 +1,3 @@ + [GNOME Session] + Name=GNOME Classic +-RequiredComponents=org.gnome.Shell;org.gnome.SettingsDaemon.A11ySettings;org.gnome.SettingsDaemon.Clipboard;org.gnome.SettingsDaemon.Color;org.gnome.SettingsDaemon.Datetime;org.gnome.SettingsDaemon.Housekeeping;org.gnome.SettingsDaemon.Keyboard;org.gnome.SettingsDaemon.MediaKeys;org.gnome.SettingsDaemon.Mouse;org.gnome.SettingsDaemon.Power;org.gnome.SettingsDaemon.PrintNotifications;org.gnome.SettingsDaemon.Rfkill;org.gnome.SettingsDaemon.ScreensaverProxy;org.gnome.SettingsDaemon.Sharing;org.gnome.SettingsDaemon.Smartcard;org.gnome.SettingsDaemon.Sound;org.gnome.SettingsDaemon.Wacom;org.gnome.SettingsDaemon.XSettings; ++RequiredComponents=org.gnome.Shell;org.gnome.SettingsDaemon.A11ySettings;org.gnome.SettingsDaemon.Clipboard;org.gnome.SettingsDaemon.Color;org.gnome.SettingsDaemon.Datetime;org.gnome.SettingsDaemon.Housekeeping;org.gnome.SettingsDaemon.Keyboard;org.gnome.SettingsDaemon.MediaKeys;org.gnome.SettingsDaemon.Mouse;org.gnome.SettingsDaemon.Power;org.gnome.SettingsDaemon.PrintNotifications;org.gnome.SettingsDaemon.Rfkill;org.gnome.SettingsDaemon.ScreensaverProxy;org.gnome.SettingsDaemon.Sharing;org.gnome.SettingsDaemon.Smartcard;org.gnome.SettingsDaemon.Sound;org.gnome.SettingsDaemon.Wacom;org.gnome.SettingsDaemon.XSettings;nautilus-classic; +-- +2.17.1 + diff --git a/SOURCES/0001-Update-style.patch b/SOURCES/0001-Update-style.patch new file mode 100644 index 0000000..aa62709 --- /dev/null +++ b/SOURCES/0001-Update-style.patch @@ -0,0 +1,87 @@ +From bddab939dedf770220f59394b4d4d5534063f0f1 Mon Sep 17 00:00:00 2001 +From: rpm-build +Date: Mon, 11 Jun 2018 16:40:34 -0400 +Subject: [PATCH] Update style + +--- + data/gnome-shell-sass/_common.scss | 19 +++++++++++++++++-- + 1 file changed, 17 insertions(+), 2 deletions(-) + +diff --git a/data/gnome-shell-sass/_common.scss b/data/gnome-shell-sass/_common.scss +index f83b0d8..ac493ff 100644 +--- a/data/gnome-shell-sass/_common.scss ++++ b/data/gnome-shell-sass/_common.scss +@@ -683,7 +683,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; +@@ -776,6 +777,11 @@ StScrollBar { + //dimensions of the icon are hardcoded + } + ++ .panel-logo-icon { ++ padding-right: .4em; ++ icon-size: 1em; ++ } ++ + .system-status-icon, + .app-menu-icon > StIcon, + .popup-menu-arrow { +@@ -1111,7 +1117,7 @@ StScrollBar { + .aggregate-menu { + min-width: 21em; + .popup-menu-icon { padding: 0 4px; } +- .popup-sub-menu .popup-menu-item :first-child { ++ .popup-sub-menu .popup-menu-item > :first-child { + &:ltr { /* 12px spacing + 2*4px padding */ + padding-left: 20px; margin-left: 1.09em; } + &:rtl { /* 12px spacing + 2*4px padding */ +@@ -1397,6 +1403,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; +@@ -1769,7 +1783,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; +@@ -1825,6 +1844,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 + diff --git a/SOURCES/0001-apps-menu-Explicitly-set-label_actor.patch b/SOURCES/0001-apps-menu-Explicitly-set-label_actor.patch new file mode 100644 index 0000000..50ab75d --- /dev/null +++ b/SOURCES/0001-apps-menu-Explicitly-set-label_actor.patch @@ -0,0 +1,148 @@ +From c6d579383b1e3f092cc289291d8f701011d37a67 Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Florian=20M=C3=BCllner?= +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 5067b63..49a05c7 100644 +--- a/extensions/apps-menu/extension.js ++++ b/extensions/apps-menu/extension.js +@@ -7,61 +7,63 @@ const Shell = imports.gi.Shell; + const St = imports.gi.St; + const Clutter = imports.gi.Clutter; + const Main = imports.ui.main; + const Meta = imports.gi.Meta; + const PanelMenu = imports.ui.panelMenu; + const PopupMenu = imports.ui.popupMenu; + const Gtk = imports.gi.Gtk; + const GLib = imports.gi.GLib; + const Gio = imports.gi.Gio; + const Signals = imports.signals; + const Pango = imports.gi.Pango; + + const Gettext = imports.gettext.domain('gnome-shell-extensions'); + const _ = Gettext.gettext; + + const ExtensionUtils = imports.misc.extensionUtils; + const Me = ExtensionUtils.getCurrentExtension(); + const Convenience = Me.imports.convenience; + + const appSys = Shell.AppSystem.get_default(); + + const APPLICATION_ICON_SIZE = 32; + const HORIZ_FACTOR = 5; + const MENU_HEIGHT_OFFSET = 132; + const NAVIGATION_REGION_OVERSHOOT = 50; + + 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) { + this._button.menu.toggle(); + Main.overview.toggle(); + super.activate(event); + } + }; + + class ApplicationMenuItem extends PopupMenu.PopupBaseMenuItem { + constructor(button, app) { + super(); + this._app = app; + this._button = button; + + this._iconBin = new St.Bin(); + this.actor.add_child(this._iconBin); + + let appLabel = new St.Label({ text: app.get_name(), y_expand: true, + y_align: Clutter.ActorAlign.CENTER }); + this.actor.add_child(appLabel); + this.actor.label_actor = appLabel; + + let textureCache = St.TextureCache.get_default(); + let iconThemeChangedId = textureCache.connect('icon-theme-changed', + this._updateIcon.bind(this)); + this.actor.connect('destroy', () => { + textureCache.disconnect(iconThemeChangedId); + }); + this._updateIcon(); +@@ -102,61 +104,63 @@ class ApplicationMenuItem extends PopupMenu.PopupBaseMenuItem { + } + + getDragActor() { + return this._app.create_icon_texture(APPLICATION_ICON_SIZE); + } + + getDragActorSource() { + return this._iconBin; + } + + _updateIcon() { + this._iconBin.set_child(this.getDragActor()); + } + }; + + class CategoryMenuItem extends PopupMenu.PopupBaseMenuItem { + constructor(button, category) { + super(); + this._category = category; + this._button = button; + + this._oldX = -1; + this._oldY = -1; + + let name; + if (this._category) + name = this._category.get_name(); + 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)); + } + + activate(event) { + this._button.selectCategory(this._category, this); + this._button.scrollToCatButton(this); + super.activate(event); + } + + _isNavigatingSubmenu([x, y]) { + let [posX, posY] = this.actor.get_transformed_position(); + + if (this._oldX == -1) { + this._oldX = x; + this._oldY = y; + return true; + } + + let deltaX = Math.abs(x - this._oldX); + let deltaY = Math.abs(y - this._oldY); + + this._oldX = x; + this._oldY = y; + + // If it lies outside the x-coordinates then it is definitely outside. + if (posX > x || posX + this.actor.width < x) + return false; + + // If it lies inside the menu item then it is definitely inside. + if (posY <= y && posY + this.actor.height >= y) +-- +2.17.1 + diff --git a/SOURCES/0001-apps-menu-add-logo-icon-to-Applications-menu.patch b/SOURCES/0001-apps-menu-add-logo-icon-to-Applications-menu.patch new file mode 100644 index 0000000..2f79690 --- /dev/null +++ b/SOURCES/0001-apps-menu-add-logo-icon-to-Applications-menu.patch @@ -0,0 +1,29 @@ +From fe6695b8d45fe7d1d9aea8c41c9aa54048a9704d Mon Sep 17 00:00:00 2001 +From: Ray Strode +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 | 5 +++++ + 1 file changed, 5 insertions(+) + +diff --git a/extensions/apps-menu/extension.js b/extensions/apps-menu/extension.js +index 2f4002a..41d1faf 100644 +--- a/extensions/apps-menu/extension.js ++++ b/extensions/apps-menu/extension.js +@@ -433,6 +433,11 @@ const ApplicationsButton = new Lang.Class({ + + 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, + y_align: Clutter.ActorAlign.CENTER }); +-- +2.14.2 + diff --git a/SOURCES/0001-data-drop-app-icon-styling.patch b/SOURCES/0001-data-drop-app-icon-styling.patch new file mode 100644 index 0000000..c1e993d --- /dev/null +++ b/SOURCES/0001-data-drop-app-icon-styling.patch @@ -0,0 +1,162 @@ +From 524bc0710f6dbbbb6b8135253f03ce5e0059da02 Mon Sep 17 00:00:00 2001 +From: rpm-build +Date: Tue, 4 Sep 2018 09:58:57 -0400 +Subject: [PATCH] data: drop app icon styling + +classic session doesn't show an app icon in the app menu, so +putting a drop shadow around where it would be creates screen +artifacts. + +This commit drops the styling of the app icon that doesn't exist. +--- + data/gnome-shell-sass/_common.scss | 10 ---------- + 1 file changed, 10 deletions(-) + +diff --git a/data/gnome-shell-sass/_common.scss b/data/gnome-shell-sass/_common.scss +index 1ceadf4..3cce6c1 100644 +--- a/data/gnome-shell-sass/_common.scss ++++ b/data/gnome-shell-sass/_common.scss +@@ -742,140 +742,130 @@ StScrollBar { + + #panelLeft, #panelCenter { // spacing between activities<>app menu and such + spacing: 4px; + } + + .panel-corner { + -panel-corner-radius: $panel-corner-radius; + -panel-corner-background-color: rgba(0, 0, 0, 0.35); + -panel-corner-border-width: 2px; + -panel-corner-border-color: transparent; + + &:active, &:overview, &:focus { + -panel-corner-border-color: lighten($selected_bg_color,5%); + } + + &.lock-screen, &.login-screen, &.unlock-screen { + -panel-corner-radius: 0; + -panel-corner-background-color: transparent; + -panel-corner-border-color: transparent; + } + } + + .panel-button { + -natural-hpadding: 12px; + -minimum-hpadding: 6px; + font-weight: bold; + color: #eee; + text-shadow: 0px 1px 2px rgba(0, 0, 0, 0.9); + transition-duration: 100ms; + +- .app-menu-icon { +- -st-icon-style: symbolic; +- margin-left: 4px; +- margin-right: 4px; +- //dimensions of the icon are hardcoded +- } +- + .panel-logo-icon { + padding-right: .4em; + icon-size: 1em; + } + + .system-status-icon, +- .app-menu-icon > StIcon, + .popup-menu-arrow { + icon-shadow: 0px 1px 2px rgba(0, 0, 0, 0.9); + } + + &:hover { + color: lighten($fg_color, 10%); + text-shadow: 0px 1px 6px rgba(0, 0, 0, 1); + + .system-status-icon, +- .app-menu-icon > StIcon, + .popup-menu-arrow { + icon-shadow: 0px 1px 6px rgba(0, 0, 0, 1); + } + } + + &:active, &:overview, &:focus, &:checked { + // Trick due to St limitations. It needs a background to draw + // a box-shadow + background-color: rgba(0, 0, 0, 0.01); + box-shadow: inset 0 -2px 0px lighten($selected_bg_color,5%); + color: lighten($fg_color,10%); + + & > .system-status-icon { icon-shadow: black 0 2px 2px; } + } + + .system-status-icon { icon-size: 1.09em; padding: 0 5px; } + .unlock-screen &, + .login-screen &, + .lock-screen & { + color: lighten($fg_color, 10%); + &:focus, &:hover, &:active { color: lighten($fg_color, 10%); } + } + } + + .panel-status-indicators-box, + .panel-status-menu-box { + spacing: 2px; + } + + // spacing between power icon and (optional) percentage label + .power-status.panel-status-indicators-box { + spacing: 0; + } + + .screencast-indicator { color: $warning_color; } + + &.solid { + background-color: black; + /* transition from transparent to solid */ + transition-duration: 300ms; + + .panel-corner { + -panel-corner-background-color: black; + } + + .panel-button { + color: #ccc; + text-shadow: none; + + &:hover, &:active, &:overview, &:focus, &:checked { + color: lighten($fg_color, 10%); + } + } + + .system-status-icon, +- .app-menu-icon > StIcon, + .popup-menu-arrow { + icon-shadow: none; + } + } + } + + // calendar popover + #calendarArea { + padding: 0.75em 1.0em; + } + + .calendar { + margin-bottom: 1em; + } + + .calendar, + .datemenu-today-button, + .datemenu-displays-box, + .message-list-sections { + margin: 0 1.5em; + } + + .datemenu-calendar-column { spacing: 0.5em; } + .datemenu-displays-section { padding-bottom: 3em; } + .datemenu-displays-box { spacing: 1em; } + + .datemenu-calendar-column { + border: 0 solid lighten($bg_color,5%); + &:ltr { border-left-width: 1px; } + &:rtl { border-right-width: 1px; } +-- +2.17.1 + diff --git a/SOURCES/0001-window-list-drop-button-grab-when-leaving-button.patch b/SOURCES/0001-window-list-drop-button-grab-when-leaving-button.patch new file mode 100644 index 0000000..b36896a --- /dev/null +++ b/SOURCES/0001-window-list-drop-button-grab-when-leaving-button.patch @@ -0,0 +1,42 @@ +From c12006de1af0edb5380b0870ffdfe41d4ff153f1 Mon Sep 17 00:00:00 2001 +From: Ray Strode +Date: Wed, 6 Mar 2019 16:35:14 -0500 +Subject: [PATCH] window-list: drop button grab when leaving button + +StButton doesn't always drop its grab, if the user releases the +mouse over the wrong part of the screen. + +This commit works around the problem by pretending the user +lift their finger from the mouse as soon as they leave the +boundaries of the button actor. +--- + extensions/window-list/extension.js | 6 ++++++ + 1 file changed, 6 insertions(+) + +diff --git a/extensions/window-list/extension.js b/extensions/window-list/extension.js +index 716a324..af71e91 100644 +--- a/extensions/window-list/extension.js ++++ b/extensions/window-list/extension.js +@@ -227,6 +227,7 @@ class BaseButton { + this.actor.connect('allocation-changed', + this._updateIconGeometry.bind(this)); + this.actor.connect('clicked', this._onClicked.bind(this)); ++ this.actor.connect('leave-event', this._onLeave.bind(this)); + this.actor.connect('destroy', this._onDestroy.bind(this)); + this.actor.connect('popup-menu', this._onPopupMenu.bind(this)); + +@@ -261,6 +262,11 @@ class BaseButton { + throw new Error('Not implemented'); + } + ++ _onLeave(actor, button) { ++ this.actor.fake_release(); ++ return Clutter.EVENT_PROPAGATE; ++ } ++ + _canOpenPopupMenu() { + return true; + } +-- +1.8.3.1 + diff --git a/SOURCES/0001-window-list-workspace-indicator-Set-reactiveness-of-.patch b/SOURCES/0001-window-list-workspace-indicator-Set-reactiveness-of-.patch new file mode 100644 index 0000000..1853ae4 --- /dev/null +++ b/SOURCES/0001-window-list-workspace-indicator-Set-reactiveness-of-.patch @@ -0,0 +1,31 @@ +From 4e8db57a1c6a205e50b864ed9bebc9a25cfc12ae Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Jonas=20=C3=85dahl?= +Date: Wed, 25 Sep 2019 10:45:29 +0200 +Subject: [PATCH] window-list/workspace-indicator: Set reactiveness of actual + actor + +Setting this.reactive on the workspace indicator instance doesn't change +the reactiveness of the corresponding clutter actor, as this is the +GNOME 3.28 backport where the actor is wrapped in ShellGenericContainer. + +Fix this by instead setting this.actor.reactive. +--- + extensions/window-list/workspaceIndicator.js | 2 +- + 1 file changed, 1 insertion(+), 1 deletion(-) + +diff --git a/extensions/window-list/workspaceIndicator.js b/extensions/window-list/workspaceIndicator.js +index c0dd65d..f19d1c5 100644 +--- a/extensions/window-list/workspaceIndicator.js ++++ b/extensions/window-list/workspaceIndicator.js +@@ -273,7 +273,7 @@ var WorkspaceIndicator = class WorkspaceIndicator extends PanelMenu.Button { + + _onWorkspaceOrientationChanged() { + let vertical = global.screen.layout_rows == -1; +- this.reactive = vertical; ++ this.actor.reactive = vertical; + + this._statusBin.visible = vertical; + this._thumbnailsBox.visible = !vertical; +-- +2.21.0 + diff --git a/SOURCES/add-extra-extensions.patch b/SOURCES/add-extra-extensions.patch new file mode 100644 index 0000000..2a29be3 --- /dev/null +++ b/SOURCES/add-extra-extensions.patch @@ -0,0 +1,15225 @@ +From ef4ced7c19941f1a6e368ec6b859cdc918d28426 Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Florian=20M=C3=BCllner?= +Date: Wed, 20 May 2015 17:44:50 +0200 +Subject: [PATCH 1/7] Add top-icons extension + +--- + extensions/top-icons/extension.js | 225 ++++++++++++++++++++++++++ + extensions/top-icons/meson.build | 5 + + extensions/top-icons/metadata.json.in | 10 ++ + extensions/top-icons/stylesheet.css | 1 + + meson.build | 1 + + 5 files changed, 242 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 + create mode 100644 extensions/top-icons/stylesheet.css + +diff --git a/extensions/top-icons/extension.js b/extensions/top-icons/extension.js +new file mode 100644 +index 0000000..7312a26 +--- /dev/null ++++ b/extensions/top-icons/extension.js +@@ -0,0 +1,225 @@ ++// -*- mode: js; js-indent-level: 4; indent-tabs-mode: nil -*- ++ ++const Clutter = imports.gi.Clutter; ++const Shell = imports.gi.Shell; ++const St = imports.gi.St; ++const Main = imports.ui.main; ++const GLib = imports.gi.GLib; ++const Lang = imports.lang; ++const Panel = imports.ui.panel; ++const PanelMenu = imports.ui.panelMenu; ++const Meta = imports.gi.Meta; ++const Mainloop = imports.mainloop; ++const NotificationDaemon = imports.ui.notificationDaemon; ++const System = imports.system; ++ ++let trayAddedId = 0; ++let trayRemovedId = 0; ++let getSource = null; ++let icons = []; ++let notificationDaemon = null; ++let sysTray = null; ++ ++const PANEL_ICON_SIZE = 24; ++ ++function init() { ++ if (Main.legacyTray) { ++ notificationDaemon = Main.legacyTray; ++ NotificationDaemon.STANDARD_TRAY_ICON_IMPLEMENTATIONS = imports.ui.legacyTray.STANDARD_TRAY_ICON_IMPLEMENTATIONS; ++ } ++ else if (Main.notificationDaemon._fdoNotificationDaemon && ++ Main.notificationDaemon._fdoNotificationDaemon._trayManager) { ++ notificationDaemon = Main.notificationDaemon._fdoNotificationDaemon; ++ getSource = Lang.bind(notificationDaemon, NotificationDaemon.FdoNotificationDaemon.prototype._getSource); ++ } ++ else if (Main.notificationDaemon._trayManager) { ++ notificationDaemon = Main.notificationDaemon; ++ getSource = Lang.bind(notificationDaemon, NotificationDaemon.NotificationDaemon.prototype._getSource); ++ } ++ else { ++ NotificationDaemon.STANDARD_TRAY_ICON_IMPLEMENTATIONS = { ++ 'bluetooth-applet': 1, 'gnome-sound-applet': 1, 'nm-applet': 1, ++ 'gnome-power-manager': 1, 'keyboard': 1, 'a11y-keyboard': 1, ++ 'kbd-scrolllock': 1, 'kbd-numlock': 1, 'kbd-capslock': 1, 'ibus-ui-gtk': 1 ++ }; ++ } ++} ++ ++function enable() { ++ if (notificationDaemon) ++ GLib.idle_add(GLib.PRIORITY_LOW, moveToTop); ++ else ++ createTray(); ++} ++ ++function createSource (title, pid, ndata, sender, trayIcon) { ++ if (trayIcon) { ++ onTrayIconAdded(this, trayIcon, title); ++ return null; ++ } ++ ++ return getSource(title, pid, ndata, sender, trayIcon); ++}; ++ ++function onTrayIconAdded(o, icon, role) { ++ let wmClass = icon.wm_class ? icon.wm_class.toLowerCase() : ''; ++ if (NotificationDaemon.STANDARD_TRAY_ICON_IMPLEMENTATIONS[wmClass] !== undefined) ++ return; ++ ++ let buttonBox = new PanelMenu.ButtonBox(); ++ let box = buttonBox.actor; ++ let parent = box.get_parent(); ++ ++ let scaleFactor = St.ThemeContext.get_for_stage(global.stage).scale_factor; ++ let iconSize = PANEL_ICON_SIZE * scaleFactor; ++ ++ icon.set_size(iconSize, iconSize); ++ box.add_actor(icon); ++ ++ icon.reactive = true; ++ ++ if (parent) ++ parent.remove_actor(box); ++ ++ icons.push(icon); ++ Main.panel._rightBox.insert_child_at_index(box, 0); ++ ++ let clickProxy = new St.Bin({ width: iconSize, height: iconSize }); ++ clickProxy.reactive = true; ++ Main.uiGroup.add_actor(clickProxy); ++ ++ icon._proxyAlloc = Main.panel._rightBox.connect('allocation-changed', function() { ++ Meta.later_add(Meta.LaterType.BEFORE_REDRAW, function() { ++ let [x, y] = icon.get_transformed_position(); ++ clickProxy.set_position(x, y); ++ }); ++ }); ++ ++ icon.connect("destroy", function() { ++ Main.panel._rightBox.disconnect(icon._proxyAlloc); ++ clickProxy.destroy(); ++ }); ++ ++ clickProxy.connect('button-release-event', function(actor, event) { ++ icon.click(event); ++ }); ++ ++ icon._clickProxy = clickProxy; ++ ++ /* Fixme: HACK */ ++ Meta.later_add(Meta.LaterType.BEFORE_REDRAW, function() { ++ let [x, y] = icon.get_transformed_position(); ++ clickProxy.set_position(x, y); ++ return false; ++ }); ++ let timerId = 0; ++ let i = 0; ++ timerId = Mainloop.timeout_add(500, function() { ++ icon.set_size(icon.width == iconSize ? iconSize - 1 : iconSize, ++ icon.width == iconSize ? iconSize - 1 : iconSize); ++ i++; ++ if (i == 2) ++ Mainloop.source_remove(timerId); ++ }); ++} ++ ++function onTrayIconRemoved(o, icon) { ++ let parent = icon.get_parent(); ++ parent.destroy(); ++ icon.destroy(); ++ icons.splice(icons.indexOf(icon), 1); ++} ++ ++function createTray() { ++ sysTray = new Shell.TrayManager(); ++ sysTray.connect('tray-icon-added', onTrayIconAdded); ++ sysTray.connect('tray-icon-removed', onTrayIconRemoved); ++ sysTray.manage_screen(global.screen, Main.panel.actor); ++} ++ ++function destroyTray() { ++ icons.forEach(icon => { icon.get_parent().destroy(); }); ++ icons = []; ++ ++ sysTray = null; ++ System.gc(); // force finalizing tray to unmanage screen ++} ++ ++function moveToTop() { ++ notificationDaemon._trayManager.disconnect(notificationDaemon._trayIconAddedId); ++ notificationDaemon._trayManager.disconnect(notificationDaemon._trayIconRemovedId); ++ trayAddedId = notificationDaemon._trayManager.connect('tray-icon-added', onTrayIconAdded); ++ trayRemovedId = notificationDaemon._trayManager.connect('tray-icon-removed', onTrayIconRemoved); ++ ++ notificationDaemon._getSource = createSource; ++ ++ let toDestroy = []; ++ if (notificationDaemon._sources) { ++ for (let i = 0; i < notificationDaemon._sources.length; i++) { ++ let source = notificationDaemon._sources[i]; ++ if (!source.trayIcon) ++ continue; ++ let parent = source.trayIcon.get_parent(); ++ parent.remove_actor(source.trayIcon); ++ onTrayIconAdded(this, source.trayIcon, source.initialTitle); ++ toDestroy.push(source); ++ } ++ } ++ else { ++ for (let i = 0; i < notificationDaemon._iconBox.get_n_children(); i++) { ++ let button = notificationDaemon._iconBox.get_child_at_index(i); ++ let icon = button.child; ++ button.remove_actor(icon); ++ onTrayIconAdded(this, icon, ''); ++ toDestroy.push(button); ++ } ++ } ++ ++ for (let i = 0; i < toDestroy.length; i++) { ++ toDestroy[i].destroy(); ++ } ++} ++ ++function moveToTray() { ++ if (trayAddedId != 0) { ++ notificationDaemon._trayManager.disconnect(trayAddedId); ++ trayAddedId = 0; ++ } ++ ++ if (trayRemovedId != 0) { ++ notificationDaemon._trayManager.disconnect(trayRemovedId); ++ trayRemovedId = 0; ++ } ++ ++ notificationDaemon._trayIconAddedId = notificationDaemon._trayManager.connect('tray-icon-added', ++ Lang.bind(notificationDaemon, notificationDaemon._onTrayIconAdded)); ++ notificationDaemon._trayIconRemovedId = notificationDaemon._trayManager.connect('tray-icon-removed', ++ Lang.bind(notificationDaemon, notificationDaemon._onTrayIconRemoved)); ++ ++ notificationDaemon._getSource = getSource; ++ ++ for (let i = 0; i < icons.length; i++) { ++ let icon = icons[i]; ++ let parent = icon.get_parent(); ++ if (icon._clicked) { ++ icon.disconnect(icon._clicked); ++ } ++ icon._clicked = undefined; ++ if (icon._proxyAlloc) { ++ Main.panel._rightBox.disconnect(icon._proxyAlloc); ++ } ++ icon._clickProxy.destroy(); ++ parent.remove_actor(icon); ++ parent.destroy(); ++ notificationDaemon._onTrayIconAdded(notificationDaemon, icon); ++ } ++ ++ icons = []; ++} ++ ++function disable() { ++ if (notificationDaemon) ++ moveToTray(); ++ else ++ destroyTray(); ++} +diff --git a/extensions/top-icons/meson.build b/extensions/top-icons/meson.build +new file mode 100644 +index 0000000..48504f6 +--- /dev/null ++++ b/extensions/top-icons/meson.build +@@ -0,0 +1,5 @@ ++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 0000000..f1e2436 +--- /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": "Shows legacy tray icons on top", ++"shell-version": [ "@shell_current@" ], ++"url": "http://94.247.144.115/repo/topicons/" ++} +diff --git a/extensions/top-icons/stylesheet.css b/extensions/top-icons/stylesheet.css +new file mode 100644 +index 0000000..25134b6 +--- /dev/null ++++ b/extensions/top-icons/stylesheet.css +@@ -0,0 +1 @@ ++/* This extensions requires no special styling */ +diff --git a/meson.build b/meson.build +index 40a8275..c16bde1 100644 +--- a/meson.build ++++ b/meson.build +@@ -54,6 +54,7 @@ all_extensions += [ + 'auto-move-windows', + 'example', + 'native-window-placement', ++ 'top-icons', + 'user-theme' + ] + +-- +2.21.0 + + +From a219c4f34806b6642f0cfc5f890662d599bfd1a5 Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Florian=20M=C3=BCllner?= +Date: Wed, 20 May 2015 18:05:41 +0200 +Subject: [PATCH 2/7] Add dash-to-dock extension + +--- + extensions/dash-to-dock/Settings.ui | 3332 +++++++++++++++++ + extensions/dash-to-dock/appIconIndicators.js | 1124 ++++++ + extensions/dash-to-dock/appIcons.js | 1171 ++++++ + extensions/dash-to-dock/convenience.js | 74 + + extensions/dash-to-dock/dash.js | 1175 ++++++ + extensions/dash-to-dock/docking.js | 1925 ++++++++++ + extensions/dash-to-dock/extension.js | 23 + + extensions/dash-to-dock/intellihide.js | 323 ++ + extensions/dash-to-dock/launcherAPI.js | 244 ++ + extensions/dash-to-dock/media/glossy.svg | 139 + + extensions/dash-to-dock/media/logo.svg | 528 +++ + extensions/dash-to-dock/meson.build | 23 + + extensions/dash-to-dock/metadata.json.in | 12 + + ....shell.extensions.dash-to-dock.gschema.xml | 540 +++ + extensions/dash-to-dock/prefs.js | 868 +++++ + extensions/dash-to-dock/stylesheet.css | 109 + + extensions/dash-to-dock/theming.js | 672 ++++ + extensions/dash-to-dock/utils.js | 255 ++ + extensions/dash-to-dock/windowPreview.js | 630 ++++ + meson.build | 1 + + 20 files changed, 13168 insertions(+) + create mode 100644 extensions/dash-to-dock/Settings.ui + create mode 100644 extensions/dash-to-dock/appIconIndicators.js + create mode 100644 extensions/dash-to-dock/appIcons.js + create mode 100644 extensions/dash-to-dock/convenience.js + create mode 100644 extensions/dash-to-dock/dash.js + create mode 100644 extensions/dash-to-dock/docking.js + create mode 100644 extensions/dash-to-dock/extension.js + create mode 100644 extensions/dash-to-dock/intellihide.js + create mode 100644 extensions/dash-to-dock/launcherAPI.js + create mode 100644 extensions/dash-to-dock/media/glossy.svg + create mode 100644 extensions/dash-to-dock/media/logo.svg + create mode 100644 extensions/dash-to-dock/meson.build + create mode 100644 extensions/dash-to-dock/metadata.json.in + create mode 100644 extensions/dash-to-dock/org.gnome.shell.extensions.dash-to-dock.gschema.xml + create mode 100644 extensions/dash-to-dock/prefs.js + create mode 100644 extensions/dash-to-dock/stylesheet.css + create mode 100644 extensions/dash-to-dock/theming.js + create mode 100644 extensions/dash-to-dock/utils.js + create mode 100644 extensions/dash-to-dock/windowPreview.js + +diff --git a/extensions/dash-to-dock/Settings.ui b/extensions/dash-to-dock/Settings.ui +new file mode 100644 +index 0000000..2b164a8 +--- /dev/null ++++ b/extensions/dash-to-dock/Settings.ui +@@ -0,0 +1,3332 @@ ++ ++ ++ ++ ++ ++ 1 ++ 0.050000000000000003 ++ 0.25 ++ ++ ++ True ++ False ++ 12 ++ 12 ++ 12 ++ 12 ++ vertical ++ ++ ++ True ++ False ++ 0 ++ in ++ ++ ++ True ++ False ++ none ++ ++ ++ 100 ++ 80 ++ True ++ True ++ ++ ++ True ++ False ++ 12 ++ 12 ++ 12 ++ 12 ++ 32 ++ ++ ++ True ++ False ++ True ++ When set to minimize, double clicking minimizes all the windows of the application. ++ True ++ 40 ++ 0 ++ ++ ++ ++ 0 ++ 1 ++ ++ ++ ++ ++ True ++ False ++ True ++ Shift+Click action ++ 0 ++ ++ ++ 0 ++ 0 ++ ++ ++ ++ ++ True ++ False ++ center ++ ++ Raise window ++ Minimize window ++ Launch new instance ++ Cycle through windows ++ Minimize or overview ++ Show window previews ++ Minimize or show previews ++ Quit ++ ++ ++ ++ 1 ++ 0 ++ 2 ++ ++ ++ ++ ++ ++ ++ ++ ++ 100 ++ 80 ++ True ++ True ++ ++ ++ True ++ False ++ 12 ++ 12 ++ 12 ++ 12 ++ 32 ++ ++ ++ True ++ False ++ True ++ Behavior for Middle-Click. ++ True ++ 40 ++ 0 ++ ++ ++ ++ 0 ++ 1 ++ ++ ++ ++ ++ True ++ False ++ True ++ Middle-Click action ++ 0 ++ ++ ++ 0 ++ 0 ++ ++ ++ ++ ++ True ++ False ++ center ++ ++ Raise window ++ Minimize window ++ Launch new instance ++ Cycle through windows ++ Minimize or overview ++ Show window previews ++ Minimize or show previews ++ Quit ++ ++ ++ ++ 1 ++ 0 ++ 2 ++ ++ ++ ++ ++ ++ ++ ++ ++ 100 ++ 80 ++ True ++ True ++ ++ ++ True ++ False ++ 12 ++ 12 ++ 12 ++ 12 ++ 32 ++ ++ ++ True ++ False ++ True ++ Behavior for Shift+Middle-Click. ++ True ++ 40 ++ 0 ++ ++ ++ ++ 0 ++ 1 ++ ++ ++ ++ ++ True ++ False ++ True ++ Shift+Middle-Click action ++ 0 ++ ++ ++ 0 ++ 0 ++ ++ ++ ++ ++ True ++ False ++ center ++ ++ Raise window ++ Minimize window ++ Launch new instance ++ Cycle through windows ++ Minimize or overview ++ Show window previews ++ Minimize or show previews ++ Quit ++ ++ ++ ++ 1 ++ 0 ++ 2 ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ False ++ True ++ 0 ++ ++ ++ ++ ++ 1 ++ 0.01 ++ 0.10000000000000001 ++ ++ ++ 0.33000000000000002 ++ 1 ++ 0.01 ++ 0.10000000000000001 ++ ++ ++ 10 ++ 1 ++ 5 ++ ++ ++ True ++ False ++ 12 ++ 12 ++ 12 ++ 12 ++ vertical ++ ++ ++ True ++ False ++ 0 ++ in ++ ++ ++ True ++ False ++ none ++ ++ ++ 100 ++ 80 ++ True ++ True ++ ++ ++ True ++ False ++ 12 ++ 12 ++ 12 ++ 12 ++ vertical ++ 12 ++ ++ ++ True ++ False ++ 32 ++ ++ ++ True ++ False ++ center ++ Enable Unity7 like glossy backlit items ++ 0 ++ ++ ++ True ++ True ++ 0 ++ ++ ++ ++ ++ True ++ True ++ center ++ ++ ++ False ++ True ++ 1 ++ ++ ++ ++ ++ False ++ True ++ 0 ++ ++ ++ ++ ++ True ++ False ++ ++ ++ True ++ False ++ start ++ Use dominant color ++ ++ ++ True ++ True ++ 0 ++ ++ ++ ++ ++ True ++ True ++ ++ ++ False ++ True ++ 1 ++ ++ ++ ++ ++ False ++ True ++ 1 ++ ++ ++ ++ ++ True ++ False ++ 32 ++ ++ ++ True ++ True ++ ++ ++ 1 ++ 0 ++ ++ ++ ++ ++ True ++ False ++ True ++ Customize indicator style ++ fill ++ 0 ++ ++ ++ 0 ++ 0 ++ ++ ++ ++ ++ False ++ True ++ 2 ++ ++ ++ ++ ++ True ++ False ++ 1 ++ vertical ++ 12 ++ ++ ++ True ++ False ++ 32 ++ ++ ++ True ++ False ++ Color ++ 0 ++ ++ ++ True ++ True ++ 0 ++ ++ ++ ++ ++ True ++ True ++ True ++ ++ ++ False ++ True ++ 1 ++ ++ ++ ++ ++ False ++ True ++ 0 ++ ++ ++ ++ ++ True ++ False ++ 32 ++ ++ ++ True ++ False ++ Border color ++ 0 ++ ++ ++ True ++ True ++ 0 ++ ++ ++ ++ ++ True ++ True ++ True ++ ++ ++ False ++ True ++ 1 ++ ++ ++ ++ ++ False ++ True ++ 1 ++ ++ ++ ++ ++ True ++ False ++ 32 ++ ++ ++ True ++ False ++ Border width ++ 0 ++ ++ ++ True ++ True ++ 0 ++ ++ ++ ++ ++ True ++ True ++ dot_border_width_adjustment ++ ++ ++ False ++ True ++ 1 ++ ++ ++ ++ ++ False ++ True ++ 2 ++ ++ ++ ++ ++ False ++ True ++ 3 ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ False ++ True ++ 0 ++ ++ ++ ++ ++ 1 ++ 0.050000000000000003 ++ 0.25 ++ ++ ++ 16 ++ 128 ++ 1 ++ 10 ++ ++ ++ True ++ True ++ 6 ++ 6 ++ 6 ++ 6 ++ ++ ++ True ++ False ++ 24 ++ 24 ++ 24 ++ 24 ++ vertical ++ 24 ++ ++ ++ True ++ False ++ 0 ++ in ++ ++ ++ True ++ False ++ none ++ ++ ++ True ++ True ++ ++ ++ True ++ False ++ 12 ++ 12 ++ 12 ++ 12 ++ 32 ++ ++ ++ True ++ False ++ True ++ Show the dock on ++ 0 ++ ++ ++ 0 ++ 0 ++ ++ ++ ++ ++ True ++ False ++ center ++ ++ ++ ++ 1 ++ 0 ++ ++ ++ ++ ++ Show on all monitors. ++ True ++ True ++ False ++ 12 ++ 0 ++ True ++ ++ ++ 0 ++ 2 ++ 2 ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ 100 ++ True ++ True ++ ++ ++ True ++ False ++ 12 ++ 12 ++ 12 ++ 12 ++ 32 ++ ++ ++ True ++ False ++ True ++ Position on screen ++ 0 ++ ++ ++ False ++ True ++ 0 ++ ++ ++ ++ ++ True ++ False ++ 32 ++ ++ ++ Left ++ True ++ True ++ False ++ end ++ center ++ 0 ++ True ++ ++ ++ ++ False ++ True ++ 0 ++ ++ ++ ++ ++ Bottom ++ True ++ True ++ False ++ center ++ 0 ++ True ++ position_left_button ++ ++ ++ ++ False ++ True ++ 1 ++ ++ ++ ++ ++ Top ++ True ++ True ++ False ++ center ++ 0 ++ bottom ++ True ++ position_left_button ++ ++ ++ ++ False ++ True ++ 2 ++ ++ ++ ++ ++ Right ++ True ++ True ++ False ++ center ++ 0 ++ True ++ position_left_button ++ ++ ++ ++ False ++ True ++ 3 ++ ++ ++ ++ ++ False ++ True ++ 1 ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ False ++ True ++ 0 ++ ++ ++ ++ ++ True ++ False ++ 0 ++ in ++ ++ ++ True ++ False ++ none ++ ++ ++ 100 ++ 80 ++ True ++ True ++ ++ ++ True ++ False ++ 12 ++ 12 ++ 12 ++ 12 ++ 32 ++ ++ ++ True ++ False ++ True ++ Hide the dock when it obstructs a window of the current application. More refined settings are available. ++ True ++ 0 ++ ++ ++ ++ 0 ++ 1 ++ ++ ++ ++ ++ True ++ False ++ True ++ Intelligent autohide ++ 0 ++ ++ ++ 0 ++ 0 ++ ++ ++ ++ ++ True ++ False ++ 6 ++ ++ ++ True ++ True ++ True ++ center ++ center ++ 0.46000000834465027 ++ ++ ++ True ++ False ++ emblem-system-symbolic ++ ++ ++ ++ ++ ++ False ++ True ++ 0 ++ ++ ++ ++ ++ True ++ True ++ end ++ center ++ ++ ++ False ++ True ++ 1 ++ ++ ++ ++ ++ 1 ++ 0 ++ 2 ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ False ++ True ++ 1 ++ ++ ++ ++ ++ True ++ False ++ 0 ++ in ++ ++ ++ True ++ False ++ none ++ ++ ++ 100 ++ 80 ++ True ++ True ++ ++ ++ True ++ False ++ 12 ++ 12 ++ 12 ++ 12 ++ 32 ++ ++ ++ True ++ False ++ Dock size limit ++ 0 ++ ++ ++ 0 ++ 0 ++ ++ ++ ++ ++ True ++ True ++ baseline ++ True ++ dock_size_adjustment ++ 0 ++ 2 ++ right ++ ++ ++ ++ ++ 1 ++ 0 ++ ++ ++ ++ ++ Panel mode: extend to the screen edge ++ True ++ True ++ False ++ 12 ++ 0 ++ True ++ ++ ++ 0 ++ 1 ++ 2 ++ ++ ++ ++ ++ ++ ++ ++ ++ True ++ True ++ ++ ++ True ++ False ++ 12 ++ 12 ++ 12 ++ 12 ++ 32 ++ ++ ++ True ++ False ++ Icon size limit ++ 0 ++ ++ ++ 0 ++ 0 ++ ++ ++ ++ ++ True ++ True ++ baseline ++ True ++ icon_size_adjustment ++ 1 ++ 0 ++ right ++ ++ ++ ++ ++ 1 ++ 0 ++ ++ ++ ++ ++ Fixed icon size: scroll to reveal other icons ++ True ++ True ++ False ++ 12 ++ 0 ++ True ++ ++ ++ 0 ++ 1 ++ 2 ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ False ++ True ++ 2 ++ ++ ++ ++ ++ ++ ++ True ++ False ++ Position and size ++ ++ ++ False ++ ++ ++ ++ ++ True ++ False ++ 24 ++ 24 ++ 24 ++ 24 ++ vertical ++ 24 ++ ++ ++ True ++ False ++ 0 ++ in ++ ++ ++ True ++ False ++ none ++ ++ ++ True ++ True ++ ++ ++ True ++ False ++ 12 ++ 12 ++ 12 ++ 12 ++ 32 ++ ++ ++ True ++ True ++ end ++ center ++ ++ ++ 1 ++ 0 ++ ++ ++ ++ ++ True ++ False ++ True ++ Show favorite applications ++ 0 ++ ++ ++ 0 ++ 0 ++ ++ ++ ++ ++ ++ ++ ++ ++ True ++ True ++ ++ ++ True ++ False ++ 12 ++ 12 ++ 12 ++ 12 ++ 32 ++ ++ ++ True ++ True ++ end ++ center ++ ++ ++ 1 ++ 0 ++ ++ ++ ++ ++ True ++ False ++ True ++ Show running applications ++ 0 ++ ++ ++ 0 ++ 0 ++ ++ ++ ++ ++ Isolate workspaces. ++ True ++ True ++ False ++ 12 ++ 0 ++ True ++ ++ ++ 0 ++ 2 ++ 2 ++ ++ ++ ++ ++ Isolate monitors. ++ True ++ True ++ False ++ 12 ++ 0 ++ True ++ ++ ++ 0 ++ 3 ++ 2 ++ ++ ++ ++ ++ True ++ True ++ False ++ 3 ++ 0 ++ True ++ ++ ++ True ++ False ++ Show open windows previews. ++ True ++ ++ ++ ++ ++ 0 ++ 1 ++ 2 ++ ++ ++ ++ ++ ++ ++ ++ ++ True ++ True ++ ++ ++ True ++ False ++ 12 ++ 12 ++ 12 ++ 12 ++ 32 ++ ++ ++ True ++ True ++ end ++ center ++ ++ ++ 1 ++ 0 ++ 2 ++ ++ ++ ++ ++ True ++ False ++ True ++ If disabled, these settings are accessible from gnome-tweak-tool or the extension website. ++ True ++ 0 ++ ++ ++ ++ 0 ++ 1 ++ ++ ++ ++ ++ True ++ False ++ True ++ Show <i>Applications</i> icon ++ True ++ 0 ++ ++ ++ 0 ++ 0 ++ ++ ++ ++ ++ Move the applications button at the beginning of the dock. ++ True ++ True ++ False ++ 12 ++ 0 ++ True ++ ++ ++ 0 ++ 2 ++ 2 ++ ++ ++ ++ ++ True ++ True ++ False ++ 3 ++ 0 ++ 0.43000000715255737 ++ True ++ ++ ++ True ++ False ++ Animate <i>Show Applications</i>. ++ True ++ ++ ++ ++ ++ 0 ++ 3 ++ 2 ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ False ++ True ++ 0 ++ ++ ++ ++ ++ 1 ++ ++ ++ ++ ++ True ++ False ++ Launchers ++ ++ ++ 1 ++ False ++ ++ ++ ++ ++ True ++ False ++ 24 ++ 24 ++ 24 ++ 24 ++ vertical ++ 24 ++ ++ ++ True ++ False ++ 0 ++ in ++ ++ ++ True ++ False ++ none ++ ++ ++ True ++ True ++ ++ ++ True ++ False ++ 12 ++ 12 ++ 12 ++ 12 ++ 32 ++ ++ ++ True ++ False ++ True ++ Enable Super+(0-9) as shortcuts to activate apps. It can also be used together with Shift and Ctrl. ++ True ++ True ++ 0 ++ ++ ++ ++ 0 ++ 1 ++ ++ ++ ++ ++ True ++ False ++ True ++ Use keyboard shortcuts to activate apps ++ 0 ++ ++ ++ 0 ++ 0 ++ ++ ++ ++ ++ True ++ False ++ 6 ++ ++ ++ True ++ True ++ True ++ center ++ center ++ 0.46000000834465027 ++ ++ ++ True ++ False ++ emblem-system-symbolic ++ ++ ++ ++ ++ ++ False ++ True ++ 0 ++ ++ ++ ++ ++ True ++ True ++ end ++ center ++ ++ ++ False ++ True ++ 1 ++ ++ ++ ++ ++ 1 ++ 0 ++ 2 ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ False ++ True ++ 1 ++ ++ ++ ++ ++ True ++ False ++ 0 ++ in ++ ++ ++ True ++ False ++ none ++ ++ ++ True ++ True ++ ++ ++ True ++ False ++ 12 ++ 12 ++ 12 ++ 12 ++ 32 ++ ++ ++ True ++ False ++ True ++ Behaviour when clicking on the icon of a running application. ++ True ++ 0 ++ ++ ++ ++ 0 ++ 1 ++ ++ ++ ++ ++ True ++ False ++ True ++ Click action ++ 0 ++ ++ ++ 0 ++ 0 ++ ++ ++ ++ ++ True ++ False ++ 6 ++ ++ ++ True ++ True ++ True ++ center ++ ++ ++ True ++ False ++ emblem-system-symbolic ++ ++ ++ ++ ++ ++ False ++ True ++ 0 ++ ++ ++ ++ ++ True ++ False ++ center ++ ++ Raise window ++ Minimize ++ Launch new instance ++ Cycle through windows ++ Minimize or overview ++ Show window previews ++ Minimize or show previews ++ ++ ++ ++ False ++ True ++ 1 ++ ++ ++ ++ ++ 1 ++ 0 ++ 2 ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ False ++ True ++ 2 ++ ++ ++ ++ ++ True ++ False ++ 0 ++ in ++ ++ ++ True ++ False ++ none ++ ++ ++ True ++ True ++ ++ ++ True ++ False ++ 12 ++ 12 ++ 12 ++ 12 ++ 32 ++ ++ ++ True ++ False ++ True ++ Behaviour when scrolling on the icon of an application. ++ True ++ 0 ++ ++ ++ ++ 0 ++ 1 ++ ++ ++ ++ ++ True ++ False ++ True ++ Scroll action ++ 0 ++ ++ ++ 0 ++ 0 ++ ++ ++ ++ ++ True ++ False ++ 6 ++ ++ ++ True ++ False ++ center ++ ++ Do nothing ++ Cycle through windows ++ Switch workspace ++ ++ ++ ++ False ++ True ++ 1 ++ ++ ++ ++ ++ 1 ++ 0 ++ 2 ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ False ++ True ++ 3 ++ ++ ++ ++ ++ 2 ++ ++ ++ ++ ++ True ++ False ++ Behavior ++ ++ ++ 2 ++ False ++ ++ ++ ++ ++ True ++ False ++ 24 ++ 24 ++ 24 ++ 24 ++ vertical ++ 24 ++ ++ ++ True ++ False ++ 0 ++ in ++ ++ ++ True ++ True ++ none ++ ++ ++ True ++ True ++ ++ ++ True ++ False ++ 12 ++ 12 ++ 12 ++ 12 ++ 32 ++ ++ ++ True ++ False ++ True ++ Few customizations meant to integrate the dock with the default GNOME theme. Alternatively, specific options can be enabled below. ++ True ++ 0 ++ ++ ++ ++ 0 ++ 1 ++ ++ ++ ++ ++ True ++ False ++ True ++ Use built-in theme ++ 0 ++ ++ ++ 0 ++ 0 ++ ++ ++ ++ ++ True ++ True ++ end ++ center ++ ++ ++ 1 ++ 0 ++ 2 ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ False ++ True ++ 0 ++ ++ ++ ++ ++ True ++ False ++ 0 ++ in ++ ++ ++ True ++ False ++ none ++ ++ ++ True ++ True ++ ++ ++ True ++ False ++ 12 ++ 12 ++ 12 ++ 12 ++ 32 ++ ++ ++ True ++ True ++ end ++ center ++ ++ ++ 1 ++ 0 ++ 2 ++ ++ ++ ++ ++ True ++ False ++ True ++ Save space reducing padding and border radius. ++ 0 ++ ++ ++ ++ 0 ++ 1 ++ ++ ++ ++ ++ True ++ False ++ True ++ Shrink the dash ++ 0 ++ ++ ++ 0 ++ 0 ++ ++ ++ ++ ++ ++ ++ ++ ++ True ++ True ++ ++ ++ True ++ False ++ 12 ++ 12 ++ 12 ++ 12 ++ 32 ++ ++ ++ True ++ False ++ True ++ Customize windows counter indicators ++ 0 ++ ++ ++ 0 ++ 0 ++ ++ ++ ++ ++ True ++ False ++ 6 ++ ++ ++ True ++ True ++ True ++ center ++ center ++ 0.46000000834465027 ++ ++ ++ True ++ False ++ emblem-system-symbolic ++ ++ ++ ++ ++ ++ False ++ True ++ 0 ++ ++ ++ ++ ++ True ++ False ++ ++ Default ++ Dots ++ Squares ++ Dashes ++ Segmented ++ Solid ++ Ciliora ++ Metro ++ ++ ++ ++ False ++ True ++ 1 ++ ++ ++ ++ ++ 1 ++ 0 ++ 2 ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ True ++ True ++ ++ ++ True ++ False ++ 12 ++ 12 ++ 12 ++ 12 ++ 32 ++ ++ ++ True ++ False ++ True ++ Set the background color for the dash. ++ True ++ 0 ++ ++ ++ ++ 0 ++ 1 ++ ++ ++ ++ ++ True ++ False ++ True ++ Customize the dash color ++ 0 ++ ++ ++ 0 ++ 0 ++ ++ ++ ++ ++ True ++ False ++ 6 ++ ++ ++ True ++ True ++ True ++ center ++ center ++ 0.46000000834465027 ++ ++ ++ False ++ True ++ 1 ++ ++ ++ ++ ++ True ++ True ++ end ++ center ++ ++ ++ False ++ True ++ 1 ++ ++ ++ ++ ++ 1 ++ 0 ++ 2 ++ ++ ++ ++ ++ ++ ++ ++ ++ True ++ True ++ ++ ++ True ++ False ++ vertical ++ ++ ++ True ++ False ++ 12 ++ 12 ++ 12 ++ 12 ++ 32 ++ ++ ++ True ++ False ++ True ++ Tune the dash background opacity. ++ 0 ++ ++ ++ ++ 0 ++ 1 ++ ++ ++ ++ ++ True ++ False ++ True ++ Customize opacity ++ 0 ++ ++ ++ 0 ++ 0 ++ ++ ++ ++ ++ True ++ False ++ 6 ++ ++ ++ True ++ True ++ True ++ center ++ center ++ ++ ++ True ++ False ++ emblem-system-symbolic ++ ++ ++ ++ ++ ++ False ++ True ++ 0 ++ ++ ++ ++ ++ True ++ False ++ center ++ ++ Default ++ Fixed ++ Adaptive ++ Dynamic ++ ++ ++ ++ False ++ True ++ 1 ++ ++ ++ ++ ++ 1 ++ 0 ++ 2 ++ ++ ++ ++ ++ False ++ True ++ 0 ++ ++ ++ ++ ++ True ++ False ++ 12 ++ 12 ++ 12 ++ 12 ++ 32 ++ ++ ++ True ++ False ++ Opacity ++ ++ ++ False ++ True ++ 0 ++ ++ ++ ++ ++ True ++ True ++ custom_opacity_adjustement ++ on ++ False ++ 0 ++ 0 ++ 2 ++ right ++ ++ ++ ++ ++ True ++ True ++ 1 ++ ++ ++ ++ ++ False ++ True ++ 1 ++ ++ ++ ++ ++ ++ ++ ++ ++ 100 ++ 80 ++ True ++ True ++ ++ ++ True ++ False ++ 32 ++ ++ ++ True ++ False ++ center ++ 12 ++ Force straight corner ++ ++ 0 ++ ++ ++ True ++ True ++ 0 ++ ++ ++ ++ ++ True ++ True ++ center ++ 3 ++ ++ ++ False ++ True ++ 12 ++ 1 ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ False ++ True ++ 1 ++ ++ ++ ++ ++ 3 ++ ++ ++ ++ ++ True ++ False ++ Appearance ++ ++ ++ 3 ++ False ++ ++ ++ ++ ++ False ++ 24 ++ 24 ++ True ++ True ++ vertical ++ 5 ++ ++ ++ ++ False ++ True ++ 10 ++ 0 ++ ++ ++ ++ ++ True ++ False ++ <b>Dash to Dock</b> ++ True ++ ++ ++ False ++ True ++ 1 ++ ++ ++ ++ ++ True ++ False ++ center ++ ++ ++ True ++ False ++ end ++ version: ++ ++ ++ False ++ True ++ 0 ++ ++ ++ ++ ++ True ++ False ++ start ++ ... ++ ++ ++ False ++ True ++ 1 ++ ++ ++ ++ ++ False ++ True ++ 2 ++ ++ ++ ++ ++ True ++ False ++ Moves the dash out of the overview transforming it in a dock ++ center ++ True ++ ++ ++ False ++ True ++ 3 ++ ++ ++ ++ ++ True ++ False ++ center ++ 5 ++ ++ ++ True ++ False ++ Created by ++ ++ ++ False ++ True ++ 0 ++ ++ ++ ++ ++ True ++ True ++ Michele (<a href="mailto:micxgx@gmail.com">micxgx@gmail.com</a>) ++ True ++ ++ ++ False ++ True ++ 1 ++ ++ ++ ++ ++ False ++ True ++ 4 ++ ++ ++ ++ ++ Webpage ++ True ++ True ++ True ++ ++ center ++ none ++ https://micheleg.github.io/dash-to-dock/ ++ ++ ++ False ++ True ++ 5 ++ ++ ++ ++ ++ True ++ True ++ end ++ <span size="small">This program comes with ABSOLUTELY NO WARRANTY. ++See the <a href="https://www.gnu.org/licenses/old-licenses/gpl-2.0.html">GNU General Public License, version 2 or later</a> for details.</span> ++ True ++ center ++ True ++ ++ ++ True ++ True ++ 6 ++ ++ ++ ++ ++ 4 ++ ++ ++ ++ ++ True ++ False ++ About ++ ++ ++ 4 ++ False ++ ++ ++ ++ ++ 1 ++ 0.01 ++ 0.10000000000000001 ++ ++ ++ 1 ++ 0.01 ++ 0.10000000000000001 ++ ++ ++ True ++ False ++ 12 ++ 12 ++ 12 ++ 12 ++ vertical ++ ++ ++ True ++ False ++ 0 ++ in ++ ++ ++ True ++ False ++ none ++ ++ ++ 100 ++ 80 ++ True ++ True ++ ++ ++ True ++ False ++ 12 ++ 12 ++ 12 ++ 12 ++ vertical ++ 12 ++ ++ ++ True ++ False ++ 32 ++ ++ ++ True ++ True ++ ++ ++ 1 ++ 0 ++ ++ ++ ++ ++ True ++ False ++ True ++ Customize minimum and maximum opacity values ++ fill ++ 0 ++ ++ ++ 0 ++ 0 ++ ++ ++ ++ ++ False ++ True ++ 0 ++ ++ ++ ++ ++ True ++ False ++ 12 ++ 12 ++ 12 ++ 12 ++ 32 ++ ++ ++ True ++ False ++ Minimum opacity ++ ++ ++ False ++ True ++ 0 ++ ++ ++ ++ ++ True ++ True ++ min_opacity_adjustement ++ on ++ False ++ 0 ++ 0 ++ 2 ++ right ++ ++ ++ ++ ++ True ++ True ++ 1 ++ ++ ++ ++ ++ False ++ True ++ 1 ++ ++ ++ ++ ++ True ++ False ++ 12 ++ 12 ++ 12 ++ 12 ++ 32 ++ ++ ++ True ++ False ++ Maximum opacity ++ ++ ++ False ++ True ++ 0 ++ ++ ++ ++ ++ True ++ True ++ max_opacity_adjustement ++ on ++ False ++ 0 ++ 0 ++ 2 ++ right ++ ++ ++ ++ ++ True ++ True ++ 1 ++ ++ ++ ++ ++ False ++ True ++ 1 ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ False ++ True ++ 0 ++ ++ ++ ++ ++ 1000 ++ 50 ++ 250 ++ ++ ++ 10 ++ 0.25 ++ 1 ++ ++ ++ True ++ False ++ 12 ++ 12 ++ 12 ++ 12 ++ vertical ++ ++ ++ True ++ False ++ 0 ++ in ++ ++ ++ True ++ False ++ none ++ ++ ++ 100 ++ 80 ++ True ++ True ++ ++ ++ True ++ False ++ 12 ++ 12 ++ 12 ++ 12 ++ 32 ++ ++ ++ True ++ True ++ end ++ center ++ ++ ++ 1 ++ 0 ++ 2 ++ ++ ++ ++ ++ True ++ False ++ True ++ Number overlay ++ 0 ++ ++ ++ 0 ++ 0 ++ ++ ++ ++ ++ True ++ False ++ Temporarily show the application numbers over the icons, corresponding to the shortcut. ++ True ++ 40 ++ 0 ++ ++ ++ ++ 0 ++ 1 ++ ++ ++ ++ ++ ++ ++ ++ ++ 100 ++ 80 ++ True ++ True ++ ++ ++ True ++ False ++ 12 ++ 12 ++ 12 ++ 12 ++ 32 ++ ++ ++ True ++ True ++ end ++ center ++ ++ ++ 1 ++ 0 ++ 2 ++ ++ ++ ++ ++ True ++ False ++ True ++ Show the dock if it is hidden ++ 0 ++ ++ ++ 0 ++ 0 ++ ++ ++ ++ ++ True ++ False ++ If using autohide, the dock will appear for a short time when triggering the shortcut. ++ True ++ 40 ++ 0 ++ ++ ++ ++ 0 ++ 1 ++ ++ ++ ++ ++ ++ ++ ++ ++ 100 ++ 80 ++ True ++ True ++ ++ ++ True ++ False ++ 12 ++ 12 ++ 12 ++ 12 ++ 32 ++ ++ ++ False ++ center ++ 12 ++ ++ ++ 1 ++ 0 ++ ++ ++ ++ ++ True ++ False ++ True ++ Shortcut for the options above ++ 0 ++ ++ ++ 0 ++ 0 ++ ++ ++ ++ ++ True ++ False ++ Syntax: <Shift>, <Ctrl>, <Alt>, <Super> ++ True ++ 40 ++ 0 ++ ++ ++ ++ 0 ++ 1 ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ True ++ True ++ ++ ++ True ++ False ++ 12 ++ 12 ++ 12 ++ 12 ++ True ++ 6 ++ 32 ++ ++ ++ True ++ True ++ end ++ shortcut_time_adjustment ++ 3 ++ ++ ++ 1 ++ 0 ++ ++ ++ ++ ++ True ++ False ++ True ++ Hide timeout (s) ++ 0 ++ ++ ++ 0 ++ 0 ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ False ++ True ++ 0 ++ ++ ++ ++ ++ 1 ++ 0.050000000000000003 ++ 0.25 ++ ++ ++ True ++ False ++ 12 ++ 12 ++ 12 ++ 12 ++ vertical ++ ++ ++ True ++ False ++ 0 ++ in ++ ++ ++ True ++ False ++ none ++ ++ ++ True ++ True ++ ++ ++ True ++ False ++ 12 ++ 12 ++ 12 ++ 12 ++ 32 ++ ++ ++ True ++ False ++ True ++ Show the dock by mouse hover on the screen edge. ++ True ++ 0 ++ ++ ++ ++ 0 ++ 1 ++ ++ ++ ++ ++ True ++ False ++ True ++ Autohide ++ 0 ++ ++ ++ 0 ++ 0 ++ ++ ++ ++ ++ True ++ True ++ end ++ center ++ ++ ++ 1 ++ 0 ++ 2 ++ ++ ++ ++ ++ Push to show: require pressure to show the dock ++ True ++ True ++ False ++ 0 ++ True ++ ++ ++ 0 ++ 3 ++ 2 ++ ++ ++ ++ ++ Enable in fullscreen mode ++ True ++ True ++ False ++ 12 ++ 0 ++ True ++ ++ ++ 0 ++ 2 ++ 2 ++ ++ ++ ++ ++ ++ ++ ++ ++ True ++ True ++ ++ ++ True ++ False ++ 12 ++ 12 ++ 12 ++ 12 ++ 32 ++ ++ ++ True ++ False ++ True ++ Show the dock when it doesn't obstruct application windows. ++ True ++ 0 ++ ++ ++ ++ 0 ++ 1 ++ ++ ++ ++ ++ True ++ False ++ True ++ Dodge windows ++ 0 ++ ++ ++ 0 ++ 0 ++ ++ ++ ++ ++ True ++ True ++ end ++ center ++ ++ ++ 1 ++ 0 ++ 2 ++ ++ ++ ++ ++ True ++ False ++ vertical ++ ++ ++ All windows ++ True ++ True ++ False ++ 12 ++ 0 ++ True ++ True ++ ++ ++ ++ False ++ True ++ 0 ++ ++ ++ ++ ++ Only focused application's windows ++ True ++ True ++ False ++ 0 ++ True ++ True ++ all_windows_radio_button ++ ++ ++ ++ False ++ True ++ 1 ++ ++ ++ ++ ++ Only maximized windows ++ True ++ True ++ False ++ 0 ++ True ++ True ++ all_windows_radio_button ++ ++ ++ ++ False ++ True ++ 2 ++ ++ ++ ++ ++ 0 ++ 2 ++ 2 ++ ++ ++ ++ ++ ++ ++ ++ ++ True ++ True ++ ++ ++ True ++ False ++ 12 ++ 12 ++ 12 ++ 12 ++ True ++ 6 ++ 32 ++ ++ ++ True ++ True ++ end ++ animation_time_adjustment ++ 3 ++ ++ ++ 1 ++ 0 ++ ++ ++ ++ ++ True ++ False ++ True ++ Animation duration (s) ++ 0 ++ ++ ++ 0 ++ 0 ++ ++ ++ ++ ++ True ++ True ++ end ++ hide_timeout_adjustment ++ 3 ++ ++ ++ 1 ++ 1 ++ ++ ++ ++ ++ True ++ True ++ end ++ show_timeout_adjustment ++ 3 ++ ++ ++ 1 ++ 2 ++ ++ ++ ++ ++ True ++ True ++ 0.000 ++ pressure_threshold_adjustment ++ ++ ++ 1 ++ 3 ++ ++ ++ ++ ++ True ++ False ++ True ++ Hide timeout (s) ++ 0 ++ ++ ++ 0 ++ 1 ++ ++ ++ ++ ++ True ++ False ++ True ++ Show timeout (s) ++ 0 ++ ++ ++ 0 ++ 2 ++ ++ ++ ++ ++ True ++ False ++ True ++ Pressure threshold ++ 0 ++ ++ ++ 0 ++ 3 ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ False ++ True ++ 0 ++ ++ ++ ++ +diff --git a/extensions/dash-to-dock/appIconIndicators.js b/extensions/dash-to-dock/appIconIndicators.js +new file mode 100644 +index 0000000..6eb0706 +--- /dev/null ++++ b/extensions/dash-to-dock/appIconIndicators.js +@@ -0,0 +1,1124 @@ ++const Cogl = imports.gi.Cogl; ++const Cairo = imports.cairo; ++const Clutter = imports.gi.Clutter; ++const GdkPixbuf = imports.gi.GdkPixbuf ++const Gio = imports.gi.Gio; ++const Gtk = imports.gi.Gtk; ++const Lang = imports.lang; ++const Pango = imports.gi.Pango; ++const Shell = imports.gi.Shell; ++const St = imports.gi.St; ++ ++const Util = imports.misc.util; ++ ++const Me = imports.misc.extensionUtils.getCurrentExtension(); ++const Utils = Me.imports.utils; ++ ++let tracker = Shell.WindowTracker.get_default(); ++ ++const RunningIndicatorStyle = { ++ DEFAULT: 0, ++ DOTS: 1, ++ SQUARES: 2, ++ DASHES: 3, ++ SEGMENTED: 4, ++ SOLID: 5, ++ CILIORA: 6, ++ METRO: 7 ++}; ++ ++const MAX_WINDOWS_CLASSES = 4; ++ ++ ++/* ++ * This is the main indicator class to be used. The desired bahviour is ++ * obtained by composing the desired classes below based on the settings. ++ * ++ */ ++var AppIconIndicator = new Lang.Class({ ++ ++ Name: 'DashToDock.AppIconIndicator', ++ ++ _init: function(source, settings) { ++ this._indicators = []; ++ ++ // Unity indicators always enabled for now ++ let unityIndicator = new UnityIndicator(source, settings); ++ this._indicators.push(unityIndicator); ++ ++ // Choose the style for the running indicators ++ let runningIndicator = null; ++ let runningIndicatorStyle; ++ ++ if (settings.get_boolean('apply-custom-theme' )) { ++ runningIndicatorStyle = RunningIndicatorStyle.DOTS; ++ } else { ++ runningIndicatorStyle = settings.get_enum('running-indicator-style'); ++ } ++ ++ switch (runningIndicatorStyle) { ++ case RunningIndicatorStyle.DEFAULT: ++ runningIndicator = new RunningIndicatorBase(source, settings); ++ break; ++ ++ case RunningIndicatorStyle.DOTS: ++ runningIndicator = new RunningIndicatorDots(source, settings); ++ break; ++ ++ case RunningIndicatorStyle.SQUARES: ++ runningIndicator = new RunningIndicatorSquares(source, settings); ++ break; ++ ++ case RunningIndicatorStyle.DASHES: ++ runningIndicator = new RunningIndicatorDashes(source, settings); ++ break; ++ ++ case RunningIndicatorStyle.SEGMENTED: ++ runningIndicator = new RunningIndicatorSegmented(source, settings); ++ break; ++ ++ case RunningIndicatorStyle.SOLID: ++ runningIndicator = new RunningIndicatorSolid(source, settings); ++ break; ++ ++ case RunningIndicatorStyle.CILIORA: ++ runningIndicator = new RunningIndicatorCiliora(source, settings); ++ break; ++ ++ case RunningIndicatorStyle.METRO: ++ runningIndicator = new RunningIndicatorMetro(source, settings); ++ break; ++ ++ default: ++ runningIndicator = new RunningIndicatorBase(source, settings); ++ } ++ ++ this._indicators.push(runningIndicator); ++ }, ++ ++ update: function() { ++ for (let i=0; i 0) ++ this._isFocused = true; ++ else ++ this._isFocused = false; ++ ++ // In the case of workspace isolation, we need to hide the dots of apps with ++ // no windows in the current workspace ++ if (this._source.app.state != Shell.AppState.STOPPED && this._nWindows > 0) ++ this._isRunning = true; ++ else ++ this._isRunning = false; ++ ++ this._updateCounterClass(); ++ this._updateFocusClass(); ++ this._updateDefaultDot(); ++ }, ++ ++ _updateCounterClass: function() { ++ for (let i = 1; i <= MAX_WINDOWS_CLASSES; i++) { ++ let className = 'running' + i; ++ if (i != this._nWindows) ++ this._source.actor.remove_style_class_name(className); ++ else ++ this._source.actor.add_style_class_name(className); ++ } ++ }, ++ ++ _updateFocusClass: function() { ++ if (this._isFocused) ++ this._source.actor.add_style_class_name('focused'); ++ else ++ this._source.actor.remove_style_class_name('focused'); ++ }, ++ ++ _updateDefaultDot: function() { ++ if (this._isRunning) ++ this._source._dot.show(); ++ else ++ this._source._dot.hide(); ++ }, ++ ++ _hideDefaultDot: function() { ++ // I use opacity to hide the default dot because the show/hide function ++ // are used by the parent class. ++ this._source._dot.opacity = 0; ++ }, ++ ++ _restoreDefaultDot: function() { ++ this._source._dot.opacity = 255; ++ }, ++ ++ _enableBacklight: function() { ++ ++ let colorPalette = this._dominantColorExtractor._getColorPalette(); ++ ++ // Fallback ++ if (colorPalette === null) { ++ this._source._iconContainer.set_style( ++ 'border-radius: 5px;' + ++ 'background-gradient-direction: vertical;' + ++ 'background-gradient-start: #e0e0e0;' + ++ 'background-gradient-end: darkgray;' ++ ); ++ ++ return; ++ } ++ ++ this._source._iconContainer.set_style( ++ 'border-radius: 5px;' + ++ 'background-gradient-direction: vertical;' + ++ 'background-gradient-start: ' + colorPalette.original + ';' + ++ 'background-gradient-end: ' + colorPalette.darker + ';' ++ ); ++ ++ }, ++ ++ _disableBacklight: function() { ++ this._source._iconContainer.set_style(null); ++ }, ++ ++ destroy: function() { ++ this.parent(); ++ this._disableBacklight(); ++ // Remove glossy background if the children still exists ++ if (this._source._iconContainer.get_children().length > 1) ++ this._source._iconContainer.get_children()[1].set_style(null); ++ this._restoreDefaultDot(); ++ } ++}); ++ ++ ++const RunningIndicatorDots = new Lang.Class({ ++ ++ Name: 'DashToDock.RunningIndicatorDots', ++ Extends: RunningIndicatorBase, ++ ++ _init: function(source, settings) { ++ ++ this.parent(source, settings) ++ ++ this._hideDefaultDot(); ++ ++ this._area = new St.DrawingArea({x_expand: true, y_expand: true}); ++ ++ // We draw for the bottom case and rotate the canvas for other placements ++ //set center of rotatoins to the center ++ this._area.set_pivot_point(0.5, 0.5); ++ // prepare transformation matrix ++ let m = new Cogl.Matrix(); ++ m.init_identity(); ++ ++ switch (this._side) { ++ case St.Side.TOP: ++ m.xx = -1; ++ m.rotate(180, 0, 0, 1); ++ break ++ ++ case St.Side.BOTTOM: ++ // nothing ++ break; ++ ++ case St.Side.LEFT: ++ m.yy = -1; ++ m.rotate(90, 0, 0, 1); ++ break; ++ ++ case St.Side.RIGHT: ++ m.rotate(-90, 0, 0, 1); ++ break ++ } ++ ++ this._area.set_transform(m); ++ ++ this._area.connect('repaint', Lang.bind(this, this._updateIndicator)); ++ this._source._iconContainer.add_child(this._area); ++ ++ let keys = ['custom-theme-running-dots-color', ++ 'custom-theme-running-dots-border-color', ++ 'custom-theme-running-dots-border-width', ++ 'custom-theme-customize-running-dots', ++ 'unity-backlit-items', ++ 'running-indicator-dominant-color']; ++ ++ keys.forEach(function(key) { ++ this._signalsHandler.add([ ++ this._settings, ++ 'changed::' + key, ++ Lang.bind(this, this.update) ++ ]); ++ }, this); ++ ++ // Apply glossy background ++ // TODO: move to enable/disableBacklit to apply itonly to the running apps? ++ // TODO: move to css class for theming support ++ let path = imports.misc.extensionUtils.getCurrentExtension().path; ++ this._glossyBackgroundStyle = 'background-image: url(\'' + path + '/media/glossy.svg\');' + ++ 'background-size: contain;'; ++ ++ }, ++ ++ update: function() { ++ this.parent(); ++ ++ // Enable / Disable the backlight of running apps ++ if (!this._settings.get_boolean('apply-custom-theme') && this._settings.get_boolean('unity-backlit-items')) { ++ this._source._iconContainer.get_children()[1].set_style(this._glossyBackgroundStyle); ++ if (this._isRunning) ++ this._enableBacklight(); ++ else ++ this._disableBacklight(); ++ } else { ++ this._disableBacklight(); ++ this._source._iconContainer.get_children()[1].set_style(null); ++ } ++ ++ if (this._area) ++ this._area.queue_repaint(); ++ }, ++ ++ _computeStyle: function() { ++ ++ let [width, height] = this._area.get_surface_size(); ++ this._width = height; ++ this._height = width; ++ ++ // By defaut re-use the style - background color, and border width and color - ++ // of the default dot ++ let themeNode = this._source._dot.get_theme_node(); ++ this._borderColor = themeNode.get_border_color(this._side); ++ this._borderWidth = themeNode.get_border_width(this._side); ++ this._bodyColor = themeNode.get_background_color(); ++ ++ if (!this._settings.get_boolean('apply-custom-theme')) { ++ // Adjust for the backlit case ++ if (this._settings.get_boolean('unity-backlit-items')) { ++ // Use dominant color for dots too if the backlit is enables ++ let colorPalette = this._dominantColorExtractor._getColorPalette(); ++ ++ // Slightly adjust the styling ++ this._borderWidth = 2; ++ ++ if (colorPalette !== null) { ++ this._borderColor = Clutter.color_from_string(colorPalette.lighter)[1] ; ++ this._bodyColor = Clutter.color_from_string(colorPalette.darker)[1]; ++ } else { ++ // Fallback ++ this._borderColor = Clutter.color_from_string('white')[1]; ++ this._bodyColor = Clutter.color_from_string('gray')[1]; ++ } ++ } ++ ++ // Apply dominant color if requested ++ if (this._settings.get_boolean('running-indicator-dominant-color')) { ++ let colorPalette = this._dominantColorExtractor._getColorPalette(); ++ if (colorPalette !== null) { ++ this._bodyColor = Clutter.color_from_string(colorPalette.original)[1]; ++ } ++ } ++ ++ // Finally, use customize style if requested ++ if (this._settings.get_boolean('custom-theme-customize-running-dots')) { ++ this._borderColor = Clutter.color_from_string(this._settings.get_string('custom-theme-running-dots-border-color'))[1]; ++ this._borderWidth = this._settings.get_int('custom-theme-running-dots-border-width'); ++ this._bodyColor = Clutter.color_from_string(this._settings.get_string('custom-theme-running-dots-color'))[1]; ++ } ++ } ++ ++ // Define the radius as an arbitrary size, but keep large enough to account ++ // for the drawing of the border. ++ this._radius = Math.max(this._width/22, this._borderWidth/2); ++ this._padding = 0; // distance from the margin ++ this._spacing = this._radius + this._borderWidth; // separation between the dots ++ }, ++ ++ _updateIndicator: function() { ++ ++ let area = this._area; ++ let cr = this._area.get_context(); ++ ++ this._computeStyle(); ++ this._drawIndicator(cr); ++ cr.$dispose(); ++ }, ++ ++ _drawIndicator: function(cr) { ++ // Draw the required numbers of dots ++ let n = this._nWindows; ++ ++ cr.setLineWidth(this._borderWidth); ++ Clutter.cairo_set_source_color(cr, this._borderColor); ++ ++ // draw for the bottom case: ++ cr.translate((this._width - (2*n)*this._radius - (n-1)*this._spacing)/2, this._height - this._padding); ++ for (let i = 0; i < n; i++) { ++ cr.newSubPath(); ++ cr.arc((2*i+1)*this._radius + i*this._spacing, -this._radius - this._borderWidth/2, this._radius, 0, 2*Math.PI); ++ } ++ ++ cr.strokePreserve(); ++ Clutter.cairo_set_source_color(cr, this._bodyColor); ++ cr.fill(); ++ }, ++ ++ destroy: function() { ++ this.parent(); ++ this._area.destroy(); ++ } ++ ++}); ++ ++// Adapted from dash-to-panel by Jason DeRose ++// https://github.com/jderose9/dash-to-panel ++const RunningIndicatorCiliora = new Lang.Class({ ++ ++ Name: 'DashToDock.RunningIndicatorCiliora', ++ Extends: RunningIndicatorDots, ++ ++ _drawIndicator: function(cr) { ++ if (this._isRunning) { ++ ++ let size = Math.max(this._width/20, this._borderWidth); ++ let spacing = size; // separation between the dots ++ let lineLength = this._width - (size*(this._nWindows-1)) - (spacing*(this._nWindows-1)); ++ let padding = this._borderWidth; ++ // For the backlit case here we don't want the outer border visible ++ if (this._settings.get_boolean('unity-backlit-items') && !this._settings.get_boolean('custom-theme-customize-running-dots')) ++ padding = 0; ++ let yOffset = this._height - padding - size; ++ ++ cr.setLineWidth(this._borderWidth); ++ Clutter.cairo_set_source_color(cr, this._borderColor); ++ ++ cr.translate(0, yOffset); ++ cr.newSubPath(); ++ cr.rectangle(0, 0, lineLength, size); ++ for (let i = 1; i < this._nWindows; i++) { ++ cr.newSubPath(); ++ cr.rectangle(lineLength + (i*spacing) + ((i-1)*size), 0, size, size); ++ } ++ ++ cr.strokePreserve(); ++ Clutter.cairo_set_source_color(cr, this._bodyColor); ++ cr.fill(); ++ } ++ } ++}); ++ ++// Adapted from dash-to-panel by Jason DeRose ++// https://github.com/jderose9/dash-to-panel ++const RunningIndicatorSegmented = new Lang.Class({ ++ ++ Name: 'DashToDock.RunningIndicatorSegmented', ++ Extends: RunningIndicatorDots, ++ ++ _drawIndicator: function(cr) { ++ if (this._isRunning) { ++ let size = Math.max(this._width/20, this._borderWidth); ++ let spacing = Math.ceil(this._width/18); // separation between the dots ++ let dashLength = Math.ceil((this._width - ((this._nWindows-1)*spacing))/this._nWindows); ++ let lineLength = this._width - (size*(this._nWindows-1)) - (spacing*(this._nWindows-1)); ++ let padding = this._borderWidth; ++ // For the backlit case here we don't want the outer border visible ++ if (this._settings.get_boolean('unity-backlit-items') && !this._settings.get_boolean('custom-theme-customize-running-dots')) ++ padding = 0; ++ let yOffset = this._height - padding - size; ++ ++ cr.setLineWidth(this._borderWidth); ++ Clutter.cairo_set_source_color(cr, this._borderColor); ++ ++ cr.translate(0, yOffset); ++ for (let i = 0; i < this._nWindows; i++) { ++ cr.newSubPath(); ++ cr.rectangle(i*dashLength + i*spacing, 0, dashLength, size); ++ } ++ ++ cr.strokePreserve(); ++ Clutter.cairo_set_source_color(cr, this._bodyColor); ++ cr.fill() ++ } ++ } ++}); ++ ++// Adapted from dash-to-panel by Jason DeRose ++// https://github.com/jderose9/dash-to-panel ++const RunningIndicatorSolid = new Lang.Class({ ++ ++ Name: 'DashToDock.RunningIndicatorSolid', ++ Extends: RunningIndicatorDots, ++ ++ _drawIndicator: function(cr) { ++ if (this._isRunning) { ++ ++ let size = Math.max(this._width/20, this._borderWidth); ++ let padding = this._borderWidth; ++ // For the backlit case here we don't want the outer border visible ++ if (this._settings.get_boolean('unity-backlit-items') && !this._settings.get_boolean('custom-theme-customize-running-dots')) ++ padding = 0; ++ let yOffset = this._height - padding - size; ++ ++ cr.setLineWidth(this._borderWidth); ++ Clutter.cairo_set_source_color(cr, this._borderColor); ++ ++ cr.translate(0, yOffset); ++ cr.newSubPath(); ++ cr.rectangle(0, 0, this._width, size); ++ ++ cr.strokePreserve(); ++ Clutter.cairo_set_source_color(cr, this._bodyColor); ++ cr.fill(); ++ ++ } ++ } ++}); ++ ++// Adapted from dash-to-panel by Jason DeRose ++// https://github.com/jderose9/dash-to-panel ++const RunningIndicatorSquares = new Lang.Class({ ++ ++ Name: 'DashToDock.RunningIndicatorSquares', ++ Extends: RunningIndicatorDots, ++ ++ _drawIndicator: function(cr) { ++ if (this._isRunning) { ++ let size = Math.max(this._width/11, this._borderWidth); ++ let padding = this._borderWidth; ++ let spacing = Math.ceil(this._width/18); // separation between the dots ++ let yOffset = this._height - padding - size; ++ ++ cr.setLineWidth(this._borderWidth); ++ Clutter.cairo_set_source_color(cr, this._borderColor); ++ ++ cr.translate(Math.floor((this._width - this._nWindows*size - (this._nWindows-1)*spacing)/2), yOffset); ++ for (let i = 0; i < this._nWindows; i++) { ++ cr.newSubPath(); ++ cr.rectangle(i*size + i*spacing, 0, size, size); ++ } ++ cr.strokePreserve(); ++ Clutter.cairo_set_source_color(cr, this._bodyColor); ++ cr.fill(); ++ } ++ } ++}); ++ ++// Adapted from dash-to-panel by Jason DeRose ++// https://github.com/jderose9/dash-to-panel ++const RunningIndicatorDashes = new Lang.Class({ ++ ++ Name: 'DashToDock.RunningIndicatorDashes', ++ Extends: RunningIndicatorDots, ++ ++ _drawIndicator: function(cr) { ++ if (this._isRunning) { ++ let size = Math.max(this._width/20, this._borderWidth); ++ let padding = this._borderWidth; ++ let spacing = Math.ceil(this._width/18); // separation between the dots ++ let dashLength = Math.floor(this._width/4) - spacing; ++ let yOffset = this._height - padding - size; ++ ++ cr.setLineWidth(this._borderWidth); ++ Clutter.cairo_set_source_color(cr, this._borderColor); ++ ++ cr.translate(Math.floor((this._width - this._nWindows*dashLength - (this._nWindows-1)*spacing)/2), yOffset); ++ for (let i = 0; i < this._nWindows; i++) { ++ cr.newSubPath(); ++ cr.rectangle(i*dashLength + i*spacing, 0, dashLength, size); ++ } ++ ++ cr.strokePreserve(); ++ Clutter.cairo_set_source_color(cr, this._bodyColor); ++ cr.fill(); ++ } ++ } ++}); ++ ++// Adapted from dash-to-panel by Jason DeRose ++// https://github.com/jderose9/dash-to-panel ++const RunningIndicatorMetro = new Lang.Class({ ++ ++ Name: 'DashToDock.RunningIndicatorMetro', ++ Extends: RunningIndicatorDots, ++ ++ _init: function(source, settings) { ++ this.parent(source, settings); ++ this._source.actor.add_style_class_name('metro'); ++ }, ++ ++ _drawIndicator: function(cr) { ++ if (this._isRunning) { ++ let size = Math.max(this._width/20, this._borderWidth); ++ let padding = 0; ++ // For the backlit case here we don't want the outer border visible ++ if (this._settings.get_boolean('unity-backlit-items') && !this._settings.get_boolean('custom-theme-customize-running-dots')) ++ padding = 0; ++ let yOffset = this._height - padding - size; ++ ++ let n = this._nWindows; ++ if(n <= 1) { ++ cr.translate(0, yOffset); ++ Clutter.cairo_set_source_color(cr, this._bodyColor); ++ cr.newSubPath(); ++ cr.rectangle(0, 0, this._width, size); ++ cr.fill(); ++ } else { ++ let blackenedLength = (1/48)*this._width; // need to scale with the SVG for the stacked highlight ++ let darkenedLength = this._isFocused ? (2/48)*this._width : (10/48)*this._width; ++ let blackenedColor = this._bodyColor.shade(.3); ++ let darkenedColor = this._bodyColor.shade(.7); ++ ++ cr.translate(0, yOffset); ++ ++ Clutter.cairo_set_source_color(cr, this._bodyColor); ++ cr.newSubPath(); ++ cr.rectangle(0, 0, this._width - darkenedLength - blackenedLength, size); ++ cr.fill(); ++ Clutter.cairo_set_source_color(cr, blackenedColor); ++ cr.newSubPath(); ++ cr.rectangle(this._width - darkenedLength - blackenedLength, 0, 1, size); ++ cr.fill(); ++ Clutter.cairo_set_source_color(cr, darkenedColor); ++ cr.newSubPath(); ++ cr.rectangle(this._width - darkenedLength, 0, darkenedLength, size); ++ cr.fill(); ++ } ++ } ++ }, ++ ++ destroy: function() { ++ this.parent(); ++ this._source.actor.remove_style_class_name('metro'); ++ } ++}); ++ ++/* ++ * Unity like notification and progress indicators ++ */ ++const UnityIndicator = new Lang.Class({ ++ Name: 'DashToDock.UnityIndicator', ++ Extends: IndicatorBase, ++ ++ _init: function(source, settings) { ++ ++ this.parent(source, settings); ++ ++ this._notificationBadgeLabel = new St.Label(); ++ this._notificationBadgeBin = new St.Bin({ ++ child: this._notificationBadgeLabel, ++ x_align: St.Align.END, y_align: St.Align.START, ++ x_expand: true, y_expand: true ++ }); ++ this._notificationBadgeLabel.add_style_class_name('notification-badge'); ++ this._notificationBadgeCount = 0; ++ this._notificationBadgeBin.hide(); ++ ++ this._source._iconContainer.add_child(this._notificationBadgeBin); ++ this._source._iconContainer.connect('allocation-changed', Lang.bind(this, this.updateNotificationBadge)); ++ ++ this._remoteEntries = []; ++ this._source.remoteModel.lookupById(this._source.app.id).forEach( ++ Lang.bind(this, function(entry) { ++ this.insertEntryRemote(entry); ++ }) ++ ); ++ ++ this._signalsHandler.add([ ++ this._source.remoteModel, ++ 'entry-added', ++ Lang.bind(this, this._onLauncherEntryRemoteAdded) ++ ], [ ++ this._source.remoteModel, ++ 'entry-removed', ++ Lang.bind(this, this._onLauncherEntryRemoteRemoved) ++ ]) ++ }, ++ ++ _onLauncherEntryRemoteAdded: function(remoteModel, entry) { ++ if (!entry || !entry.appId()) ++ return; ++ if (this._source && this._source.app && this._source.app.id == entry.appId()) { ++ this.insertEntryRemote(entry); ++ } ++ }, ++ ++ _onLauncherEntryRemoteRemoved: function(remoteModel, entry) { ++ if (!entry || !entry.appId()) ++ return; ++ ++ if (this._source && this._source.app && this._source.app.id == entry.appId()) { ++ this.removeEntryRemote(entry); ++ } ++ }, ++ ++ updateNotificationBadge: function() { ++ let scaleFactor = St.ThemeContext.get_for_stage(global.stage).scale_factor; ++ let [minWidth, natWidth] = this._source._iconContainer.get_preferred_width(-1); ++ let logicalNatWidth = natWidth / scaleFactor; ++ let font_size = Math.max(10, Math.round(logicalNatWidth / 5)); ++ let margin_left = Math.round(logicalNatWidth / 4); ++ ++ this._notificationBadgeLabel.set_style( ++ 'font-size: ' + font_size + 'px;' + ++ 'margin-left: ' + margin_left + 'px;' ++ ); ++ ++ this._notificationBadgeBin.width = Math.round(logicalNatWidth - margin_left); ++ this._notificationBadgeLabel.clutter_text.ellipsize = Pango.EllipsizeMode.MIDDLE; ++ }, ++ ++ _notificationBadgeCountToText: function(count) { ++ if (count <= 9999) { ++ return count.toString(); ++ } else if (count < 1e5) { ++ let thousands = count / 1e3; ++ return thousands.toFixed(1).toString() + "k"; ++ } else if (count < 1e6) { ++ let thousands = count / 1e3; ++ return thousands.toFixed(0).toString() + "k"; ++ } else if (count < 1e8) { ++ let millions = count / 1e6; ++ return millions.toFixed(1).toString() + "M"; ++ } else if (count < 1e9) { ++ let millions = count / 1e6; ++ return millions.toFixed(0).toString() + "M"; ++ } else { ++ let billions = count / 1e9; ++ return billions.toFixed(1).toString() + "B"; ++ } ++ }, ++ ++ setNotificationBadge: function(count) { ++ this._notificationBadgeCount = count; ++ let text = this._notificationBadgeCountToText(count); ++ this._notificationBadgeLabel.set_text(text); ++ }, ++ ++ toggleNotificationBadge: function(activate) { ++ if (activate && this._notificationBadgeCount > 0) { ++ this.updateNotificationBadge(); ++ this._notificationBadgeBin.show(); ++ } ++ else ++ this._notificationBadgeBin.hide(); ++ }, ++ ++ _showProgressOverlay: function() { ++ if (this._progressOverlayArea) { ++ this._updateProgressOverlay(); ++ return; ++ } ++ ++ this._progressOverlayArea = new St.DrawingArea({x_expand: true, y_expand: true}); ++ this._progressOverlayArea.connect('repaint', Lang.bind(this, function() { ++ this._drawProgressOverlay(this._progressOverlayArea); ++ })); ++ ++ this._source._iconContainer.add_child(this._progressOverlayArea); ++ this._updateProgressOverlay(); ++ }, ++ ++ _hideProgressOverlay: function() { ++ if (this._progressOverlayArea) ++ this._progressOverlayArea.destroy(); ++ this._progressOverlayArea = null; ++ }, ++ ++ _updateProgressOverlay: function() { ++ if (this._progressOverlayArea) ++ this._progressOverlayArea.queue_repaint(); ++ }, ++ ++ _drawProgressOverlay: function(area) { ++ let scaleFactor = St.ThemeContext.get_for_stage(global.stage).scale_factor; ++ let [surfaceWidth, surfaceHeight] = area.get_surface_size(); ++ let cr = area.get_context(); ++ ++ let iconSize = this._source.icon.iconSize * scaleFactor; ++ ++ let x = Math.floor((surfaceWidth - iconSize) / 2); ++ let y = Math.floor((surfaceHeight - iconSize) / 2); ++ ++ let lineWidth = Math.floor(1.0 * scaleFactor); ++ let padding = Math.floor(iconSize * 0.05); ++ let width = iconSize - 2.0*padding; ++ let height = Math.floor(Math.min(18.0*scaleFactor, 0.20*iconSize)); ++ x += padding; ++ y += iconSize - height - padding; ++ ++ cr.setLineWidth(lineWidth); ++ ++ // Draw the outer stroke ++ let stroke = new Cairo.LinearGradient(0, y, 0, y + height); ++ let fill = null; ++ stroke.addColorStopRGBA(0.5, 0.5, 0.5, 0.5, 0.1); ++ stroke.addColorStopRGBA(0.9, 0.8, 0.8, 0.8, 0.4); ++ Utils.drawRoundedLine(cr, x + lineWidth/2.0, y + lineWidth/2.0, width, height, true, true, stroke, fill); ++ ++ // Draw the background ++ x += lineWidth; ++ y += lineWidth; ++ width -= 2.0*lineWidth; ++ height -= 2.0*lineWidth; ++ ++ stroke = Cairo.SolidPattern.createRGBA(0.20, 0.20, 0.20, 0.9); ++ fill = new Cairo.LinearGradient(0, y, 0, y + height); ++ fill.addColorStopRGBA(0.4, 0.25, 0.25, 0.25, 1.0); ++ fill.addColorStopRGBA(0.9, 0.35, 0.35, 0.35, 1.0); ++ Utils.drawRoundedLine(cr, x + lineWidth/2.0, y + lineWidth/2.0, width, height, true, true, stroke, fill); ++ ++ // Draw the finished bar ++ x += lineWidth; ++ y += lineWidth; ++ width -= 2.0*lineWidth; ++ height -= 2.0*lineWidth; ++ ++ let finishedWidth = Math.ceil(this._progress * width); ++ stroke = Cairo.SolidPattern.createRGBA(0.8, 0.8, 0.8, 1.0); ++ fill = Cairo.SolidPattern.createRGBA(0.9, 0.9, 0.9, 1.0); ++ ++ if (Clutter.get_default_text_direction() == Clutter.TextDirection.RTL) ++ Utils.drawRoundedLine(cr, x + lineWidth/2.0 + width - finishedWidth, y + lineWidth/2.0, finishedWidth, height, true, true, stroke, fill); ++ else ++ Utils.drawRoundedLine(cr, x + lineWidth/2.0, y + lineWidth/2.0, finishedWidth, height, true, true, stroke, fill); ++ ++ cr.$dispose(); ++ }, ++ ++ setProgress: function(progress) { ++ this._progress = Math.min(Math.max(progress, 0.0), 1.0); ++ this._updateProgressOverlay(); ++ }, ++ ++ toggleProgressOverlay: function(activate) { ++ if (activate) { ++ this._showProgressOverlay(); ++ } ++ else { ++ this._hideProgressOverlay(); ++ } ++ }, ++ ++ insertEntryRemote: function(remote) { ++ if (!remote || this._remoteEntries.indexOf(remote) !== -1) ++ return; ++ ++ this._remoteEntries.push(remote); ++ this._selectEntryRemote(remote); ++ }, ++ ++ removeEntryRemote: function(remote) { ++ if (!remote || this._remoteEntries.indexOf(remote) == -1) ++ return; ++ ++ this._remoteEntries.splice(this._remoteEntries.indexOf(remote), 1); ++ ++ if (this._remoteEntries.length > 0) { ++ this._selectEntryRemote(this._remoteEntries[this._remoteEntries.length-1]); ++ } else { ++ this.setNotificationBadge(0); ++ this.toggleNotificationBadge(false); ++ this.setProgress(0); ++ this.toggleProgressOverlay(false); ++ } ++ }, ++ ++ _selectEntryRemote: function(remote) { ++ if (!remote) ++ return; ++ ++ this._signalsHandler.removeWithLabel('entry-remotes'); ++ ++ this._signalsHandler.addWithLabel('entry-remotes', ++ [ ++ remote, ++ 'count-changed', ++ Lang.bind(this, (remote, value) => { ++ this.setNotificationBadge(value); ++ }) ++ ], [ ++ remote, ++ 'count-visible-changed', ++ Lang.bind(this, (remote, value) => { ++ this.toggleNotificationBadge(value); ++ }) ++ ], [ ++ remote, ++ 'progress-changed', ++ Lang.bind(this, (remote, value) => { ++ this.setProgress(value); ++ }) ++ ], [ ++ remote, ++ 'progress-visible-changed', ++ Lang.bind(this, (remote, value) => { ++ this.toggleProgressOverlay(value); ++ }) ++ ]); ++ ++ this.setNotificationBadge(remote.count()); ++ this.toggleNotificationBadge(remote.countVisible()); ++ this.setProgress(remote.progress()); ++ this.toggleProgressOverlay(remote.progressVisible()); ++ } ++}); ++ ++ ++// We need an icons theme object, this is the only way I managed to get ++// pixel buffers that can be used for calculating the backlight color ++let themeLoader = null; ++ ++// Global icon cache. Used for Unity7 styling. ++let iconCacheMap = new Map(); ++// Max number of items to store ++// We don't expect to ever reach this number, but let's put an hard limit to avoid ++// even the remote possibility of the cached items to grow indefinitely. ++const MAX_CACHED_ITEMS = 1000; ++// When the size exceed it, the oldest 'n' ones are deleted ++const BATCH_SIZE_TO_DELETE = 50; ++// The icon size used to extract the dominant color ++const DOMINANT_COLOR_ICON_SIZE = 64; ++ ++// Compute dominant color frim the app icon. ++// The color is cached for efficiency. ++const DominantColorExtractor = new Lang.Class({ ++ Name: 'DashToDock.DominantColorExtractor', ++ ++ _init: function(app) { ++ this._app = app; ++ }, ++ ++ /** ++ * Try to get the pixel buffer for the current icon, if not fail gracefully ++ */ ++ _getIconPixBuf: function() { ++ let iconTexture = this._app.create_icon_texture(16); ++ ++ if (themeLoader === null) { ++ let ifaceSettings = new Gio.Settings({ schema: "org.gnome.desktop.interface" }); ++ ++ themeLoader = new Gtk.IconTheme(), ++ themeLoader.set_custom_theme(ifaceSettings.get_string('icon-theme')); // Make sure the correct theme is loaded ++ } ++ ++ // Unable to load the icon texture, use fallback ++ if (iconTexture instanceof St.Icon === false) { ++ return null; ++ } ++ ++ iconTexture = iconTexture.get_gicon(); ++ ++ // Unable to load the icon texture, use fallback ++ if (iconTexture === null) { ++ return null; ++ } ++ ++ if (iconTexture instanceof Gio.FileIcon) { ++ // Use GdkPixBuf to load the pixel buffer from the provided file path ++ return GdkPixbuf.Pixbuf.new_from_file(iconTexture.get_file().get_path()); ++ } ++ ++ // Get the pixel buffer from the icon theme ++ let icon_info = themeLoader.lookup_icon(iconTexture.get_names()[0], DOMINANT_COLOR_ICON_SIZE, 0); ++ if (icon_info !== null) ++ return icon_info.load_icon(); ++ else ++ return null; ++ }, ++ ++ /** ++ * The backlight color choosing algorithm was mostly ported to javascript from the ++ * Unity7 C++ source of Canonicals: ++ * https://bazaar.launchpad.net/~unity-team/unity/trunk/view/head:/launcher/LauncherIcon.cpp ++ * so it more or less works the same way. ++ */ ++ _getColorPalette: function() { ++ if (iconCacheMap.get(this._app.get_id())) { ++ // We already know the answer ++ return iconCacheMap.get(this._app.get_id()); ++ } ++ ++ let pixBuf = this._getIconPixBuf(); ++ if (pixBuf == null) ++ return null; ++ ++ let pixels = pixBuf.get_pixels(), ++ offset = 0; ++ ++ let total = 0, ++ rTotal = 0, ++ gTotal = 0, ++ bTotal = 0; ++ ++ let resample_y = 1, ++ resample_x = 1; ++ ++ // Resampling of large icons ++ // We resample icons larger than twice the desired size, as the resampling ++ // to a size s ++ // DOMINANT_COLOR_ICON_SIZE < s < 2*DOMINANT_COLOR_ICON_SIZE, ++ // most of the case exactly DOMINANT_COLOR_ICON_SIZE as the icon size is tipycally ++ // a multiple of it. ++ let width = pixBuf.get_width(); ++ let height = pixBuf.get_height(); ++ ++ // Resample ++ if (height >= 2* DOMINANT_COLOR_ICON_SIZE) ++ resample_y = Math.floor(height/DOMINANT_COLOR_ICON_SIZE); ++ ++ if (width >= 2* DOMINANT_COLOR_ICON_SIZE) ++ resample_x = Math.floor(width/DOMINANT_COLOR_ICON_SIZE); ++ ++ if (resample_x !==1 || resample_y !== 1) ++ pixels = this._resamplePixels(pixels, resample_x, resample_y); ++ ++ // computing the limit outside the for (where it would be repeated at each iteration) ++ // for performance reasons ++ let limit = pixels.length; ++ for (let offset = 0; offset < limit; offset+=4) { ++ let r = pixels[offset], ++ g = pixels[offset + 1], ++ b = pixels[offset + 2], ++ a = pixels[offset + 3]; ++ ++ let saturation = (Math.max(r,g, b) - Math.min(r,g, b)); ++ let relevance = 0.1 * 255 * 255 + 0.9 * a * saturation; ++ ++ rTotal += r * relevance; ++ gTotal += g * relevance; ++ bTotal += b * relevance; ++ ++ total += relevance; ++ } ++ ++ total = total * 255; ++ ++ let r = rTotal / total, ++ g = gTotal / total, ++ b = bTotal / total; ++ ++ let hsv = Utils.ColorUtils.RGBtoHSV(r * 255, g * 255, b * 255); ++ ++ if (hsv.s > 0.15) ++ hsv.s = 0.65; ++ hsv.v = 0.90; ++ ++ let rgb = Utils.ColorUtils.HSVtoRGB(hsv.h, hsv.s, hsv.v); ++ ++ // Cache the result. ++ let backgroundColor = { ++ lighter: Utils.ColorUtils.ColorLuminance(rgb.r, rgb.g, rgb.b, 0.2), ++ original: Utils.ColorUtils.ColorLuminance(rgb.r, rgb.g, rgb.b, 0), ++ darker: Utils.ColorUtils.ColorLuminance(rgb.r, rgb.g, rgb.b, -0.5) ++ }; ++ ++ if (iconCacheMap.size >= MAX_CACHED_ITEMS) { ++ //delete oldest cached values (which are in order of insertions) ++ let ctr=0; ++ for (let key of iconCacheMap.keys()) { ++ if (++ctr > BATCH_SIZE_TO_DELETE) ++ break; ++ iconCacheMap.delete(key); ++ } ++ } ++ ++ iconCacheMap.set(this._app.get_id(), backgroundColor); ++ ++ return backgroundColor; ++ }, ++ ++ /** ++ * Downsample large icons before scanning for the backlight color to ++ * improve performance. ++ * ++ * @param pixBuf ++ * @param pixels ++ * @param resampleX ++ * @param resampleY ++ * ++ * @return []; ++ */ ++ _resamplePixels: function (pixels, resampleX, resampleY) { ++ let resampledPixels = []; ++ // computing the limit outside the for (where it would be repeated at each iteration) ++ // for performance reasons ++ let limit = pixels.length / (resampleX * resampleY) / 4; ++ for (let i = 0; i < limit; i++) { ++ let pixel = i * resampleX * resampleY; ++ ++ resampledPixels.push(pixels[pixel * 4]); ++ resampledPixels.push(pixels[pixel * 4 + 1]); ++ resampledPixels.push(pixels[pixel * 4 + 2]); ++ resampledPixels.push(pixels[pixel * 4 + 3]); ++ } ++ ++ return resampledPixels; ++ }, ++}) +diff --git a/extensions/dash-to-dock/appIcons.js b/extensions/dash-to-dock/appIcons.js +new file mode 100644 +index 0000000..ef9c6e1 +--- /dev/null ++++ b/extensions/dash-to-dock/appIcons.js +@@ -0,0 +1,1171 @@ ++// -*- mode: js; js-indent-level: 4; indent-tabs-mode: nil -*- ++ ++const Clutter = imports.gi.Clutter; ++const GdkPixbuf = imports.gi.GdkPixbuf ++const Gio = imports.gi.Gio; ++const GLib = imports.gi.GLib; ++const Gtk = imports.gi.Gtk; ++const Signals = imports.signals; ++const Lang = imports.lang; ++const Meta = imports.gi.Meta; ++const Shell = imports.gi.Shell; ++const St = imports.gi.St; ++const Mainloop = imports.mainloop; ++ ++// Use __ () and N__() for the extension gettext domain, and reuse ++// the shell domain with the default _() and N_() ++const Gettext = imports.gettext.domain('dashtodock'); ++const __ = Gettext.gettext; ++const N__ = function(e) { return e }; ++ ++const AppDisplay = imports.ui.appDisplay; ++const AppFavorites = imports.ui.appFavorites; ++const Dash = imports.ui.dash; ++const DND = imports.ui.dnd; ++const IconGrid = imports.ui.iconGrid; ++const Main = imports.ui.main; ++const PopupMenu = imports.ui.popupMenu; ++const Tweener = imports.ui.tweener; ++const Util = imports.misc.util; ++const Workspace = imports.ui.workspace; ++ ++const Me = imports.misc.extensionUtils.getCurrentExtension(); ++const Utils = Me.imports.utils; ++const WindowPreview = Me.imports.windowPreview; ++const AppIconIndicators = Me.imports.appIconIndicators; ++ ++let tracker = Shell.WindowTracker.get_default(); ++ ++let DASH_ITEM_LABEL_SHOW_TIME = Dash.DASH_ITEM_LABEL_SHOW_TIME; ++ ++const clickAction = { ++ SKIP: 0, ++ MINIMIZE: 1, ++ LAUNCH: 2, ++ CYCLE_WINDOWS: 3, ++ MINIMIZE_OR_OVERVIEW: 4, ++ PREVIEWS: 5, ++ MINIMIZE_OR_PREVIEWS: 6, ++ QUIT: 7 ++}; ++ ++const scrollAction = { ++ DO_NOTHING: 0, ++ CYCLE_WINDOWS: 1, ++ SWITCH_WORKSPACE: 2 ++}; ++ ++let recentlyClickedAppLoopId = 0; ++let recentlyClickedApp = null; ++let recentlyClickedAppWindows = null; ++let recentlyClickedAppIndex = 0; ++let recentlyClickedAppMonitor = -1; ++ ++/** ++ * Extend AppIcon ++ * ++ * - Pass settings to the constructor and bind settings changes ++ * - Apply a css class based on the number of windows of each application (#N); ++ * - Customized indicators for running applications in place of the default "dot" style which is hidden (#N); ++ * a class of the form "running#N" is applied to the AppWellIcon actor. ++ * like the original .running one. ++ * - Add a .focused style to the focused app ++ * - Customize click actions. ++ * - Update minimization animation target ++ * - Update menu if open on windows change ++ */ ++var MyAppIcon = new Lang.Class({ ++ Name: 'DashToDock.AppIcon', ++ Extends: AppDisplay.AppIcon, ++ ++ // settings are required inside. ++ _init: function(settings, remoteModel, app, monitorIndex, iconParams) { ++ // a prefix is required to avoid conflicting with the parent class variable ++ this._dtdSettings = settings; ++ this.monitorIndex = monitorIndex; ++ this._signalsHandler = new Utils.GlobalSignalsHandler(); ++ this.remoteModel = remoteModel; ++ this._indicator = null; ++ ++ this.parent(app, iconParams); ++ ++ this._updateIndicatorStyle(); ++ ++ // Monitor windows-changes instead of app state. ++ // Keep using the same Id and function callback (that is extended) ++ if (this._stateChangedId > 0) { ++ this.app.disconnect(this._stateChangedId); ++ this._stateChangedId = 0; ++ } ++ ++ this._windowsChangedId = this.app.connect('windows-changed', ++ Lang.bind(this, ++ this.onWindowsChanged)); ++ this._focusAppChangeId = tracker.connect('notify::focus-app', ++ Lang.bind(this, ++ this._onFocusAppChanged)); ++ ++ // In Wayland sessions, this signal is needed to track the state of windows dragged ++ // from one monitor to another. As this is triggered quite often (whenever a new winow ++ // of any application opened or moved to a different desktop), ++ // we restrict this signal to the case when 'isolate-monitors' is true, ++ // and if there are at least 2 monitors. ++ if (this._dtdSettings.get_boolean('isolate-monitors') && ++ Main.layoutManager.monitors.length > 1) { ++ this._signalsHandler.removeWithLabel('isolate-monitors'); ++ this._signalsHandler.addWithLabel('isolate-monitors', [ ++ global.screen, ++ 'window-entered-monitor', ++ Lang.bind(this, this._onWindowEntered) ++ ]); ++ } ++ ++ this._progressOverlayArea = null; ++ this._progress = 0; ++ ++ let keys = ['apply-custom-theme', ++ 'running-indicator-style', ++ ]; ++ ++ keys.forEach(function(key) { ++ this._signalsHandler.add([ ++ this._dtdSettings, ++ 'changed::' + key, ++ Lang.bind(this, this._updateIndicatorStyle) ++ ]); ++ }, this); ++ ++ this._dtdSettings.connect('changed::scroll-action', Lang.bind(this, function() { ++ this._optionalScrollCycleWindows(); ++ })); ++ this._optionalScrollCycleWindows(); ++ ++ this._numberOverlay(); ++ ++ this._previewMenuManager = null; ++ this._previewMenu = null; ++ }, ++ ++ _onDestroy: function() { ++ this.parent(); ++ ++ // This is necessary due to an upstream bug ++ // https://bugzilla.gnome.org/show_bug.cgi?id=757556 ++ // It can be safely removed once it get solved upstrea. ++ if (this._menu) ++ this._menu.close(false); ++ ++ // Disconect global signals ++ ++ if (this._windowsChangedId > 0) ++ this.app.disconnect(this._windowsChangedId); ++ this._windowsChangedId = 0; ++ ++ if (this._focusAppChangeId > 0) { ++ tracker.disconnect(this._focusAppChangeId); ++ this._focusAppChangeId = 0; ++ } ++ ++ this._signalsHandler.destroy(); ++ ++ if (this._scrollEventHandler) ++ this.actor.disconnect(this._scrollEventHandler); ++ }, ++ ++ // TOOD Rename this function ++ _updateIndicatorStyle: function() { ++ ++ if (this._indicator !== null) { ++ this._indicator.destroy(); ++ this._indicator = null; ++ } ++ this._indicator = new AppIconIndicators.AppIconIndicator(this, this._dtdSettings); ++ this._indicator.update(); ++ }, ++ ++ _onWindowEntered: function(metaScreen, monitorIndex, metaWin) { ++ let app = Shell.WindowTracker.get_default().get_window_app(metaWin); ++ if (app && app.get_id() == this.app.get_id()) ++ this.onWindowsChanged(); ++ }, ++ ++ _optionalScrollCycleWindows: function() { ++ if (this._scrollEventHandler) { ++ this.actor.disconnect(this._scrollEventHandler); ++ this._scrollEventHandler = 0; ++ } ++ ++ let isEnabled = this._dtdSettings.get_enum('scroll-action') === scrollAction.CYCLE_WINDOWS; ++ if (!isEnabled) return; ++ this._scrollEventHandler = this.actor.connect('scroll-event', Lang.bind(this, ++ this.onScrollEvent)); ++ }, ++ ++ onScrollEvent: function(actor, event) { ++ ++ // We only activate windows of running applications, i.e. we never open new windows ++ // We check if the app is running, and that the # of windows is > 0 in ++ // case we use workspace isolation, ++ let appIsRunning = this.app.state == Shell.AppState.RUNNING ++ && this.getInterestingWindows().length > 0; ++ ++ if (!appIsRunning) ++ return false ++ ++ if (this._optionalScrollCycleWindowsDeadTimeId > 0) ++ return false; ++ else ++ this._optionalScrollCycleWindowsDeadTimeId = Mainloop.timeout_add(250, Lang.bind(this, function() { ++ this._optionalScrollCycleWindowsDeadTimeId = 0; ++ })); ++ ++ let direction = null; ++ ++ switch (event.get_scroll_direction()) { ++ case Clutter.ScrollDirection.UP: ++ direction = Meta.MotionDirection.UP; ++ break; ++ case Clutter.ScrollDirection.DOWN: ++ direction = Meta.MotionDirection.DOWN; ++ break; ++ case Clutter.ScrollDirection.SMOOTH: ++ let [dx, dy] = event.get_scroll_delta(); ++ if (dy < 0) ++ direction = Meta.MotionDirection.UP; ++ else if (dy > 0) ++ direction = Meta.MotionDirection.DOWN; ++ break; ++ } ++ ++ let focusedApp = tracker.focus_app; ++ if (!Main.overview._shown) { ++ let reversed = direction === Meta.MotionDirection.UP; ++ if (this.app == focusedApp) ++ this._cycleThroughWindows(reversed); ++ else { ++ // Activate the first window ++ let windows = this.getInterestingWindows(); ++ if (windows.length > 0) { ++ let w = windows[0]; ++ Main.activateWindow(w); ++ } ++ } ++ } ++ else ++ this.app.activate(); ++ return true; ++ }, ++ ++ onWindowsChanged: function() { ++ ++ if (this._menu && this._menu.isOpen) ++ this._menu.update(); ++ ++ this._indicator.update(); ++ this.updateIconGeometry(); ++ }, ++ ++ /** ++ * Update taraget for minimization animation ++ */ ++ updateIconGeometry: function() { ++ // If (for unknown reason) the actor is not on the stage the reported size ++ // and position are random values, which might exceeds the integer range ++ // resulting in an error when assigned to the a rect. This is a more like ++ // a workaround to prevent flooding the system with errors. ++ if (this.actor.get_stage() == null) ++ return; ++ ++ let rect = new Meta.Rectangle(); ++ ++ [rect.x, rect.y] = this.actor.get_transformed_position(); ++ [rect.width, rect.height] = this.actor.get_transformed_size(); ++ ++ let windows = this.app.get_windows(); ++ if (this._dtdSettings.get_boolean('multi-monitor')){ ++ let monitorIndex = this.monitorIndex; ++ windows = windows.filter(function(w) { ++ return w.get_monitor() == monitorIndex; ++ }); ++ } ++ windows.forEach(function(w) { ++ w.set_icon_geometry(rect); ++ }); ++ }, ++ ++ _updateRunningStyle: function() { ++ // The logic originally in this function has been moved to ++ // AppIconIndicatorBase._updateDefaultDot(). However it cannot be removed as ++ // it called by the parent constructor. ++ }, ++ ++ popupMenu: function() { ++ this._removeMenuTimeout(); ++ this.actor.fake_release(); ++ this._draggable.fakeRelease(); ++ ++ if (!this._menu) { ++ this._menu = new MyAppIconMenu(this, this._dtdSettings); ++ this._menu.connect('activate-window', Lang.bind(this, function(menu, window) { ++ this.activateWindow(window); ++ })); ++ this._menu.connect('open-state-changed', Lang.bind(this, function(menu, isPoppedUp) { ++ if (!isPoppedUp) ++ this._onMenuPoppedDown(); ++ else { ++ // Setting the max-height is s useful if part of the menu is ++ // scrollable so the minimum height is smaller than the natural height. ++ let monitor_index = Main.layoutManager.findIndexForActor(this.actor); ++ let workArea = Main.layoutManager.getWorkAreaForMonitor(monitor_index); ++ let position = Utils.getPosition(this._dtdSettings); ++ this._isHorizontal = ( position == St.Side.TOP || ++ position == St.Side.BOTTOM); ++ // If horizontal also remove the height of the dash ++ let additional_margin = this._isHorizontal && !this._dtdSettings.get_boolean('dock-fixed') ? Main.overview._dash.actor.height : 0; ++ let verticalMargins = this._menu.actor.margin_top + this._menu.actor.margin_bottom; ++ // Also set a max width to the menu, so long labels (long windows title) get truncated ++ this._menu.actor.style = ('max-height: ' + Math.round(workArea.height - additional_margin - verticalMargins) + 'px;' + ++ 'max-width: 400px'); ++ } ++ })); ++ let id = Main.overview.connect('hiding', Lang.bind(this, function() { ++ this._menu.close(); ++ })); ++ this._menu.actor.connect('destroy', function() { ++ Main.overview.disconnect(id); ++ }); ++ ++ this._menuManager.addMenu(this._menu); ++ } ++ ++ this.emit('menu-state-changed', true); ++ ++ this.actor.set_hover(true); ++ this._menu.popup(); ++ this._menuManager.ignoreRelease(); ++ this.emit('sync-tooltip'); ++ ++ return false; ++ }, ++ ++ _onFocusAppChanged: function() { ++ this._indicator.update(); ++ }, ++ ++ activate: function(button) { ++ let event = Clutter.get_current_event(); ++ let modifiers = event ? event.get_state() : 0; ++ let focusedApp = tracker.focus_app; ++ ++ // Only consider SHIFT and CONTROL as modifiers (exclude SUPER, CAPS-LOCK, etc.) ++ modifiers = modifiers & (Clutter.ModifierType.SHIFT_MASK | Clutter.ModifierType.CONTROL_MASK); ++ ++ // We don't change the CTRL-click behaviour: in such case we just chain ++ // up the parent method and return. ++ if (modifiers & Clutter.ModifierType.CONTROL_MASK) { ++ // Keep default behaviour: launch new window ++ // By calling the parent method I make it compatible ++ // with other extensions tweaking ctrl + click ++ this.parent(button); ++ return; ++ } ++ ++ // We check what type of click we have and if the modifier SHIFT is ++ // being used. We then define what buttonAction should be for this ++ // event. ++ let buttonAction = 0; ++ if (button && button == 2 ) { ++ if (modifiers & Clutter.ModifierType.SHIFT_MASK) ++ buttonAction = this._dtdSettings.get_enum('shift-middle-click-action'); ++ else ++ buttonAction = this._dtdSettings.get_enum('middle-click-action'); ++ } ++ else if (button && button == 1) { ++ if (modifiers & Clutter.ModifierType.SHIFT_MASK) ++ buttonAction = this._dtdSettings.get_enum('shift-click-action'); ++ else ++ buttonAction = this._dtdSettings.get_enum('click-action'); ++ } ++ ++ // We check if the app is running, and that the # of windows is > 0 in ++ // case we use workspace isolation. ++ let windows = this.getInterestingWindows(); ++ let appIsRunning = this.app.state == Shell.AppState.RUNNING ++ && windows.length > 0; ++ ++ // Some action modes (e.g. MINIMIZE_OR_OVERVIEW) require overview to remain open ++ // This variable keeps track of this ++ let shouldHideOverview = true; ++ ++ // We customize the action only when the application is already running ++ if (appIsRunning) { ++ switch (buttonAction) { ++ case clickAction.MINIMIZE: ++ // In overview just activate the app, unless the acion is explicitely ++ // requested with a keyboard modifier ++ if (!Main.overview._shown || modifiers){ ++ // If we have button=2 or a modifier, allow minimization even if ++ // the app is not focused ++ if (this.app == focusedApp || button == 2 || modifiers & Clutter.ModifierType.SHIFT_MASK) { ++ // minimize all windows on double click and always in the case of primary click without ++ // additional modifiers ++ let click_count = 0; ++ if (Clutter.EventType.CLUTTER_BUTTON_PRESS) ++ click_count = event.get_click_count(); ++ let all_windows = (button == 1 && ! modifiers) || click_count > 1; ++ this._minimizeWindow(all_windows); ++ } ++ else ++ this._activateAllWindows(); ++ } ++ else { ++ let w = windows[0]; ++ Main.activateWindow(w); ++ } ++ break; ++ ++ case clickAction.MINIMIZE_OR_OVERVIEW: ++ // When a single window is present, toggle minimization ++ // If only one windows is present toggle minimization, but only when trigggered with the ++ // simple click action (no modifiers, no middle click). ++ if (windows.length == 1 && !modifiers && button == 1) { ++ let w = windows[0]; ++ if (this.app == focusedApp) { ++ // Window is raised, minimize it ++ this._minimizeWindow(w); ++ } else { ++ // Window is minimized, raise it ++ Main.activateWindow(w); ++ } ++ // Launch overview when multiple windows are present ++ // TODO: only show current app windows when gnome shell API will allow it ++ } else { ++ shouldHideOverview = false; ++ Main.overview.toggle(); ++ } ++ break; ++ ++ case clickAction.CYCLE_WINDOWS: ++ if (!Main.overview._shown){ ++ if (this.app == focusedApp) ++ this._cycleThroughWindows(); ++ else { ++ // Activate the first window ++ let w = windows[0]; ++ Main.activateWindow(w); ++ } ++ } ++ else ++ this.app.activate(); ++ break; ++ ++ case clickAction.LAUNCH: ++ this.launchNewWindow(); ++ break; ++ ++ case clickAction.PREVIEWS: ++ if (!Main.overview._shown) { ++ // If only one windows is present just switch to it, but only when trigggered with the ++ // simple click action (no modifiers, no middle click). ++ if (windows.length == 1 && !modifiers && button == 1) { ++ let w = windows[0]; ++ Main.activateWindow(w); ++ } else ++ this._windowPreviews(); ++ } ++ else { ++ this.app.activate(); ++ } ++ break; ++ ++ case clickAction.MINIMIZE_OR_PREVIEWS: ++ // When a single window is present, toggle minimization ++ // If only one windows is present toggle minimization, but only when trigggered with the ++ // simple click action (no modifiers, no middle click). ++ if (!Main.overview._shown){ ++ if (windows.length == 1 && !modifiers && button == 1) { ++ let w = windows[0]; ++ if (this.app == focusedApp) { ++ // Window is raised, minimize it ++ this._minimizeWindow(w); ++ } else { ++ // Window is minimized, raise it ++ Main.activateWindow(w); ++ } ++ } else { ++ // Launch previews when multiple windows are present ++ this._windowPreviews(); ++ } ++ } else { ++ this.app.activate(); ++ } ++ break; ++ ++ case clickAction.QUIT: ++ this.closeAllWindows(); ++ break; ++ ++ case clickAction.SKIP: ++ let w = windows[0]; ++ Main.activateWindow(w); ++ break; ++ } ++ } ++ else { ++ this.launchNewWindow(); ++ } ++ ++ // Hide overview except when action mode requires it ++ if(shouldHideOverview) { ++ Main.overview.hide(); ++ } ++ }, ++ ++ shouldShowTooltip: function() { ++ return this.actor.hover && (!this._menu || !this._menu.isOpen) && ++ (!this._previewMenu || !this._previewMenu.isOpen); ++ }, ++ ++ _windowPreviews: function() { ++ if (!this._previewMenu) { ++ this._previewMenuManager = new PopupMenu.PopupMenuManager(this); ++ ++ this._previewMenu = new WindowPreview.WindowPreviewMenu(this, this._dtdSettings); ++ ++ this._previewMenuManager.addMenu(this._previewMenu); ++ ++ this._previewMenu.connect('open-state-changed', Lang.bind(this, function(menu, isPoppedUp) { ++ if (!isPoppedUp) ++ this._onMenuPoppedDown(); ++ })); ++ let id = Main.overview.connect('hiding', Lang.bind(this, function() { ++ this._previewMenu.close(); ++ })); ++ this._previewMenu.actor.connect('destroy', function() { ++ Main.overview.disconnect(id); ++ }); ++ ++ } ++ ++ if (this._previewMenu.isOpen) ++ this._previewMenu.close(); ++ else ++ this._previewMenu.popup(); ++ ++ return false; ++ }, ++ ++ // Try to do the right thing when attempting to launch a new window of an app. In ++ // particular, if the application doens't allow to launch a new window, activate ++ // the existing window instead. ++ launchNewWindow: function(p) { ++ let appInfo = this.app.get_app_info(); ++ let actions = appInfo.list_actions(); ++ if (this.app.can_open_new_window()) { ++ this.animateLaunch(); ++ // This is used as a workaround for a bug resulting in no new windows being opened ++ // for certain running applications when calling open_new_window(). ++ // ++ // https://bugzilla.gnome.org/show_bug.cgi?id=756844 ++ // ++ // Similar to what done when generating the popupMenu entries, if the application provides ++ // a "New Window" action, use it instead of directly requesting a new window with ++ // open_new_window(), which fails for certain application, notably Nautilus. ++ if (actions.indexOf('new-window') == -1) { ++ this.app.open_new_window(-1); ++ } ++ else { ++ let i = actions.indexOf('new-window'); ++ if (i !== -1) ++ this.app.launch_action(actions[i], global.get_current_time(), -1); ++ } ++ } ++ else { ++ // Try to manually activate the first window. Otherwise, when the app is activated by ++ // switching to a different workspace, a launch spinning icon is shown and disappers only ++ // after a timeout. ++ let windows = this.app.get_windows(); ++ if (windows.length > 0) ++ Main.activateWindow(windows[0]) ++ else ++ this.app.activate(); ++ } ++ }, ++ ++ _numberOverlay: function() { ++ // Add label for a Hot-Key visual aid ++ this._numberOverlayLabel = new St.Label(); ++ this._numberOverlayBin = new St.Bin({ ++ child: this._numberOverlayLabel, ++ x_align: St.Align.START, y_align: St.Align.START, ++ x_expand: true, y_expand: true ++ }); ++ this._numberOverlayLabel.add_style_class_name('number-overlay'); ++ this._numberOverlayOrder = -1; ++ this._numberOverlayBin.hide(); ++ ++ this._iconContainer.add_child(this._numberOverlayBin); ++ ++ }, ++ ++ updateNumberOverlay: function() { ++ // We apply an overall scale factor that might come from a HiDPI monitor. ++ // Clutter dimensions are in physical pixels, but CSS measures are in logical ++ // pixels, so make sure to consider the scale. ++ let scaleFactor = St.ThemeContext.get_for_stage(global.stage).scale_factor; ++ // Set the font size to something smaller than the whole icon so it is ++ // still visible. The border radius is large to make the shape circular ++ let [minWidth, natWidth] = this._iconContainer.get_preferred_width(-1); ++ let font_size = Math.round(Math.max(12, 0.3*natWidth) / scaleFactor); ++ let size = Math.round(font_size*1.2); ++ this._numberOverlayLabel.set_style( ++ 'font-size: ' + font_size + 'px;' + ++ 'border-radius: ' + this.icon.iconSize + 'px;' + ++ 'width: ' + size + 'px; height: ' + size +'px;' ++ ); ++ }, ++ ++ setNumberOverlay: function(number) { ++ this._numberOverlayOrder = number; ++ this._numberOverlayLabel.set_text(number.toString()); ++ }, ++ ++ toggleNumberOverlay: function(activate) { ++ if (activate && this._numberOverlayOrder > -1) { ++ this.updateNumberOverlay(); ++ this._numberOverlayBin.show(); ++ } ++ else ++ this._numberOverlayBin.hide(); ++ }, ++ ++ _minimizeWindow: function(param) { ++ // Param true make all app windows minimize ++ let windows = this.getInterestingWindows(); ++ let current_workspace = global.screen.get_active_workspace(); ++ for (let i = 0; i < windows.length; i++) { ++ let w = windows[i]; ++ if (w.get_workspace() == current_workspace && w.showing_on_its_workspace()) { ++ w.minimize(); ++ // Just minimize one window. By specification it should be the ++ // focused window on the current workspace. ++ if(!param) ++ break; ++ } ++ } ++ }, ++ ++ // By default only non minimized windows are activated. ++ // This activates all windows in the current workspace. ++ _activateAllWindows: function() { ++ // First activate first window so workspace is switched if needed. ++ // We don't do this if isolation is on! ++ if (!this._dtdSettings.get_boolean('isolate-workspaces') && ++ !this._dtdSettings.get_boolean('isolate-monitors')) ++ this.app.activate(); ++ ++ // then activate all other app windows in the current workspace ++ let windows = this.getInterestingWindows(); ++ let activeWorkspace = global.screen.get_active_workspace_index(); ++ ++ if (windows.length <= 0) ++ return; ++ ++ let activatedWindows = 0; ++ ++ for (let i = windows.length - 1; i >= 0; i--) { ++ if (windows[i].get_workspace().index() == activeWorkspace) { ++ Main.activateWindow(windows[i]); ++ activatedWindows++; ++ } ++ } ++ }, ++ ++ //This closes all windows of the app. ++ closeAllWindows: function() { ++ let windows = this.getInterestingWindows(); ++ for (let i = 0; i < windows.length; i++) ++ windows[i].delete(global.get_current_time()); ++ }, ++ ++ _cycleThroughWindows: function(reversed) { ++ // Store for a little amount of time last clicked app and its windows ++ // since the order changes upon window interaction ++ let MEMORY_TIME=3000; ++ ++ let app_windows = this.getInterestingWindows(); ++ ++ if (app_windows.length <1) ++ return ++ ++ if (recentlyClickedAppLoopId > 0) ++ Mainloop.source_remove(recentlyClickedAppLoopId); ++ recentlyClickedAppLoopId = Mainloop.timeout_add(MEMORY_TIME, this._resetRecentlyClickedApp); ++ ++ // If there isn't already a list of windows for the current app, ++ // or the stored list is outdated, use the current windows list. ++ let monitorIsolation = this._dtdSettings.get_boolean('isolate-monitors'); ++ if (!recentlyClickedApp || ++ recentlyClickedApp.get_id() != this.app.get_id() || ++ recentlyClickedAppWindows.length != app_windows.length || ++ (recentlyClickedAppMonitor != this.monitorIndex && monitorIsolation)) { ++ recentlyClickedApp = this.app; ++ recentlyClickedAppWindows = app_windows; ++ recentlyClickedAppMonitor = this.monitorIndex; ++ recentlyClickedAppIndex = 0; ++ } ++ ++ if (reversed) { ++ recentlyClickedAppIndex--; ++ if (recentlyClickedAppIndex < 0) recentlyClickedAppIndex = recentlyClickedAppWindows.length - 1; ++ } else { ++ recentlyClickedAppIndex++; ++ } ++ let index = recentlyClickedAppIndex % recentlyClickedAppWindows.length; ++ let window = recentlyClickedAppWindows[index]; ++ ++ Main.activateWindow(window); ++ }, ++ ++ _resetRecentlyClickedApp: function() { ++ if (recentlyClickedAppLoopId > 0) ++ Mainloop.source_remove(recentlyClickedAppLoopId); ++ recentlyClickedAppLoopId=0; ++ recentlyClickedApp =null; ++ recentlyClickedAppWindows = null; ++ recentlyClickedAppIndex = 0; ++ recentlyClickedAppMonitor = -1; ++ ++ return false; ++ }, ++ ++ // Filter out unnecessary windows, for instance ++ // nautilus desktop window. ++ getInterestingWindows: function() { ++ return getInterestingWindows(this.app, this._dtdSettings, this.monitorIndex); ++ } ++}); ++/** ++ * Extend AppIconMenu ++ * ++ * - Pass settings to the constructor ++ * - set popup arrow side based on dash orientation ++ * - Add close windows option based on quitfromdash extension ++ * (https://github.com/deuill/shell-extension-quitfromdash) ++ * - Add open windows thumbnails instead of list ++ * - update menu when application windows change ++ */ ++const MyAppIconMenu = new Lang.Class({ ++ Name: 'DashToDock.MyAppIconMenu', ++ Extends: AppDisplay.AppIconMenu, ++ ++ _init: function(source, settings) { ++ let side = Utils.getPosition(settings); ++ ++ // Damm it, there has to be a proper way of doing this... ++ // As I can't call the parent parent constructor (?) passing the side ++ // parameter, I overwite what I need later ++ this.parent(source); ++ ++ // Change the initialized side where required. ++ this._arrowSide = side; ++ this._boxPointer._arrowSide = side; ++ this._boxPointer._userArrowSide = side; ++ ++ this._dtdSettings = settings; ++ }, ++ ++ _redisplay: function() { ++ this.removeAll(); ++ ++ if (this._dtdSettings.get_boolean('show-windows-preview')) { ++ // Display the app windows menu items and the separator between windows ++ // of the current desktop and other windows. ++ ++ this._allWindowsMenuItem = new PopupMenu.PopupSubMenuMenuItem(__('All Windows'), false); ++ this._allWindowsMenuItem.actor.hide(); ++ this.addMenuItem(this._allWindowsMenuItem); ++ ++ if (!this._source.app.is_window_backed()) { ++ 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) { ++ this._newWindowMenuItem = this._appendMenuItem(_("New Window")); ++ this._newWindowMenuItem.connect('activate', Lang.bind(this, function() { ++ if (this._source.app.state == Shell.AppState.STOPPED) ++ this._source.animateLaunch(); ++ ++ this._source.app.open_new_window(-1); ++ this.emit('activate-window', null); ++ })); ++ this._appendSeparator(); ++ } ++ ++ ++ if (AppDisplay.discreteGpuAvailable && ++ this._source.app.state == Shell.AppState.STOPPED && ++ actions.indexOf('activate-discrete-gpu') == -1) { ++ this._onDiscreteGpuMenuItem = this._appendMenuItem(_("Launch using Dedicated Graphics Card")); ++ this._onDiscreteGpuMenuItem.connect('activate', Lang.bind(this, function() { ++ if (this._source.app.state == Shell.AppState.STOPPED) ++ this._source.animateLaunch(); ++ ++ this._source.app.launch(0, -1, true); ++ 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', Lang.bind(this, function(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) { ++ this._appendSeparator(); ++ ++ let isFavorite = AppFavorites.getAppFavorites().isFavorite(this._source.app.get_id()); ++ ++ if (isFavorite) { ++ let item = this._appendMenuItem(_("Remove from Favorites")); ++ item.connect('activate', Lang.bind(this, function() { ++ let favs = AppFavorites.getAppFavorites(); ++ favs.removeFavorite(this._source.app.get_id()); ++ })); ++ } else { ++ let item = this._appendMenuItem(_("Add to Favorites")); ++ item.connect('activate', Lang.bind(this, function() { ++ let favs = AppFavorites.getAppFavorites(); ++ favs.addFavorite(this._source.app.get_id()); ++ })); ++ } ++ } ++ ++ if (Shell.AppSystem.get_default().lookup_app('org.gnome.Software.desktop')) { ++ this._appendSeparator(); ++ let item = this._appendMenuItem(_("Show Details")); ++ item.connect('activate', Lang.bind(this, function() { ++ let id = this._source.app.get_id(); ++ let args = GLib.Variant.new('(ss)', [id, '']); ++ Gio.DBus.get(Gio.BusType.SESSION, null, ++ function(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(); ++ }); ++ })); ++ } ++ } ++ ++ } else { ++ this.parent(); ++ } ++ ++ // quit menu ++ this._appendSeparator(); ++ this._quitfromDashMenuItem = this._appendMenuItem(_("Quit")); ++ this._quitfromDashMenuItem.connect('activate', Lang.bind(this, function() { ++ this._source.closeAllWindows(); ++ })); ++ ++ this.update(); ++ }, ++ ++ // update menu content when application windows change. This is desirable as actions ++ // acting on windows (closing) are performed while the menu is shown. ++ update: function() { ++ ++ if(this._dtdSettings.get_boolean('show-windows-preview')){ ++ ++ let windows = this._source.getInterestingWindows(); ++ ++ // update, show or hide the quit menu ++ if ( windows.length > 0) { ++ let quitFromDashMenuText = ""; ++ if (windows.length == 1) ++ this._quitfromDashMenuItem.label.set_text(_("Quit")); ++ else ++ this._quitfromDashMenuItem.label.set_text(_("Quit") + ' ' + windows.length + ' ' + _("Windows")); ++ ++ this._quitfromDashMenuItem.actor.show(); ++ ++ } else { ++ this._quitfromDashMenuItem.actor.hide(); ++ } ++ ++ // update, show, or hide the allWindows menu ++ // Check if there are new windows not already displayed. In such case, repopulate the allWindows ++ // menu. Windows removal is already handled by each preview being connected to the destroy signal ++ let old_windows = this._allWindowsMenuItem.menu._getMenuItems().map(function(item){ ++ return item._window; ++ }); ++ ++ let new_windows = windows.filter(function(w) {return old_windows.indexOf(w) < 0;}); ++ if (new_windows.length > 0) { ++ this._populateAllWindowMenu(windows); ++ ++ // Try to set the width to that of the submenu. ++ // TODO: can't get the actual size, getting a bit less. ++ // Temporary workaround: add 15px to compensate ++ this._allWindowsMenuItem.actor.width = this._allWindowsMenuItem.menu.actor.width + 15; ++ ++ } ++ ++ // The menu is created hidden and never hidded after being shown. Instead, a singlal ++ // connected to its items destroy will set is insensitive if no more windows preview are shown. ++ if (windows.length > 0){ ++ this._allWindowsMenuItem.actor.show(); ++ this._allWindowsMenuItem.setSensitive(true); ++ } ++ ++ // Update separators ++ this._getMenuItems().forEach(Lang.bind(this, this._updateSeparatorVisibility)); ++ } ++ ++ ++ }, ++ ++ _populateAllWindowMenu: function(windows) { ++ ++ this._allWindowsMenuItem.menu.removeAll(); ++ ++ if (windows.length > 0) { ++ ++ let activeWorkspace = global.screen.get_active_workspace(); ++ let separatorShown = windows[0].get_workspace() != activeWorkspace; ++ ++ for (let i = 0; i < windows.length; i++) { ++ let window = windows[i]; ++ if (!separatorShown && window.get_workspace() != activeWorkspace) { ++ this._allWindowsMenuItem.menu.addMenuItem(new PopupMenu.PopupSeparatorMenuItem()); ++ separatorShown = true; ++ } ++ ++ let item = new WindowPreview.WindowPreviewMenuItem(window); ++ this._allWindowsMenuItem.menu.addMenuItem(item); ++ item.connect('activate', Lang.bind(this, function() { ++ this.emit('activate-window', window); ++ })); ++ ++ // This is to achieve a more gracefull transition when the last windows is closed. ++ item.connect('destroy', Lang.bind(this, function() { ++ if(this._allWindowsMenuItem.menu._getMenuItems().length == 1) // It's still counting the item just going to be destroyed ++ this._allWindowsMenuItem.setSensitive(false); ++ })); ++ } ++ } ++ }, ++}); ++Signals.addSignalMethods(MyAppIconMenu.prototype); ++ ++// Filter out unnecessary windows, for instance ++// nautilus desktop window. ++function getInterestingWindows(app, settings, monitorIndex) { ++ let windows = app.get_windows().filter(function(w) { ++ return !w.skip_taskbar; ++ }); ++ ++ // When using workspace isolation, we filter out windows ++ // that are not in the current workspace ++ if (settings.get_boolean('isolate-workspaces')) ++ windows = windows.filter(function(w) { ++ return w.get_workspace().index() == global.screen.get_active_workspace_index(); ++ }); ++ ++ if (settings.get_boolean('isolate-monitors')) ++ windows = windows.filter(function(w) { ++ return w.get_monitor() == monitorIndex; ++ }); ++ ++ return windows; ++} ++ ++/** ++ * A wrapper class around the ShowAppsIcon class. ++ * ++ * - Pass settings to the constructor ++ * - set label position based on dash orientation (Note, I am reusing most machinery of the appIcon class) ++ * - implement a popupMenu based on the AppIcon code (Note, I am reusing most machinery of the appIcon class) ++ * ++ * I can't subclass the original object because of this: https://bugzilla.gnome.org/show_bug.cgi?id=688973. ++ * thus use this pattern where the real showAppsIcon object is encaptulated, and a reference to it will be properly wired upon ++ * use of this class in place of the original showAppsButton. ++ * ++ */ ++ ++ var ShowAppsIconWrapper = new Lang.Class({ ++ Name: 'DashToDock.ShowAppsIconWrapper', ++ ++ _init: function(settings) { ++ this._dtdSettings = settings; ++ this.realShowAppsIcon = new Dash.ShowAppsIcon(); ++ ++ /* the variable equivalent to toggleButton has a different name in the appIcon class ++ (actor): duplicate reference to easily reuse appIcon methods */ ++ this.actor = this.realShowAppsIcon.toggleButton; ++ ++ // Re-use appIcon methods ++ this._removeMenuTimeout = AppDisplay.AppIcon.prototype._removeMenuTimeout; ++ this._setPopupTimeout = AppDisplay.AppIcon.prototype._setPopupTimeout; ++ this._onButtonPress = AppDisplay.AppIcon.prototype._onButtonPress; ++ this._onKeyboardPopupMenu = AppDisplay.AppIcon.prototype._onKeyboardPopupMenu; ++ this._onLeaveEvent = AppDisplay.AppIcon.prototype._onLeaveEvent; ++ this._onTouchEvent = AppDisplay.AppIcon.prototype._onTouchEvent; ++ this._onMenuPoppedDown = AppDisplay.AppIcon.prototype._onMenuPoppedDown; ++ ++ // No action on clicked (showing of the appsview is controlled elsewhere) ++ this._onClicked = Lang.bind(this, function(actor, button) { ++ this._removeMenuTimeout(); ++ }); ++ ++ this.actor.connect('leave-event', Lang.bind(this, this._onLeaveEvent)); ++ this.actor.connect('button-press-event', Lang.bind(this, this._onButtonPress)); ++ this.actor.connect('touch-event', Lang.bind(this, this._onTouchEvent)); ++ this.actor.connect('clicked', Lang.bind(this, this._onClicked)); ++ this.actor.connect('popup-menu', Lang.bind(this, this._onKeyboardPopupMenu)); ++ ++ this._menu = null; ++ this._menuManager = new PopupMenu.PopupMenuManager(this); ++ this._menuTimeoutId = 0; ++ ++ this.showLabel = itemShowLabel; ++ }, ++ ++ popupMenu: function() { ++ this._removeMenuTimeout(); ++ this.actor.fake_release(); ++ ++ if (!this._menu) { ++ this._menu = new MyShowAppsIconMenu(this, this._dtdSettings); ++ this._menu.connect('open-state-changed', Lang.bind(this, function(menu, isPoppedUp) { ++ if (!isPoppedUp) ++ this._onMenuPoppedDown(); ++ })); ++ let id = Main.overview.connect('hiding', Lang.bind(this, function() { ++ this._menu.close(); ++ })); ++ this._menu.actor.connect('destroy', function() { ++ Main.overview.disconnect(id); ++ }); ++ this._menuManager.addMenu(this._menu); ++ } ++ ++ //this.emit('menu-state-changed', true); ++ ++ this.actor.set_hover(true); ++ this._menu.popup(); ++ this._menuManager.ignoreRelease(); ++ this.emit('sync-tooltip'); ++ ++ return false; ++ } ++}); ++Signals.addSignalMethods(ShowAppsIconWrapper.prototype); ++ ++ ++/** ++ * A menu for the showAppsIcon ++ */ ++const MyShowAppsIconMenu = new Lang.Class({ ++ Name: 'DashToDock.ShowAppsIconMenu', ++ Extends: MyAppIconMenu, ++ ++ _redisplay: function() { ++ this.removeAll(); ++ ++ /* Translators: %s is "Settings", which is automatically translated. You ++ can also translate the full message if this fits better your language. */ ++ let name = __('Dash to Dock %s').format(_('Settings')) ++ let item = this._appendMenuItem(name); ++ ++ item.connect('activate', function () { ++ Util.spawn(["gnome-shell-extension-prefs", Me.metadata.uuid]); ++ }); ++ } ++}); ++ ++/** ++ * This function is used for both extendShowAppsIcon and extendDashItemContainer ++ */ ++function itemShowLabel() { ++ // Check if the label is still present at all. When switching workpaces, the ++ // item might have been destroyed in between. ++ if (!this._labelText || this.label.get_stage() == null) ++ return; ++ ++ this.label.set_text(this._labelText); ++ this.label.opacity = 0; ++ this.label.show(); ++ ++ let [stageX, stageY] = this.get_transformed_position(); ++ let node = this.label.get_theme_node(); ++ ++ let itemWidth = this.allocation.x2 - this.allocation.x1; ++ let itemHeight = this.allocation.y2 - this.allocation.y1; ++ ++ let labelWidth = this.label.get_width(); ++ let labelHeight = this.label.get_height(); ++ ++ let x, y, xOffset, yOffset; ++ ++ let position = Utils.getPosition(this._dtdSettings); ++ this._isHorizontal = ((position == St.Side.TOP) || (position == St.Side.BOTTOM)); ++ let labelOffset = node.get_length('-x-offset'); ++ ++ switch (position) { ++ case St.Side.LEFT: ++ yOffset = Math.floor((itemHeight - labelHeight) / 2); ++ y = stageY + yOffset; ++ xOffset = labelOffset; ++ x = stageX + this.get_width() + xOffset; ++ break; ++ case St.Side.RIGHT: ++ yOffset = Math.floor((itemHeight - labelHeight) / 2); ++ y = stageY + yOffset; ++ xOffset = labelOffset; ++ x = Math.round(stageX) - labelWidth - xOffset; ++ break; ++ case St.Side.TOP: ++ y = stageY + labelOffset + itemHeight; ++ xOffset = Math.floor((itemWidth - labelWidth) / 2); ++ x = stageX + xOffset; ++ break; ++ case St.Side.BOTTOM: ++ yOffset = labelOffset; ++ y = stageY - labelHeight - yOffset; ++ xOffset = Math.floor((itemWidth - labelWidth) / 2); ++ x = stageX + xOffset; ++ break; ++ } ++ ++ // keep the label inside the screen border ++ // Only needed fot the x coordinate. ++ ++ // Leave a few pixel gap ++ let gap = 5; ++ let monitor = Main.layoutManager.findMonitorForActor(this); ++ if (x - monitor.x < gap) ++ x += monitor.x - x + labelOffset; ++ else if (x + labelWidth > monitor.x + monitor.width - gap) ++ x -= x + labelWidth - (monitor.x + monitor.width) + gap; ++ ++ this.label.set_position(x, y); ++ Tweener.addTween(this.label, { ++ opacity: 255, ++ time: DASH_ITEM_LABEL_SHOW_TIME, ++ transition: 'easeOutQuad', ++ }); ++} +diff --git a/extensions/dash-to-dock/convenience.js b/extensions/dash-to-dock/convenience.js +new file mode 100644 +index 0000000..bc50419 +--- /dev/null ++++ b/extensions/dash-to-dock/convenience.js +@@ -0,0 +1,74 @@ ++/* -*- mode: js; js-basic-offset: 4; indent-tabs-mode: nil -*- */ ++ ++/* ++ * Part of this file comes from gnome-shell-extensions: ++ * https://gitlab.gnome.org/GNOME/gnome-shell-extensions/ ++ */ ++ ++const Gettext = imports.gettext; ++const Gio = imports.gi.Gio; ++ ++const Config = imports.misc.config; ++const ExtensionUtils = imports.misc.extensionUtils; ++ ++/** ++ * initTranslations: ++ * @domain: (optional): the gettext domain to use ++ * ++ * Initialize Gettext to load translations from extensionsdir/locale. ++ * If @domain is not provided, it will be taken from metadata['gettext-domain'] ++ */ ++function initTranslations(domain) { ++ let extension = ExtensionUtils.getCurrentExtension(); ++ ++ domain = domain || extension.metadata['gettext-domain']; ++ ++ // Check if this extension was built with "make zip-file", and thus ++ // has the locale files in a subfolder ++ // otherwise assume that extension has been installed in the ++ // same prefix as gnome-shell ++ let localeDir = extension.dir.get_child('locale'); ++ if (localeDir.query_exists(null)) ++ Gettext.bindtextdomain(domain, localeDir.get_path()); ++ else ++ Gettext.bindtextdomain(domain, Config.LOCALEDIR); ++} ++ ++/** ++ * getSettings: ++ * @schema: (optional): the GSettings schema id ++ * ++ * Builds and return a GSettings schema for @schema, using schema files ++ * in extensionsdir/schemas. If @schema is not provided, it is taken from ++ * metadata['settings-schema']. ++ */ ++function getSettings(schema) { ++ let extension = ExtensionUtils.getCurrentExtension(); ++ ++ schema = schema || extension.metadata['settings-schema']; ++ ++ const GioSSS = Gio.SettingsSchemaSource; ++ ++ // Check if this extension was built with "make zip-file", and thus ++ // has the schema files in a subfolder ++ // otherwise assume that extension has been installed in the ++ // same prefix as gnome-shell (and therefore schemas are available ++ // in the standard folders) ++ let schemaDir = extension.dir.get_child('schemas'); ++ let schemaSource; ++ if (schemaDir.query_exists(null)) ++ schemaSource = GioSSS.new_from_directory(schemaDir.get_path(), ++ GioSSS.get_default(), ++ false); ++ else ++ schemaSource = GioSSS.get_default(); ++ ++ let schemaObj = schemaSource.lookup(schema, true); ++ if (!schemaObj) ++ throw new Error('Schema ' + schema + ' could not be found for extension ' ++ + extension.metadata.uuid + '. Please check your installation.'); ++ ++ return new Gio.Settings({ ++ settings_schema: schemaObj ++ }); ++} +diff --git a/extensions/dash-to-dock/dash.js b/extensions/dash-to-dock/dash.js +new file mode 100644 +index 0000000..4cf5aa2 +--- /dev/null ++++ b/extensions/dash-to-dock/dash.js +@@ -0,0 +1,1175 @@ ++// -*- mode: js; js-indent-level: 4; indent-tabs-mode: nil -*- ++ ++const Clutter = imports.gi.Clutter; ++const Gio = imports.gi.Gio; ++const GLib = imports.gi.GLib; ++const Gtk = imports.gi.Gtk; ++const Signals = imports.signals; ++const Lang = imports.lang; ++const Meta = imports.gi.Meta; ++const Shell = imports.gi.Shell; ++const St = imports.gi.St; ++const Mainloop = imports.mainloop; ++ ++const AppDisplay = imports.ui.appDisplay; ++const AppFavorites = imports.ui.appFavorites; ++const Dash = imports.ui.dash; ++const DND = imports.ui.dnd; ++const IconGrid = imports.ui.iconGrid; ++const Main = imports.ui.main; ++const PopupMenu = imports.ui.popupMenu; ++const Tweener = imports.ui.tweener; ++const Util = imports.misc.util; ++const Workspace = imports.ui.workspace; ++ ++const Me = imports.misc.extensionUtils.getCurrentExtension(); ++const Utils = Me.imports.utils; ++const AppIcons = Me.imports.appIcons; ++ ++let DASH_ANIMATION_TIME = Dash.DASH_ANIMATION_TIME; ++let DASH_ITEM_LABEL_HIDE_TIME = Dash.DASH_ITEM_LABEL_HIDE_TIME; ++let DASH_ITEM_HOVER_TIMEOUT = Dash.DASH_ITEM_HOVER_TIMEOUT; ++ ++/** ++ * Extend DashItemContainer ++ * ++ * - Pass settings to the constructor ++ * - set label position based on dash orientation ++ * ++ * I can't subclass the original object because of this: https://bugzilla.gnome.org/show_bug.cgi?id=688973. ++ * thus use this ugly pattern. ++ */ ++function extendDashItemContainer(dashItemContainer, settings) { ++ dashItemContainer._dtdSettings = settings; ++ dashItemContainer.showLabel = AppIcons.itemShowLabel; ++} ++ ++/** ++ * This class is a fork of the upstream DashActor class (ui.dash.js) ++ * ++ * Summary of changes: ++ * - passed settings to class as parameter ++ * - modified chldBox calculations for when 'show-apps-at-top' option is checked ++ * - handle horizontal dash ++ */ ++const MyDashActor = new Lang.Class({ ++ Name: 'DashToDock.MyDashActor', ++ ++ _init: function(settings) { ++ // a prefix is required to avoid conflicting with the parent class variable ++ this._dtdSettings = settings; ++ this._rtl = (Clutter.get_default_text_direction() == Clutter.TextDirection.RTL); ++ ++ this._position = Utils.getPosition(settings); ++ this._isHorizontal = ((this._position == St.Side.TOP) || ++ (this._position == St.Side.BOTTOM)); ++ ++ let layout = new Clutter.BoxLayout({ ++ orientation: this._isHorizontal ? Clutter.Orientation.HORIZONTAL : Clutter.Orientation.VERTICAL ++ }); ++ ++ this.actor = new Shell.GenericContainer({ ++ name: 'dash', ++ layout_manager: layout, ++ clip_to_allocation: true ++ }); ++ this.actor.connect('get-preferred-width', Lang.bind(this, this._getPreferredWidth)); ++ this.actor.connect('get-preferred-height', Lang.bind(this, this._getPreferredHeight)); ++ this.actor.connect('allocate', Lang.bind(this, this._allocate)); ++ ++ this.actor._delegate = this; ++ }, ++ ++ _allocate: function(actor, box, flags) { ++ let contentBox = box; ++ let availWidth = contentBox.x2 - contentBox.x1; ++ let availHeight = contentBox.y2 - contentBox.y1; ++ ++ let [appIcons, showAppsButton] = actor.get_children(); ++ let [showAppsMinHeight, showAppsNatHeight] = showAppsButton.get_preferred_height(availWidth); ++ let [showAppsMinWidth, showAppsNatWidth] = showAppsButton.get_preferred_width(availHeight); ++ ++ let offset_x = this._isHorizontal?showAppsNatWidth:0; ++ let offset_y = this._isHorizontal?0:showAppsNatHeight; ++ ++ let childBox = new Clutter.ActorBox(); ++ if ((this._dtdSettings.get_boolean('show-apps-at-top') && !this._isHorizontal) ++ || (this._dtdSettings.get_boolean('show-apps-at-top') && !this._rtl) ++ || (!this._dtdSettings.get_boolean('show-apps-at-top') && this._isHorizontal && this._rtl)) { ++ childBox.x1 = contentBox.x1 + offset_x; ++ childBox.y1 = contentBox.y1 + offset_y; ++ childBox.x2 = contentBox.x2; ++ childBox.y2 = contentBox.y2; ++ appIcons.allocate(childBox, flags); ++ ++ childBox.y1 = contentBox.y1; ++ childBox.x1 = contentBox.x1; ++ childBox.x2 = contentBox.x1 + showAppsNatWidth; ++ childBox.y2 = contentBox.y1 + showAppsNatHeight; ++ showAppsButton.allocate(childBox, flags); ++ } ++ else { ++ childBox.x1 = contentBox.x1; ++ childBox.y1 = contentBox.y1; ++ childBox.x2 = contentBox.x2 - offset_x; ++ childBox.y2 = contentBox.y2 - offset_y; ++ appIcons.allocate(childBox, flags); ++ ++ childBox.x2 = contentBox.x2; ++ childBox.y2 = contentBox.y2; ++ childBox.x1 = contentBox.x2 - showAppsNatWidth; ++ childBox.y1 = contentBox.y2 - showAppsNatHeight; ++ showAppsButton.allocate(childBox, flags); ++ } ++ }, ++ ++ _getPreferredWidth: function(actor, forHeight, alloc) { ++ // We want to request the natural height of all our children ++ // as our natural height, so we chain up to StWidget (which ++ // then calls BoxLayout), but we only request the showApps ++ // button as the minimum size ++ ++ let [, natWidth] = this.actor.layout_manager.get_preferred_width(this.actor, forHeight); ++ ++ let themeNode = this.actor.get_theme_node(); ++ let [, showAppsButton] = this.actor.get_children(); ++ let [minWidth, ] = showAppsButton.get_preferred_height(forHeight); ++ ++ alloc.min_size = minWidth; ++ alloc.natural_size = natWidth; ++ ++ }, ++ ++ _getPreferredHeight: function(actor, forWidth, alloc) { ++ // We want to request the natural height of all our children ++ // as our natural height, so we chain up to StWidget (which ++ // then calls BoxLayout), but we only request the showApps ++ // button as the minimum size ++ ++ let [, natHeight] = this.actor.layout_manager.get_preferred_height(this.actor, forWidth); ++ ++ let themeNode = this.actor.get_theme_node(); ++ let [, showAppsButton] = this.actor.get_children(); ++ let [minHeight, ] = showAppsButton.get_preferred_height(forWidth); ++ ++ alloc.min_size = minHeight; ++ alloc.natural_size = natHeight; ++ } ++}); ++ ++const baseIconSizes = [16, 22, 24, 32, 48, 64, 96, 128]; ++ ++/** ++ * This class is a fork of the upstream dash class (ui.dash.js) ++ * ++ * Summary of changes: ++ * - disconnect global signals adding a destroy method; ++ * - play animations even when not in overview mode ++ * - set a maximum icon size ++ * - show running and/or favorite applications ++ * - emit a custom signal when an app icon is added ++ * - hide showApps label when the custom menu is shown. ++ * - add scrollview ++ * ensure actor is visible on keyfocus inseid the scrollview ++ * - add 128px icon size, might be usefull for hidpi display ++ * - sync minimization application target position. ++ * - keep running apps ordered. ++ */ ++var MyDash = new Lang.Class({ ++ Name: 'DashToDock.MyDash', ++ ++ _init: function(settings, remoteModel, monitorIndex) { ++ this._dtdSettings = settings; ++ ++ // Initialize icon variables and size ++ this._maxHeight = -1; ++ this.iconSize = this._dtdSettings.get_int('dash-max-icon-size'); ++ this._availableIconSizes = baseIconSizes; ++ this._shownInitially = false; ++ this._initializeIconSize(this.iconSize); ++ ++ this._remoteModel = remoteModel; ++ this._monitorIndex = monitorIndex; ++ this._position = Utils.getPosition(settings); ++ this._isHorizontal = ((this._position == St.Side.TOP) || ++ (this._position == St.Side.BOTTOM)); ++ this._signalsHandler = new Utils.GlobalSignalsHandler(); ++ ++ this._dragPlaceholder = null; ++ this._dragPlaceholderPos = -1; ++ this._animatingPlaceholdersCount = 0; ++ this._showLabelTimeoutId = 0; ++ this._resetHoverTimeoutId = 0; ++ this._ensureAppIconVisibilityTimeoutId = 0; ++ this._labelShowing = false; ++ ++ this._containerObject = new MyDashActor(settings); ++ this._container = this._containerObject.actor; ++ this._scrollView = new St.ScrollView({ ++ name: 'dashtodockDashScrollview', ++ hscrollbar_policy: Gtk.PolicyType.NEVER, ++ vscrollbar_policy: Gtk.PolicyType.NEVER, ++ enable_mouse_scrolling: false ++ }); ++ ++ this._scrollView.connect('scroll-event', Lang.bind(this, this._onScrollEvent)); ++ ++ this._box = new St.BoxLayout({ ++ vertical: !this._isHorizontal, ++ clip_to_allocation: false, ++ x_align: Clutter.ActorAlign.START, ++ y_align: Clutter.ActorAlign.START ++ }); ++ this._box._delegate = this; ++ this._container.add_actor(this._scrollView); ++ this._scrollView.add_actor(this._box); ++ ++ // Create a wrapper around the real showAppsIcon in order to add a popupMenu. ++ let showAppsIconWrapper = new AppIcons.ShowAppsIconWrapper(this._dtdSettings); ++ showAppsIconWrapper.connect('menu-state-changed', Lang.bind(this, function(showAppsIconWrapper, opened) { ++ this._itemMenuStateChanged(showAppsIconWrapper, opened); ++ })); ++ // an instance of the showAppsIcon class is encapsulated in the wrapper ++ this._showAppsIcon = showAppsIconWrapper.realShowAppsIcon; ++ ++ this._showAppsIcon.childScale = 1; ++ this._showAppsIcon.childOpacity = 255; ++ this._showAppsIcon.icon.setIconSize(this.iconSize); ++ this._hookUpLabel(this._showAppsIcon); ++ ++ this.showAppsButton = this._showAppsIcon.toggleButton; ++ ++ this._container.add_actor(this._showAppsIcon); ++ ++ let rtl = Clutter.get_default_text_direction() == Clutter.TextDirection.RTL; ++ this.actor = new St.Bin({ ++ child: this._container, ++ y_align: St.Align.START, ++ x_align: rtl ? St.Align.END : St.Align.START ++ }); ++ ++ if (this._isHorizontal) { ++ this.actor.connect('notify::width', Lang.bind(this, function() { ++ if (this._maxHeight != this.actor.width) ++ this._queueRedisplay(); ++ this._maxHeight = this.actor.width; ++ })); ++ } ++ else { ++ this.actor.connect('notify::height', Lang.bind(this, function() { ++ if (this._maxHeight != this.actor.height) ++ this._queueRedisplay(); ++ this._maxHeight = this.actor.height; ++ })); ++ } ++ ++ // Update minimization animation target position on allocation of the ++ // container and on scrollview change. ++ this._box.connect('notify::allocation', Lang.bind(this, this._updateAppsIconGeometry)); ++ let scrollViewAdjustment = this._isHorizontal ? this._scrollView.hscroll.adjustment : this._scrollView.vscroll.adjustment; ++ scrollViewAdjustment.connect('notify::value', Lang.bind(this, this._updateAppsIconGeometry)); ++ ++ this._workId = Main.initializeDeferredWork(this._box, Lang.bind(this, this._redisplay)); ++ ++ this._settings = new Gio.Settings({ ++ schema_id: 'org.gnome.shell' ++ }); ++ ++ this._appSystem = Shell.AppSystem.get_default(); ++ ++ this._signalsHandler.add([ ++ this._appSystem, ++ 'installed-changed', ++ Lang.bind(this, function() { ++ AppFavorites.getAppFavorites().reload(); ++ this._queueRedisplay(); ++ }) ++ ], [ ++ AppFavorites.getAppFavorites(), ++ 'changed', ++ Lang.bind(this, this._queueRedisplay) ++ ], [ ++ this._appSystem, ++ 'app-state-changed', ++ Lang.bind(this, this._queueRedisplay) ++ ], [ ++ Main.overview, ++ 'item-drag-begin', ++ Lang.bind(this, this._onDragBegin) ++ ], [ ++ Main.overview, ++ 'item-drag-end', ++ Lang.bind(this, this._onDragEnd) ++ ], [ ++ Main.overview, ++ 'item-drag-cancelled', ++ Lang.bind(this, this._onDragCancelled) ++ ]); ++ }, ++ ++ destroy: function() { ++ this._signalsHandler.destroy(); ++ }, ++ ++ _onScrollEvent: function(actor, event) { ++ // If scroll is not used because the icon is resized, let the scroll event propagate. ++ if (!this._dtdSettings.get_boolean('icon-size-fixed')) ++ return Clutter.EVENT_PROPAGATE; ++ ++ // reset timeout to avid conflicts with the mousehover event ++ if (this._ensureAppIconVisibilityTimeoutId > 0) { ++ Mainloop.source_remove(this._ensureAppIconVisibilityTimeoutId); ++ this._ensureAppIconVisibilityTimeoutId = 0; ++ } ++ ++ // Skip to avoid double events mouse ++ if (event.is_pointer_emulated()) ++ return Clutter.EVENT_STOP; ++ ++ let adjustment, delta; ++ ++ if (this._isHorizontal) ++ adjustment = this._scrollView.get_hscroll_bar().get_adjustment(); ++ else ++ adjustment = this._scrollView.get_vscroll_bar().get_adjustment(); ++ ++ let increment = adjustment.step_increment; ++ ++ switch (event.get_scroll_direction()) { ++ case Clutter.ScrollDirection.UP: ++ delta = -increment; ++ break; ++ case Clutter.ScrollDirection.DOWN: ++ delta = +increment; ++ break; ++ case Clutter.ScrollDirection.SMOOTH: ++ let [dx, dy] = event.get_scroll_delta(); ++ delta = dy * increment; ++ // Also consider horizontal component, for instance touchpad ++ if (this._isHorizontal) ++ delta += dx * increment; ++ break; ++ } ++ ++ adjustment.set_value(adjustment.get_value() + delta); ++ ++ return Clutter.EVENT_STOP; ++ }, ++ ++ _onDragBegin: function() { ++ this._dragCancelled = false; ++ this._dragMonitor = { ++ dragMotion: Lang.bind(this, this._onDragMotion) ++ }; ++ DND.addDragMonitor(this._dragMonitor); ++ ++ if (this._box.get_n_children() == 0) { ++ this._emptyDropTarget = new Dash.EmptyDropTargetItem(); ++ this._box.insert_child_at_index(this._emptyDropTarget, 0); ++ this._emptyDropTarget.show(true); ++ } ++ }, ++ ++ _onDragCancelled: function() { ++ this._dragCancelled = true; ++ this._endDrag(); ++ }, ++ ++ _onDragEnd: function() { ++ if (this._dragCancelled) ++ return; ++ ++ this._endDrag(); ++ }, ++ ++ _endDrag: function() { ++ this._clearDragPlaceholder(); ++ this._clearEmptyDropTarget(); ++ this._showAppsIcon.setDragApp(null); ++ DND.removeDragMonitor(this._dragMonitor); ++ }, ++ ++ _onDragMotion: function(dragEvent) { ++ let app = Dash.getAppFromSource(dragEvent.source); ++ if (app == null) ++ return DND.DragMotionResult.CONTINUE; ++ ++ let showAppsHovered = this._showAppsIcon.contains(dragEvent.targetActor); ++ ++ if (!this._box.contains(dragEvent.targetActor) || showAppsHovered) ++ this._clearDragPlaceholder(); ++ ++ if (showAppsHovered) ++ this._showAppsIcon.setDragApp(app); ++ else ++ this._showAppsIcon.setDragApp(null); ++ ++ return DND.DragMotionResult.CONTINUE; ++ }, ++ ++ _appIdListToHash: function(apps) { ++ let ids = {}; ++ for (let i = 0; i < apps.length; i++) ++ ids[apps[i].get_id()] = apps[i]; ++ return ids; ++ }, ++ ++ _queueRedisplay: function() { ++ Main.queueDeferredWork(this._workId); ++ }, ++ ++ _hookUpLabel: function(item, appIcon) { ++ item.child.connect('notify::hover', Lang.bind(this, function() { ++ this._syncLabel(item, appIcon); ++ })); ++ ++ let id = Main.overview.connect('hiding', Lang.bind(this, function() { ++ this._labelShowing = false; ++ item.hideLabel(); ++ })); ++ item.child.connect('destroy', function() { ++ Main.overview.disconnect(id); ++ }); ++ ++ if (appIcon) { ++ appIcon.connect('sync-tooltip', Lang.bind(this, function() { ++ this._syncLabel(item, appIcon); ++ })); ++ } ++ }, ++ ++ _createAppItem: function(app) { ++ let appIcon = new AppIcons.MyAppIcon(this._dtdSettings, this._remoteModel, app, this._monitorIndex, ++ { setSizeManually: true, ++ showLabel: false }); ++ ++ if (appIcon._draggable) { ++ appIcon._draggable.connect('drag-begin', Lang.bind(this, function() { ++ appIcon.actor.opacity = 50; ++ })); ++ appIcon._draggable.connect('drag-end', Lang.bind(this, function() { ++ appIcon.actor.opacity = 255; ++ })); ++ } ++ ++ appIcon.connect('menu-state-changed', Lang.bind(this, function(appIcon, opened) { ++ this._itemMenuStateChanged(item, opened); ++ })); ++ ++ let item = new Dash.DashItemContainer(); ++ ++ extendDashItemContainer(item, this._dtdSettings); ++ item.setChild(appIcon.actor); ++ ++ appIcon.actor.connect('notify::hover', Lang.bind(this, function() { ++ if (appIcon.actor.hover) { ++ this._ensureAppIconVisibilityTimeoutId = Mainloop.timeout_add(100, Lang.bind(this, function() { ++ ensureActorVisibleInScrollView(this._scrollView, appIcon.actor); ++ this._ensureAppIconVisibilityTimeoutId = 0; ++ return GLib.SOURCE_REMOVE; ++ })); ++ } ++ else { ++ if (this._ensureAppIconVisibilityTimeoutId > 0) { ++ Mainloop.source_remove(this._ensureAppIconVisibilityTimeoutId); ++ this._ensureAppIconVisibilityTimeoutId = 0; ++ } ++ } ++ })); ++ ++ appIcon.actor.connect('clicked', Lang.bind(this, function(actor) { ++ ensureActorVisibleInScrollView(this._scrollView, actor); ++ })); ++ ++ appIcon.actor.connect('key-focus-in', Lang.bind(this, function(actor) { ++ let [x_shift, y_shift] = ensureActorVisibleInScrollView(this._scrollView, actor); ++ ++ // This signal is triggered also by mouse click. The popup menu is opened at the original ++ // coordinates. Thus correct for the shift which is going to be applied to the scrollview. ++ if (appIcon._menu) { ++ appIcon._menu._boxPointer.xOffset = -x_shift; ++ appIcon._menu._boxPointer.yOffset = -y_shift; ++ } ++ })); ++ ++ // Override default AppIcon label_actor, now the ++ // accessible_name is set at DashItemContainer.setLabelText ++ appIcon.actor.label_actor = null; ++ item.setLabelText(app.get_name()); ++ ++ appIcon.icon.setIconSize(this.iconSize); ++ this._hookUpLabel(item, appIcon); ++ ++ return item; ++ }, ++ ++ /** ++ * Return an array with the "proper" appIcons currently in the dash ++ */ ++ getAppIcons: function() { ++ // Only consider children which are "proper" ++ // icons (i.e. ignoring drag placeholders) and which are not ++ // animating out (which means they will be destroyed at the end of ++ // the animation) ++ let iconChildren = this._box.get_children().filter(function(actor) { ++ return actor.child && ++ actor.child._delegate && ++ actor.child._delegate.icon && ++ !actor.animatingOut; ++ }); ++ ++ let appIcons = iconChildren.map(function(actor) { ++ return actor.child._delegate; ++ }); ++ ++ return appIcons; ++ }, ++ ++ _updateAppsIconGeometry: function() { ++ let appIcons = this.getAppIcons(); ++ appIcons.forEach(function(icon) { ++ icon.updateIconGeometry(); ++ }); ++ }, ++ ++ _itemMenuStateChanged: function(item, opened) { ++ // When the menu closes, it calls sync_hover, which means ++ // that the notify::hover handler does everything we need to. ++ if (opened) { ++ if (this._showLabelTimeoutId > 0) { ++ Mainloop.source_remove(this._showLabelTimeoutId); ++ this._showLabelTimeoutId = 0; ++ } ++ ++ item.hideLabel(); ++ } ++ else { ++ // I want to listen from outside when a menu is closed. I used to ++ // add a custom signal to the appIcon, since gnome 3.8 the signal ++ // calling this callback was added upstream. ++ this.emit('menu-closed'); ++ } ++ }, ++ ++ _syncLabel: function(item, appIcon) { ++ let shouldShow = appIcon ? appIcon.shouldShowTooltip() : item.child.get_hover(); ++ ++ if (shouldShow) { ++ if (this._showLabelTimeoutId == 0) { ++ let timeout = this._labelShowing ? 0 : DASH_ITEM_HOVER_TIMEOUT; ++ this._showLabelTimeoutId = Mainloop.timeout_add(timeout, Lang.bind(this, function() { ++ this._labelShowing = true; ++ item.showLabel(); ++ this._showLabelTimeoutId = 0; ++ return GLib.SOURCE_REMOVE; ++ })); ++ GLib.Source.set_name_by_id(this._showLabelTimeoutId, '[gnome-shell] item.showLabel'); ++ if (this._resetHoverTimeoutId > 0) { ++ Mainloop.source_remove(this._resetHoverTimeoutId); ++ this._resetHoverTimeoutId = 0; ++ } ++ } ++ } ++ else { ++ if (this._showLabelTimeoutId > 0) ++ Mainloop.source_remove(this._showLabelTimeoutId); ++ this._showLabelTimeoutId = 0; ++ item.hideLabel(); ++ if (this._labelShowing) { ++ this._resetHoverTimeoutId = Mainloop.timeout_add(DASH_ITEM_HOVER_TIMEOUT, Lang.bind(this, function() { ++ this._labelShowing = false; ++ this._resetHoverTimeoutId = 0; ++ return GLib.SOURCE_REMOVE; ++ })); ++ GLib.Source.set_name_by_id(this._resetHoverTimeoutId, '[gnome-shell] this._labelShowing'); ++ } ++ } ++ }, ++ ++ _adjustIconSize: function() { ++ // For the icon size, we only consider children which are "proper" ++ // icons (i.e. ignoring drag placeholders) and which are not ++ // animating out (which means they will be destroyed at the end of ++ // the animation) ++ let iconChildren = this._box.get_children().filter(function(actor) { ++ return actor.child && ++ actor.child._delegate && ++ actor.child._delegate.icon && ++ !actor.animatingOut; ++ }); ++ ++ iconChildren.push(this._showAppsIcon); ++ ++ if (this._maxHeight == -1) ++ return; ++ ++ // Check if the container is present in the stage. This avoids critical ++ // errors when unlocking the screen ++ if (!this._container.get_stage()) ++ return; ++ ++ let themeNode = this._container.get_theme_node(); ++ let maxAllocation = new Clutter.ActorBox({ ++ x1: 0, ++ y1: 0, ++ x2: this._isHorizontal ? this._maxHeight : 42 /* whatever */, ++ y2: this._isHorizontal ? 42 : this._maxHeight ++ }); ++ let maxContent = themeNode.get_content_box(maxAllocation); ++ let availHeight; ++ if (this._isHorizontal) ++ availHeight = maxContent.x2 - maxContent.x1; ++ else ++ availHeight = maxContent.y2 - maxContent.y1; ++ let spacing = themeNode.get_length('spacing'); ++ ++ let firstButton = iconChildren[0].child; ++ let firstIcon = firstButton._delegate.icon; ++ ++ let minHeight, natHeight, minWidth, natWidth; ++ ++ // Enforce the current icon size during the size request ++ firstIcon.setIconSize(this.iconSize); ++ [minHeight, natHeight] = firstButton.get_preferred_height(-1); ++ [minWidth, natWidth] = firstButton.get_preferred_width(-1); ++ ++ let scaleFactor = St.ThemeContext.get_for_stage(global.stage).scale_factor; ++ let iconSizes = this._availableIconSizes.map(function(s) { ++ return s * scaleFactor; ++ }); ++ ++ // Subtract icon padding and box spacing from the available height ++ if (this._isHorizontal) ++ availHeight -= iconChildren.length * (natWidth - this.iconSize * scaleFactor) + ++ (iconChildren.length - 1) * spacing; ++ else ++ availHeight -= iconChildren.length * (natHeight - this.iconSize * scaleFactor) + ++ (iconChildren.length - 1) * spacing; ++ ++ let availSize = availHeight / iconChildren.length; ++ ++ ++ let newIconSize = this._availableIconSizes[0]; ++ for (let i = 0; i < iconSizes.length; i++) { ++ if (iconSizes[i] < availSize) ++ newIconSize = this._availableIconSizes[i]; ++ } ++ ++ if (newIconSize == this.iconSize) ++ return; ++ ++ let oldIconSize = this.iconSize; ++ this.iconSize = newIconSize; ++ this.emit('icon-size-changed'); ++ ++ let scale = oldIconSize / newIconSize; ++ for (let i = 0; i < iconChildren.length; i++) { ++ let icon = iconChildren[i].child._delegate.icon; ++ ++ // Set the new size immediately, to keep the icons' sizes ++ // in sync with this.iconSize ++ icon.setIconSize(this.iconSize); ++ ++ // Don't animate the icon size change when the overview ++ // is transitioning, or when initially filling ++ // the dash ++ if (Main.overview.animationInProgress || ++ !this._shownInitially) ++ continue; ++ ++ let [targetWidth, targetHeight] = icon.icon.get_size(); ++ ++ // Scale the icon's texture to the previous size and ++ // tween to the new size ++ icon.icon.set_size(icon.icon.width * scale, ++ icon.icon.height * scale); ++ ++ Tweener.addTween(icon.icon, ++ { width: targetWidth, ++ height: targetHeight, ++ time: DASH_ANIMATION_TIME, ++ transition: 'easeOutQuad', ++ }); ++ } ++ }, ++ ++ _redisplay: function() { ++ let favorites = AppFavorites.getAppFavorites().getFavoriteMap(); ++ ++ let running = this._appSystem.get_running(); ++ if (this._dtdSettings.get_boolean('isolate-workspaces') || ++ this._dtdSettings.get_boolean('isolate-monitors')) { ++ // When using isolation, we filter out apps that have no windows in ++ // the current workspace ++ let settings = this._dtdSettings; ++ let monitorIndex = this._monitorIndex; ++ running = running.filter(function(_app) { ++ return AppIcons.getInterestingWindows(_app, settings, monitorIndex).length != 0; ++ }); ++ } ++ ++ let children = this._box.get_children().filter(function(actor) { ++ return actor.child && ++ actor.child._delegate && ++ actor.child._delegate.app; ++ }); ++ // Apps currently in the dash ++ let oldApps = children.map(function(actor) { ++ return actor.child._delegate.app; ++ }); ++ // Apps supposed to be in the dash ++ let newApps = []; ++ ++ if (this._dtdSettings.get_boolean('show-favorites')) { ++ for (let id in favorites) ++ newApps.push(favorites[id]); ++ } ++ ++ // We reorder the running apps so that they don't change position on the ++ // dash with every redisplay() call ++ if (this._dtdSettings.get_boolean('show-running')) { ++ // First: add the apps from the oldApps list that are still running ++ for (let i = 0; i < oldApps.length; i++) { ++ let index = running.indexOf(oldApps[i]); ++ if (index > -1) { ++ let app = running.splice(index, 1)[0]; ++ if (this._dtdSettings.get_boolean('show-favorites') && (app.get_id() in favorites)) ++ continue; ++ newApps.push(app); ++ } ++ } ++ // Second: add the new apps ++ for (let i = 0; i < running.length; i++) { ++ let app = running[i]; ++ if (this._dtdSettings.get_boolean('show-favorites') && (app.get_id() in favorites)) ++ continue; ++ newApps.push(app); ++ } ++ } ++ ++ // Figure out the actual changes to the list of items; we iterate ++ // over both the list of items currently in the dash and the list ++ // of items expected there, and collect additions and removals. ++ // Moves are both an addition and a removal, where the order of ++ // the operations depends on whether we encounter the position ++ // where the item has been added first or the one from where it ++ // was removed. ++ // There is an assumption that only one item is moved at a given ++ // time; when moving several items at once, everything will still ++ // end up at the right position, but there might be additional ++ // additions/removals (e.g. it might remove all the launchers ++ // and add them back in the new order even if a smaller set of ++ // additions and removals is possible). ++ // If above assumptions turns out to be a problem, we might need ++ // to use a more sophisticated algorithm, e.g. Longest Common ++ // Subsequence as used by diff. ++ ++ let addedItems = []; ++ let removedActors = []; ++ ++ let newIndex = 0; ++ let oldIndex = 0; ++ while ((newIndex < newApps.length) || (oldIndex < oldApps.length)) { ++ // No change at oldIndex/newIndex ++ if (oldApps[oldIndex] && oldApps[oldIndex] == newApps[newIndex]) { ++ oldIndex++; ++ newIndex++; ++ continue; ++ } ++ ++ // App removed at oldIndex ++ if (oldApps[oldIndex] && (newApps.indexOf(oldApps[oldIndex]) == -1)) { ++ removedActors.push(children[oldIndex]); ++ oldIndex++; ++ continue; ++ } ++ ++ // App added at newIndex ++ if (newApps[newIndex] && (oldApps.indexOf(newApps[newIndex]) == -1)) { ++ let newItem = this._createAppItem(newApps[newIndex]); ++ addedItems.push({ app: newApps[newIndex], ++ item: newItem, ++ pos: newIndex }); ++ newIndex++; ++ continue; ++ } ++ ++ // App moved ++ let insertHere = newApps[newIndex + 1] && (newApps[newIndex + 1] == oldApps[oldIndex]); ++ let alreadyRemoved = removedActors.reduce(function(result, actor) { ++ let removedApp = actor.child._delegate.app; ++ return result || removedApp == newApps[newIndex]; ++ }, false); ++ ++ if (insertHere || alreadyRemoved) { ++ let newItem = this._createAppItem(newApps[newIndex]); ++ addedItems.push({ ++ app: newApps[newIndex], ++ item: newItem, ++ pos: newIndex + removedActors.length ++ }); ++ newIndex++; ++ } ++ else { ++ removedActors.push(children[oldIndex]); ++ oldIndex++; ++ } ++ } ++ ++ for (let i = 0; i < addedItems.length; i++) ++ this._box.insert_child_at_index(addedItems[i].item, ++ addedItems[i].pos); ++ ++ for (let i = 0; i < removedActors.length; i++) { ++ let item = removedActors[i]; ++ ++ // Don't animate item removal when the overview is transitioning ++ if (!Main.overview.animationInProgress) ++ item.animateOutAndDestroy(); ++ else ++ item.destroy(); ++ } ++ ++ this._adjustIconSize(); ++ ++ for (let i = 0; i < addedItems.length; i++) ++ // Emit a custom signal notifying that a new item has been added ++ this.emit('item-added', addedItems[i]); ++ ++ // Skip animations on first run when adding the initial set ++ // of items, to avoid all items zooming in at once ++ ++ let animate = this._shownInitially && ++ !Main.overview.animationInProgress; ++ ++ if (!this._shownInitially) ++ this._shownInitially = true; ++ ++ for (let i = 0; i < addedItems.length; i++) ++ addedItems[i].item.show(animate); ++ ++ // Workaround for https://bugzilla.gnome.org/show_bug.cgi?id=692744 ++ // Without it, StBoxLayout may use a stale size cache ++ this._box.queue_relayout(); ++ ++ // This is required for icon reordering when the scrollview is used. ++ this._updateAppsIconGeometry(); ++ ++ // This will update the size, and the corresponding number for each icon ++ this._updateNumberOverlay(); ++ }, ++ ++ _updateNumberOverlay: function() { ++ let appIcons = this.getAppIcons(); ++ let counter = 1; ++ appIcons.forEach(function(icon) { ++ if (counter < 10){ ++ icon.setNumberOverlay(counter); ++ counter++; ++ } ++ else if (counter == 10) { ++ icon.setNumberOverlay(0); ++ counter++; ++ } ++ else { ++ // No overlay after 10 ++ icon.setNumberOverlay(-1); ++ } ++ icon.updateNumberOverlay(); ++ }); ++ ++ }, ++ ++ toggleNumberOverlay: function(activate) { ++ let appIcons = this.getAppIcons(); ++ appIcons.forEach(function(icon) { ++ icon.toggleNumberOverlay(activate); ++ }); ++ }, ++ ++ _initializeIconSize: function(max_size) { ++ let max_allowed = baseIconSizes[baseIconSizes.length-1]; ++ max_size = Math.min(max_size, max_allowed); ++ ++ if (this._dtdSettings.get_boolean('icon-size-fixed')) ++ this._availableIconSizes = [max_size]; ++ else { ++ this._availableIconSizes = baseIconSizes.filter(function(val) { ++ return (val numChildren) ++ pos = numChildren; ++ } ++ else ++ pos = 0; // always insert at the top when dash is empty ++ ++ // Take into account childredn position in rtl ++ if (this._isHorizontal && (Clutter.get_default_text_direction() == Clutter.TextDirection.RTL)) ++ pos = numChildren - pos; ++ ++ if ((pos != this._dragPlaceholderPos) && (pos <= numFavorites) && (this._animatingPlaceholdersCount == 0)) { ++ this._dragPlaceholderPos = pos; ++ ++ // Don't allow positioning before or after self ++ if ((favPos != -1) && (pos == favPos || pos == favPos + 1)) { ++ this._clearDragPlaceholder(); ++ return DND.DragMotionResult.CONTINUE; ++ } ++ ++ // If the placeholder already exists, we just move ++ // it, but if we are adding it, expand its size in ++ // an animation ++ let fadeIn; ++ if (this._dragPlaceholder) { ++ this._dragPlaceholder.destroy(); ++ fadeIn = false; ++ } ++ else ++ fadeIn = true; ++ ++ this._dragPlaceholder = new Dash.DragPlaceholderItem(); ++ this._dragPlaceholder.child.set_width (this.iconSize); ++ this._dragPlaceholder.child.set_height (this.iconSize / 2); ++ this._box.insert_child_at_index(this._dragPlaceholder, ++ this._dragPlaceholderPos); ++ this._dragPlaceholder.show(fadeIn); ++ // Ensure the next and previous icon are visible when moving the placeholder ++ // (I assume there's room for both of them) ++ if (this._dragPlaceholderPos > 1) ++ ensureActorVisibleInScrollView(this._scrollView, this._box.get_children()[this._dragPlaceholderPos-1]); ++ if (this._dragPlaceholderPos < this._box.get_children().length-1) ++ ensureActorVisibleInScrollView(this._scrollView, this._box.get_children()[this._dragPlaceholderPos+1]); ++ } ++ ++ // Remove the drag placeholder if we are not in the ++ // "favorites zone" ++ if (pos > numFavorites) ++ this._clearDragPlaceholder(); ++ ++ if (!this._dragPlaceholder) ++ return DND.DragMotionResult.NO_DROP; ++ ++ let srcIsFavorite = (favPos != -1); ++ ++ if (srcIsFavorite) ++ return DND.DragMotionResult.MOVE_DROP; ++ ++ return DND.DragMotionResult.COPY_DROP; ++ }, ++ ++ /** ++ * Draggable target interface ++ */ ++ acceptDrop: function(source, actor, x, y, time) { ++ let app = Dash.getAppFromSource(source); ++ ++ // Don't allow favoriting of transient apps ++ if (app == null || app.is_window_backed()) ++ return false; ++ ++ if (!this._settings.is_writable('favorite-apps') || !this._dtdSettings.get_boolean('show-favorites')) ++ return false; ++ ++ let id = app.get_id(); ++ ++ let favorites = AppFavorites.getAppFavorites().getFavoriteMap(); ++ ++ let srcIsFavorite = (id in favorites); ++ ++ let favPos = 0; ++ let children = this._box.get_children(); ++ for (let i = 0; i < this._dragPlaceholderPos; i++) { ++ if (this._dragPlaceholder && (children[i] == this._dragPlaceholder)) ++ continue; ++ ++ let childId = children[i].child._delegate.app.get_id(); ++ if (childId == id) ++ continue; ++ if (childId in favorites) ++ favPos++; ++ } ++ ++ // No drag placeholder means we don't wan't to favorite the app ++ // and we are dragging it to its original position ++ if (!this._dragPlaceholder) ++ return true; ++ ++ Meta.later_add(Meta.LaterType.BEFORE_REDRAW, Lang.bind(this, function() { ++ let appFavorites = AppFavorites.getAppFavorites(); ++ if (srcIsFavorite) ++ appFavorites.moveFavoriteToPos(id, favPos); ++ else ++ appFavorites.addFavoriteAtPos(id, favPos); ++ return false; ++ })); ++ ++ return true; ++ }, ++ ++ showShowAppsButton: function() { ++ this.showAppsButton.visible = true ++ this.showAppsButton.set_width(-1) ++ this.showAppsButton.set_height(-1) ++ }, ++ ++ hideShowAppsButton: function() { ++ this.showAppsButton.hide() ++ this.showAppsButton.set_width(0) ++ this.showAppsButton.set_height(0) ++ } ++}); ++ ++Signals.addSignalMethods(MyDash.prototype); ++ ++/** ++ * This is a copy of the same function in utils.js, but also adjust horizontal scrolling ++ * and perform few further cheks on the current value to avoid changing the values when ++ * it would be clamp to the current one in any case. ++ * Return the amount of shift applied ++ */ ++function ensureActorVisibleInScrollView(scrollView, actor) { ++ let adjust_v = true; ++ let adjust_h = true; ++ ++ let vadjustment = scrollView.vscroll.adjustment; ++ let hadjustment = scrollView.hscroll.adjustment; ++ let [vvalue, vlower, vupper, vstepIncrement, vpageIncrement, vpageSize] = vadjustment.get_values(); ++ let [hvalue, hlower, hupper, hstepIncrement, hpageIncrement, hpageSize] = hadjustment.get_values(); ++ ++ let [hvalue0, vvalue0] = [hvalue, vvalue]; ++ ++ let voffset = 0; ++ let hoffset = 0; ++ let fade = scrollView.get_effect('fade'); ++ if (fade) { ++ voffset = fade.vfade_offset; ++ hoffset = fade.hfade_offset; ++ } ++ ++ let box = actor.get_allocation_box(); ++ let y1 = box.y1, y2 = box.y2, x1 = box.x1, x2 = box.x2; ++ ++ let parent = actor.get_parent(); ++ while (parent != scrollView) { ++ if (!parent) ++ throw new Error('Actor not in scroll view'); ++ ++ let box = parent.get_allocation_box(); ++ y1 += box.y1; ++ y2 += box.y1; ++ x1 += box.x1; ++ x2 += box.x1; ++ parent = parent.get_parent(); ++ } ++ ++ if (y1 < vvalue + voffset) ++ vvalue = Math.max(0, y1 - voffset); ++ else if (vvalue < vupper - vpageSize && y2 > vvalue + vpageSize - voffset) ++ vvalue = Math.min(vupper -vpageSize, y2 + voffset - vpageSize); ++ ++ if (x1 < hvalue + hoffset) ++ hvalue = Math.max(0, x1 - hoffset); ++ else if (hvalue < hupper - hpageSize && x2 > hvalue + hpageSize - hoffset) ++ hvalue = Math.min(hupper - hpageSize, x2 + hoffset - hpageSize); ++ ++ if (vvalue !== vvalue0) { ++ Tweener.addTween(vadjustment, { value: vvalue, ++ time: Util.SCROLL_TIME, ++ transition: 'easeOutQuad' ++ }); ++ } ++ ++ if (hvalue !== hvalue0) { ++ Tweener.addTween(hadjustment, ++ { value: hvalue, ++ time: Util.SCROLL_TIME, ++ transition: 'easeOutQuad' }); ++ } ++ ++ return [hvalue- hvalue0, vvalue - vvalue0]; ++} +diff --git a/extensions/dash-to-dock/docking.js b/extensions/dash-to-dock/docking.js +new file mode 100644 +index 0000000..11810a1 +--- /dev/null ++++ b/extensions/dash-to-dock/docking.js +@@ -0,0 +1,1925 @@ ++// -*- mode: js; js-indent-level: 4; indent-tabs-mode: nil -*- ++ ++const Clutter = imports.gi.Clutter; ++const GLib = imports.gi.GLib; ++const Gtk = imports.gi.Gtk; ++const Lang = imports.lang; ++const Meta = imports.gi.Meta; ++const Shell = imports.gi.Shell; ++const St = imports.gi.St; ++const Mainloop = imports.mainloop; ++const Params = imports.misc.params; ++ ++const Main = imports.ui.main; ++const Dash = imports.ui.dash; ++const IconGrid = imports.ui.iconGrid; ++const Overview = imports.ui.overview; ++const OverviewControls = imports.ui.overviewControls; ++const PointerWatcher = imports.ui.pointerWatcher; ++const Tweener = imports.ui.tweener; ++const Signals = imports.signals; ++const ViewSelector = imports.ui.viewSelector; ++const WorkspaceSwitcherPopup= imports.ui.workspaceSwitcherPopup; ++const Layout = imports.ui.layout; ++const LayoutManager = imports.ui.main.layoutManager; ++ ++const Me = imports.misc.extensionUtils.getCurrentExtension(); ++const Convenience = Me.imports.convenience; ++const Utils = Me.imports.utils; ++const Intellihide = Me.imports.intellihide; ++const Theming = Me.imports.theming; ++const MyDash = Me.imports.dash; ++const LauncherAPI = Me.imports.launcherAPI; ++ ++const DOCK_DWELL_CHECK_INTERVAL = 100; ++ ++var State = { ++ HIDDEN: 0, ++ SHOWING: 1, ++ SHOWN: 2, ++ HIDING: 3 ++}; ++ ++const scrollAction = { ++ DO_NOTHING: 0, ++ CYCLE_WINDOWS: 1, ++ SWITCH_WORKSPACE: 2 ++}; ++ ++/** ++ * A simple St.Widget with one child whose allocation takes into account the ++ * slide out of its child via the _slidex parameter ([0:1]). ++ * ++ * Required since I want to track the input region of this container which is ++ * based on its allocation even if the child overlows the parent actor. By doing ++ * this the region of the dash that is slideout is not steling anymore the input ++ * regions making the extesion usable when the primary monitor is the right one. ++ * ++ * The slidex parameter can be used to directly animate the sliding. The parent ++ * must have a WEST (SOUTH) anchor_point to achieve the sliding to the RIGHT (BOTTOM) ++ * side. ++ * ++ * It can't be an extended object because of this: https://bugzilla.gnome.org/show_bug.cgi?id=688973. ++ * thus use the Shell.GenericContainer pattern. ++*/ ++const DashSlideContainer = new Lang.Class({ ++ Name: 'DashToDock.DashSlideContainer', ++ ++ _init: function(params) { ++ // Default local params ++ let localDefaults = { ++ side: St.Side.LEFT, ++ initialSlideValue: 1 ++ } ++ ++ let localParams = Params.parse(params, localDefaults, true); ++ ++ if (params) { ++ // Remove local params before passing the params to the parent ++ // constructor to avoid errors. ++ let prop; ++ for (prop in localDefaults) { ++ if ((prop in params)) ++ delete params[prop]; ++ } ++ } ++ ++ this.actor = new Shell.GenericContainer(params); ++ this.actor.connect('get-preferred-width', Lang.bind(this, this._getPreferredWidth)); ++ this.actor.connect('get-preferred-height', Lang.bind(this, this._getPreferredHeight)); ++ this.actor.connect('allocate', Lang.bind(this, this._allocate)); ++ ++ this.actor._delegate = this; ++ ++ this._child = null; ++ ++ // slide parameter: 1 = visible, 0 = hidden. ++ this._slidex = localParams.initialSlideValue; ++ this._side = localParams.side; ++ this._slideoutSize = 0; // minimum size when slided out ++ }, ++ ++ _allocate: function(actor, box, flags) { ++ if (this._child == null) ++ return; ++ ++ let availWidth = box.x2 - box.x1; ++ let availHeight = box.y2 - box.y1; ++ let [minChildWidth, minChildHeight, natChildWidth, natChildHeight] = ++ this._child.get_preferred_size(); ++ ++ let childWidth = natChildWidth; ++ let childHeight = natChildHeight; ++ ++ let childBox = new Clutter.ActorBox(); ++ ++ let slideoutSize = this._slideoutSize; ++ ++ if (this._side == St.Side.LEFT) { ++ childBox.x1 = (this._slidex -1) * (childWidth - slideoutSize); ++ childBox.x2 = slideoutSize + this._slidex*(childWidth - slideoutSize); ++ childBox.y1 = 0; ++ childBox.y2 = childBox.y1 + childHeight; ++ } ++ else if ((this._side == St.Side.RIGHT) || (this._side == St.Side.BOTTOM)) { ++ childBox.x1 = 0; ++ childBox.x2 = childWidth; ++ childBox.y1 = 0; ++ childBox.y2 = childBox.y1 + childHeight; ++ } ++ else if (this._side == St.Side.TOP) { ++ childBox.x1 = 0; ++ childBox.x2 = childWidth; ++ childBox.y1 = (this._slidex -1) * (childHeight - slideoutSize); ++ childBox.y2 = slideoutSize + this._slidex * (childHeight - slideoutSize); ++ } ++ ++ this._child.allocate(childBox, flags); ++ this._child.set_clip(-childBox.x1, -childBox.y1, ++ -childBox.x1+availWidth, -childBox.y1 + availHeight); ++ }, ++ ++ /** ++ * Just the child width but taking into account the slided out part ++ */ ++ _getPreferredWidth: function(actor, forHeight, alloc) { ++ let [minWidth, natWidth] = this._child.get_preferred_width(forHeight); ++ if ((this._side == St.Side.LEFT) || (this._side == St.Side.RIGHT)) { ++ minWidth = (minWidth - this._slideoutSize) * this._slidex + this._slideoutSize; ++ natWidth = (natWidth - this._slideoutSize) * this._slidex + this._slideoutSize; ++ } ++ ++ alloc.min_size = minWidth; ++ alloc.natural_size = natWidth; ++ }, ++ ++ /** ++ * Just the child height but taking into account the slided out part ++ */ ++ _getPreferredHeight: function(actor, forWidth, alloc) { ++ let [minHeight, natHeight] = this._child.get_preferred_height(forWidth); ++ if ((this._side == St.Side.TOP) || (this._side == St.Side.BOTTOM)) { ++ minHeight = (minHeight - this._slideoutSize) * this._slidex + this._slideoutSize; ++ natHeight = (natHeight - this._slideoutSize) * this._slidex + this._slideoutSize; ++ } ++ alloc.min_size = minHeight; ++ alloc.natural_size = natHeight; ++ }, ++ ++ /** ++ * I was expecting it to be a virtual function... stil I don't understand ++ * how things work. ++ */ ++ add_child: function(actor) { ++ // I'm supposed to have only on child ++ if (this._child !== null) ++ this.actor.remove_child(actor); ++ ++ this._child = actor; ++ this.actor.add_child(actor); ++ }, ++ ++ set slidex(value) { ++ this._slidex = value; ++ this._child.queue_relayout(); ++ }, ++ ++ get slidex() { ++ return this._slidex; ++ } ++}); ++ ++const DockedDash = new Lang.Class({ ++ Name: 'DashToDock.DockedDash', ++ ++ _init: function(settings, remoteModel, monitorIndex) { ++ this._rtl = (Clutter.get_default_text_direction() == Clutter.TextDirection.RTL); ++ ++ // Load settings ++ this._settings = settings; ++ this._remoteModel = remoteModel; ++ this._monitorIndex = monitorIndex; ++ // Connect global signals ++ this._signalsHandler = new Utils.GlobalSignalsHandler(); ++ ++ this._bindSettingsChanges(); ++ ++ this._position = Utils.getPosition(settings); ++ this._isHorizontal = ((this._position == St.Side.TOP) || (this._position == St.Side.BOTTOM)); ++ ++ // Temporary ignore hover events linked to autohide for whatever reason ++ this._ignoreHover = false; ++ this._oldignoreHover = null; ++ // This variables are linked to the settings regardles of autohide or intellihide ++ // being temporary disable. Get set by _updateVisibilityMode; ++ this._autohideIsEnabled = null; ++ this._intellihideIsEnabled = null; ++ this._fixedIsEnabled = null; ++ ++ // Create intellihide object to monitor windows overlapping ++ this._intellihide = new Intellihide.Intellihide(this._settings, this._monitorIndex); ++ ++ // initialize dock state ++ this._dockState = State.HIDDEN; ++ ++ // Put dock on the required monitor ++ this._monitor = Main.layoutManager.monitors[this._monitorIndex]; ++ ++ // this store size and the position where the dash is shown; ++ // used by intellihide module to check window overlap. ++ this.staticBox = new Clutter.ActorBox(); ++ ++ // Initialize pressure barrier variables ++ this._canUsePressure = false; ++ this._pressureBarrier = null; ++ this._barrier = null; ++ this._removeBarrierTimeoutId = 0; ++ ++ // Initialize dwelling system variables ++ this._dockDwelling = false; ++ this._dockWatch = null; ++ this._dockDwellUserTime = 0; ++ this._dockDwellTimeoutId = 0 ++ ++ // 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')) ++ this.dash.hideShowAppsButton(); ++ ++ // Create the main actor and the containers for sliding in and out and ++ // centering, turn on track hover ++ ++ let positionStyleClass = ['top', 'right', 'bottom', 'left']; ++ // This is the centering actor ++ this.actor = new St.Bin({ ++ name: 'dashtodockContainer', ++ reactive: false, ++ style_class: positionStyleClass[this._position], ++ x_align: this._isHorizontal?St.Align.MIDDLE:St.Align.START, ++ y_align: this._isHorizontal?St.Align.START:St.Align.MIDDLE ++ }); ++ this.actor._delegate = this; ++ ++ // This is the sliding actor whose allocation is to be tracked for input regions ++ this._slider = new DashSlideContainer({ ++ side: this._position, ++ initialSlideValue: 0 ++ }); ++ ++ // This is the actor whose hover status us tracked for autohide ++ this._box = new St.BoxLayout({ ++ name: 'dashtodockBox', ++ reactive: true, ++ track_hover: true ++ }); ++ this._box.connect('notify::hover', Lang.bind(this, this._hoverChanged)); ++ ++ // Create and apply height constraint to the dash. It's controlled by this.actor height ++ this.constrainSize = new Clutter.BindConstraint({ ++ source: this.actor, ++ coordinate: this._isHorizontal?Clutter.BindCoordinate.WIDTH:Clutter.BindCoordinate.HEIGHT ++ }); ++ this.dash.actor.add_constraint(this.constrainSize); ++ ++ this._signalsHandler.add([ ++ Main.overview, ++ 'item-drag-begin', ++ Lang.bind(this, this._onDragStart) ++ ], [ ++ Main.overview, ++ 'item-drag-end', ++ Lang.bind(this, this._onDragEnd) ++ ], [ ++ Main.overview, ++ 'item-drag-cancelled', ++ Lang.bind(this, this._onDragEnd) ++ ], [ ++ // update when workarea changes, for instance if other extensions modify the struts ++ //(like moving th panel at the bottom) ++ global.screen, ++ 'workareas-changed', ++ Lang.bind(this, this._resetPosition) ++ ], [ ++ Main.overview, ++ 'showing', ++ Lang.bind(this, this._onOverviewShowing) ++ ], [ ++ Main.overview, ++ 'hiding', ++ Lang.bind(this, this._onOverviewHiding) ++ ], [ ++ // Hide on appview ++ Main.overview.viewSelector, ++ 'page-changed', ++ Lang.bind(this, this._pageChanged) ++ ], [ ++ Main.overview.viewSelector, ++ 'page-empty', ++ Lang.bind(this, this._onPageEmpty) ++ ], [ ++ // Ensure the ShowAppsButton status is kept in sync ++ Main.overview.viewSelector._showAppsButton, ++ 'notify::checked', ++ Lang.bind(this, this._syncShowAppsButtonToggled) ++ ], [ ++ global.screen, ++ 'in-fullscreen-changed', ++ Lang.bind(this, this._updateBarrier) ++ ], [ ++ // Monitor windows overlapping ++ this._intellihide, ++ 'status-changed', ++ Lang.bind(this, this._updateDashVisibility) ++ ], [ ++ // Keep dragged icon consistent in size with this dash ++ this.dash, ++ 'icon-size-changed', ++ Lang.bind(this, function() { ++ 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', ++ Lang.bind(this, function() { ++ Main.overview.dashIconSize = this.dash.iconSize; ++ }) ++ ]); ++ ++ this._injectionsHandler = new Utils.InjectionsHandler(); ++ this._themeManager = new Theming.ThemeManager(this._settings, this); ++ ++ // Since the actor is not a topLevel child and its parent is now not added to the Chrome, ++ // the allocation change of the parent container (slide in and slideout) doesn't trigger ++ // anymore an update of the input regions. Force the update manually. ++ this.actor.connect('notify::allocation', ++ Lang.bind(Main.layoutManager, Main.layoutManager._queueUpdateRegions)); ++ ++ this.dash._container.connect('allocation-changed', Lang.bind(this, this._updateStaticBox)); ++ this._slider.actor.connect(this._isHorizontal ? 'notify::x' : 'notify::y', Lang.bind(this, this._updateStaticBox)); ++ ++ // sync hover after a popupmenu is closed ++ this.dash.connect('menu-closed', Lang.bind(this, function() { ++ this._box.sync_hover(); ++ })); ++ ++ // Load optional features that need to be activated for one dock only ++ if (this._monitorIndex == this._settings.get_int('preferred-monitor')) ++ this._enableExtraFeatures(); ++ // Load optional features that need to be activated once per dock ++ this._optionalScrollWorkspaceSwitch(); ++ ++ // Delay operations that require the shell to be fully loaded and with ++ // user theme applied. ++ ++ this._paintId = this.actor.connect('paint', Lang.bind(this, this._initialize)); ++ ++ // Manage the which is used to reserve space in the overview for the dock ++ // Add and additional dashSpacer positioned according to the dash positioning. ++ // It gets restored on extension unload. ++ 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); ++ ++ // Add dash container actor and the container to the Chrome. ++ this.actor.set_child(this._slider.actor); ++ this._slider.add_child(this._box); ++ this._box.add_actor(this.dash.actor); ++ ++ // Add aligning container without tracking it for input region ++ Main.uiGroup.add_child(this.actor); ++ ++ if (this._settings.get_boolean('dock-fixed')) { ++ // Note: tracking the fullscreen directly on the slider actor causes some hiccups when fullscreening ++ // windows of certain applications ++ Main.layoutManager._trackActor(this.actor, {affectsInputRegion: false, trackFullscreen: true}); ++ Main.layoutManager._trackActor(this._slider.actor, {affectsStruts: true}); ++ } ++ else ++ Main.layoutManager._trackActor(this._slider.actor); ++ ++ // Set initial position ++ this._resetDepth(); ++ this._resetPosition(); ++ }, ++ ++ _initialize: function() { ++ if (this._paintId > 0) { ++ this.actor.disconnect(this._paintId); ++ this._paintId=0; ++ } ++ ++ // Apply custome css class according to the settings ++ this._themeManager.updateCustomTheme(); ++ ++ // 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) ++ Main.overview.viewSelector._activePage = Main.overview.viewSelector._workspacesPage; ++ ++ this._updateVisibilityMode(); ++ ++ // In case we are already inside the overview when the extension is loaded, ++ // for instance on unlocking the screen if it was locked with the overview open. ++ if (Main.overview.visibleTarget) { ++ this._onOverviewShowing(); ++ this._pageChanged(); ++ } ++ ++ // Setup pressure barrier (GS38+ only) ++ this._updatePressureBarrier(); ++ this._updateBarrier(); ++ ++ // setup dwelling system if pressure barriers are not available ++ this._setupDockDwellIfNeeded(); ++ }, ++ ++ destroy: function() { ++ // Disconnect global signals ++ this._signalsHandler.destroy(); ++ // The dash, intellihide and themeManager have global signals as well internally ++ this.dash.destroy(); ++ this._intellihide.destroy(); ++ this._themeManager.destroy(); ++ ++ this._injectionsHandler.destroy(); ++ ++ // Destroy main clutter actor: this should be sufficient removing it and ++ // destroying all its children ++ this.actor.destroy(); ++ ++ // Remove barrier timeout ++ if (this._removeBarrierTimeoutId > 0) ++ Mainloop.source_remove(this._removeBarrierTimeoutId); ++ ++ // Remove existing barrier ++ this._removeBarrier(); ++ ++ // Remove pointer watcher ++ if (this._dockWatch) { ++ PointerWatcher.getPointerWatcher()._removeWatch(this._dockWatch); ++ this._dockWatch = null; ++ } ++ ++ // Remove the dashSpacer ++ this._dashSpacer.destroy(); ++ ++ // Restore legacyTray position ++ this._resetLegacyTray(); ++ ++ }, ++ ++ _bindSettingsChanges: function() { ++ this._signalsHandler.add([ ++ this._settings, ++ 'changed::scroll-action', ++ Lang.bind(this, function() { ++ this._optionalScrollWorkspaceSwitch(); ++ }) ++ ], [ ++ this._settings, ++ 'changed::dash-max-icon-size', ++ Lang.bind(this, function() { ++ this.dash.setIconSize(this._settings.get_int('dash-max-icon-size')); ++ }) ++ ], [ ++ this._settings, ++ 'changed::icon-size-fixed', ++ Lang.bind(this, function() { ++ this.dash.setIconSize(this._settings.get_int('dash-max-icon-size')); ++ }) ++ ], [ ++ this._settings, ++ 'changed::show-favorites', ++ Lang.bind(this, function() { ++ this.dash.resetAppIcons(); ++ }) ++ ], [ ++ this._settings, ++ 'changed::show-running', ++ Lang.bind(this, function() { ++ this.dash.resetAppIcons(); ++ }) ++ ], [ ++ this._settings, ++ 'changed::show-apps-at-top', ++ Lang.bind(this, function() { ++ this.dash.resetAppIcons(); ++ }) ++ ], [ ++ this._settings, ++ 'changed::show-show-apps-button', ++ Lang.bind(this, function() { ++ if (this._settings.get_boolean('show-show-apps-button')) ++ this.dash.showShowAppsButton(); ++ else ++ this.dash.hideShowAppsButton(); ++ }) ++ ], [ ++ this._settings, ++ 'changed::dock-fixed', ++ Lang.bind(this, function() { ++ if (this._settings.get_boolean('dock-fixed')) { ++ Main.layoutManager._untrackActor(this.actor); ++ Main.layoutManager._trackActor(this.actor, {affectsInputRegion: false, trackFullscreen: true}); ++ Main.layoutManager._untrackActor(this._slider.actor); ++ Main.layoutManager._trackActor(this._slider.actor, {affectsStruts: true}); ++ } else { ++ Main.layoutManager._untrackActor(this.actor); ++ Main.layoutManager._untrackActor(this._slider.actor); ++ Main.layoutManager._trackActor(this._slider.actor); ++ } ++ ++ this._resetPosition(); ++ ++ // Add or remove barrier depending on if dock-fixed ++ this._updateBarrier(); ++ ++ this._updateVisibilityMode(); ++ }) ++ ], [ ++ this._settings, ++ 'changed::intellihide', ++ Lang.bind(this, this._updateVisibilityMode) ++ ], [ ++ this._settings, ++ 'changed::intellihide-mode', ++ Lang.bind(this, function() { ++ this._intellihide.forceUpdate(); ++ }) ++ ], [ ++ this._settings, ++ 'changed::autohide', ++ Lang.bind(this, function() { ++ this._updateVisibilityMode(); ++ this._updateBarrier(); ++ }) ++ ], [ ++ this._settings, ++ 'changed::autohide-in-fullscreen', ++ Lang.bind(this, this._updateBarrier) ++ ], ++ [ ++ this._settings, ++ 'changed::extend-height', ++ Lang.bind(this, this._resetPosition) ++ ], [ ++ this._settings, ++ 'changed::height-fraction', ++ Lang.bind(this, this._resetPosition) ++ ], [ ++ this._settings, ++ 'changed::require-pressure-to-show', ++ Lang.bind(this, function() { ++ // Remove pointer watcher ++ if (this._dockWatch) { ++ PointerWatcher.getPointerWatcher()._removeWatch(this._dockWatch); ++ this._dockWatch = null; ++ } ++ this._setupDockDwellIfNeeded(); ++ this._updateBarrier(); ++ }) ++ ], [ ++ this._settings, ++ 'changed::pressure-threshold', ++ Lang.bind(this, function() { ++ this._updatePressureBarrier(); ++ this._updateBarrier(); ++ }) ++ ]); ++ ++ }, ++ ++ /** ++ * This is call when visibility settings change ++ */ ++ _updateVisibilityMode: function() { ++ if (this._settings.get_boolean('dock-fixed')) { ++ this._fixedIsEnabled = true; ++ this._autohideIsEnabled = false; ++ this._intellihideIsEnabled = false; ++ } ++ else { ++ this._fixedIsEnabled = false; ++ this._autohideIsEnabled = this._settings.get_boolean('autohide') ++ this._intellihideIsEnabled = this._settings.get_boolean('intellihide') ++ } ++ ++ if (this._intellihideIsEnabled) ++ this._intellihide.enable(); ++ else ++ this._intellihide.disable(); ++ ++ this._updateDashVisibility(); ++ }, ++ ++ /** ++ * Show/hide dash based on, in order of priority: ++ * overview visibility ++ * fixed mode ++ * intellihide ++ * autohide ++ * overview visibility ++ */ ++ _updateDashVisibility: function() { ++ if (Main.overview.visibleTarget) ++ return; ++ ++ if (this._fixedIsEnabled) { ++ this._removeAnimations(); ++ this._animateIn(this._settings.get_double('animation-time'), 0); ++ } ++ else if (this._intellihideIsEnabled) { ++ if (this._intellihide.getOverlapStatus()) { ++ this._ignoreHover = false; ++ // Do not hide if autohide is enabled and mouse is hover ++ if (!this._box.hover || !this._autohideIsEnabled) ++ this._animateOut(this._settings.get_double('animation-time'), 0); ++ } ++ else { ++ this._ignoreHover = true; ++ this._removeAnimations(); ++ this._animateIn(this._settings.get_double('animation-time'), 0); ++ } ++ } ++ else { ++ if (this._autohideIsEnabled) { ++ this._ignoreHover = false; ++ global.sync_pointer(); ++ ++ if (this._box.hover) ++ this._animateIn(this._settings.get_double('animation-time'), 0); ++ else ++ this._animateOut(this._settings.get_double('animation-time'), 0); ++ } ++ else ++ this._animateOut(this._settings.get_double('animation-time'), 0); ++ } ++ }, ++ ++ _onOverviewShowing: function() { ++ this._ignoreHover = true; ++ this._intellihide.disable(); ++ this._removeAnimations(); ++ this._animateIn(this._settings.get_double('animation-time'), 0); ++ }, ++ ++ _onOverviewHiding: function() { ++ this._ignoreHover = false; ++ this._intellihide.enable(); ++ this._updateDashVisibility(); ++ }, ++ ++ _hoverChanged: function() { ++ if (!this._ignoreHover) { ++ // Skip if dock is not in autohide mode for instance because it is shown ++ // by intellihide. ++ if (this._autohideIsEnabled) { ++ if (this._box.hover) ++ this._show(); ++ else ++ this._hide(); ++ } ++ } ++ }, ++ ++ getDockState: function() { ++ return this._dockState; ++ }, ++ ++ _show: function() { ++ if ((this._dockState == State.HIDDEN) || (this._dockState == State.HIDING)) { ++ if (this._dockState == State.HIDING) ++ // suppress all potential queued hiding animations - i.e. added to Tweener but not started, ++ // always give priority to show ++ this._removeAnimations(); ++ ++ this.emit('showing'); ++ this._animateIn(this._settings.get_double('animation-time'), 0); ++ } ++ }, ++ ++ _hide: function() { ++ // If no hiding animation is running or queued ++ if ((this._dockState == State.SHOWN) || (this._dockState == State.SHOWING)) { ++ let delay; ++ ++ if (this._dockState == State.SHOWING) ++ //if a show already started, let it finish; queue hide without removing the show. ++ // to obtain this I increase the delay to avoid the overlap and interference ++ // between the animations ++ delay = this._settings.get_double('hide-delay') + this._settings.get_double('animation-time'); ++ else ++ delay = this._settings.get_double('hide-delay'); ++ ++ this.emit('hiding'); ++ this._animateOut(this._settings.get_double('animation-time'), delay); ++ } ++ }, ++ ++ _animateIn: function(time, delay) { ++ this._dockState = State.SHOWING; ++ ++ Tweener.addTween(this._slider, { ++ slidex: 1, ++ time: time, ++ delay: delay, ++ transition: 'easeOutQuad', ++ onComplete: Lang.bind(this, function() { ++ this._dockState = State.SHOWN; ++ // Remove barrier so that mouse pointer is released and can access monitors on other side of dock ++ // NOTE: Delay needed to keep mouse from moving past dock and re-hiding dock immediately. This ++ // gives users an opportunity to hover over the dock ++ if (this._removeBarrierTimeoutId > 0) ++ Mainloop.source_remove(this._removeBarrierTimeoutId); ++ this._removeBarrierTimeoutId = Mainloop.timeout_add(100, Lang.bind(this, this._removeBarrier)); ++ }) ++ }); ++ }, ++ ++ _animateOut: function(time, delay) { ++ this._dockState = State.HIDING; ++ Tweener.addTween(this._slider, { ++ slidex: 0, ++ time: time, ++ delay: delay , ++ transition: 'easeOutQuad', ++ onComplete: Lang.bind(this, function() { ++ this._dockState = State.HIDDEN; ++ // Remove queued barried removal if any ++ if (this._removeBarrierTimeoutId > 0) ++ Mainloop.source_remove(this._removeBarrierTimeoutId); ++ this._updateBarrier(); ++ }) ++ }); ++ }, ++ ++ /** ++ * Dwelling system based on the GNOME Shell 3.14 messageTray code. ++ */ ++ _setupDockDwellIfNeeded: function() { ++ // If we don't have extended barrier features, then we need ++ // to support the old tray dwelling mechanism. ++ if (!global.display.supports_extended_barriers() || !this._settings.get_boolean('require-pressure-to-show')) { ++ let pointerWatcher = PointerWatcher.getPointerWatcher(); ++ this._dockWatch = pointerWatcher.addWatch(DOCK_DWELL_CHECK_INTERVAL, Lang.bind(this, this._checkDockDwell)); ++ this._dockDwelling = false; ++ this._dockDwellUserTime = 0; ++ } ++ }, ++ ++ _checkDockDwell: function(x, y) { ++ ++ let workArea = Main.layoutManager.getWorkAreaForMonitor(this._monitor.index) ++ let shouldDwell; ++ // Check for the correct screen edge, extending the sensitive area to the whole workarea, ++ // minus 1 px to avoid conflicting with other active corners. ++ if (this._position == St.Side.LEFT) ++ shouldDwell = (x == this._monitor.x) && (y > workArea.y) && (y < workArea.y + workArea.height); ++ else if (this._position == St.Side.RIGHT) ++ shouldDwell = (x == this._monitor.x + this._monitor.width - 1) && (y > workArea.y) && (y < workArea.y + workArea.height); ++ else if (this._position == St.Side.TOP) ++ shouldDwell = (y == this._monitor.y) && (x > workArea.x) && (x < workArea.x + workArea.width); ++ else if (this._position == St.Side.BOTTOM) ++ shouldDwell = (y == this._monitor.y + this._monitor.height - 1) && (x > workArea.x) && (x < workArea.x + workArea.width); ++ ++ if (shouldDwell) { ++ // We only set up dwell timeout when the user is not hovering over the dock ++ // already (!this._box.hover). ++ // The _dockDwelling variable is used so that we only try to ++ // fire off one dock dwell - if it fails (because, say, the user has the mouse down), ++ // we don't try again until the user moves the mouse up and down again. ++ if (!this._dockDwelling && !this._box.hover && (this._dockDwellTimeoutId == 0)) { ++ // Save the interaction timestamp so we can detect user input ++ let focusWindow = global.display.focus_window; ++ this._dockDwellUserTime = focusWindow ? focusWindow.user_time : 0; ++ ++ this._dockDwellTimeoutId = Mainloop.timeout_add(this._settings.get_double('show-delay') * 1000, ++ Lang.bind(this, this._dockDwellTimeout)); ++ GLib.Source.set_name_by_id(this._dockDwellTimeoutId, '[dash-to-dock] this._dockDwellTimeout'); ++ } ++ this._dockDwelling = true; ++ } ++ else { ++ this._cancelDockDwell(); ++ this._dockDwelling = false; ++ } ++ }, ++ ++ _cancelDockDwell: function() { ++ if (this._dockDwellTimeoutId != 0) { ++ Mainloop.source_remove(this._dockDwellTimeoutId); ++ this._dockDwellTimeoutId = 0; ++ } ++ }, ++ ++ _dockDwellTimeout: function() { ++ this._dockDwellTimeoutId = 0; ++ ++ if (!this._settings.get_boolean('autohide-in-fullscreen') && this._monitor.inFullscreen) ++ return GLib.SOURCE_REMOVE; ++ ++ // We don't want to open the tray when a modal dialog ++ // is up, so we check the modal count for that. When we are in the ++ // overview we have to take the overview's modal push into account ++ if (Main.modalCount > (Main.overview.visible ? 1 : 0)) ++ return GLib.SOURCE_REMOVE; ++ ++ // If the user interacted with the focus window since we started the tray ++ // dwell (by clicking or typing), don't activate the message tray ++ let focusWindow = global.display.focus_window; ++ let currentUserTime = focusWindow ? focusWindow.user_time : 0; ++ if (currentUserTime != this._dockDwellUserTime) ++ return GLib.SOURCE_REMOVE; ++ ++ // Reuse the pressure version function, the logic is the same ++ this._onPressureSensed(); ++ return GLib.SOURCE_REMOVE; ++ }, ++ ++ _updatePressureBarrier: function() { ++ this._canUsePressure = global.display.supports_extended_barriers(); ++ let pressureThreshold = this._settings.get_double('pressure-threshold'); ++ ++ // Remove existing pressure barrier ++ if (this._pressureBarrier) { ++ this._pressureBarrier.destroy(); ++ this._pressureBarrier = null; ++ } ++ ++ if (this._barrier) { ++ this._barrier.destroy(); ++ this._barrier = null; ++ } ++ ++ // Create new pressure barrier based on pressure threshold setting ++ if (this._canUsePressure) { ++ this._pressureBarrier = new Layout.PressureBarrier(pressureThreshold, this._settings.get_double('show-delay')*1000, ++ Shell.ActionMode.NORMAL | Shell.ActionMode.OVERVIEW); ++ this._pressureBarrier.connect('trigger', Lang.bind(this, function(barrier) { ++ if (!this._settings.get_boolean('autohide-in-fullscreen') && this._monitor.inFullscreen) ++ return; ++ this._onPressureSensed(); ++ })); ++ } ++ }, ++ ++ /** ++ * handler for mouse pressure sensed ++ */ ++ _onPressureSensed: function() { ++ if (Main.overview.visibleTarget) ++ return; ++ ++ // In case the mouse move away from the dock area before hovering it, in such case the leave event ++ // would never be triggered and the dock would stay visible forever. ++ let triggerTimeoutId = Mainloop.timeout_add(250, Lang.bind(this, function() { ++ triggerTimeoutId = 0; ++ ++ let [x, y, mods] = global.get_pointer(); ++ let shouldHide = true; ++ switch (this._position) { ++ case St.Side.LEFT: ++ if (x <= this.staticBox.x2 && ++ x >= this._monitor.x && ++ y >= this._monitor.y && ++ y <= this._monitor.y + this._monitor.height) { ++ shouldHide = false; ++ } ++ break; ++ case St.Side.RIGHT: ++ if (x >= this.staticBox.x1 && ++ x <= this._monitor.x + this._monitor.width && ++ y >= this._monitor.y && ++ y <= this._monitor.y + this._monitor.height) { ++ shouldHide = false; ++ } ++ break; ++ case St.Side.TOP: ++ if (x >= this._monitor.x && ++ x <= this._monitor.x + this._monitor.width && ++ y <= this.staticBox.y2 && ++ y >= this._monitor.y) { ++ shouldHide = false; ++ } ++ break; ++ case St.Side.BOTTOM: ++ if (x >= this._monitor.x && ++ x <= this._monitor.x + this._monitor.width && ++ y >= this.staticBox.y1 && ++ y <= this._monitor.y + this._monitor.height) { ++ shouldHide = false; ++ } ++ } ++ if (shouldHide) { ++ this._hoverChanged(); ++ return GLib.SOURCE_REMOVE; ++ } ++ else { ++ return GLib.SOURCE_CONTINUE; ++ } ++ ++ })); ++ ++ this._show(); ++ }, ++ ++ /** ++ * Remove pressure barrier ++ */ ++ _removeBarrier: function() { ++ if (this._barrier) { ++ if (this._pressureBarrier) ++ this._pressureBarrier.removeBarrier(this._barrier); ++ this._barrier.destroy(); ++ this._barrier = null; ++ } ++ this._removeBarrierTimeoutId = 0; ++ return false; ++ }, ++ ++ /** ++ * Update pressure barrier size ++ */ ++ _updateBarrier: function() { ++ // Remove existing barrier ++ this._removeBarrier(); ++ ++ // The barrier needs to be removed in fullscreen with autohide disabled, otherwise the mouse can ++ // get trapped on monitor. ++ if (this._monitor.inFullscreen && !this._settings.get_boolean('autohide-in-fullscreen')) ++ return ++ ++ // Manually reset pressure barrier ++ // This is necessary because we remove the pressure barrier when it is triggered to show the dock ++ if (this._pressureBarrier) { ++ this._pressureBarrier._reset(); ++ this._pressureBarrier._isTriggered = false; ++ } ++ ++ // Create new barrier ++ // The barrier extends to the whole workarea, minus 1 px to avoid conflicting with other active corners ++ // Note: dash in fixed position doesn't use pressure barrier. ++ if (this._canUsePressure && this._autohideIsEnabled && this._settings.get_boolean('require-pressure-to-show')) { ++ let x1, x2, y1, y2, direction; ++ let workArea = Main.layoutManager.getWorkAreaForMonitor(this._monitor.index) ++ ++ if (this._position == St.Side.LEFT) { ++ x1 = this._monitor.x + 1; ++ x2 = x1; ++ y1 = workArea.y + 1; ++ y2 = workArea.y + workArea.height - 1; ++ direction = Meta.BarrierDirection.POSITIVE_X; ++ } ++ else if (this._position == St.Side.RIGHT) { ++ x1 = this._monitor.x + this._monitor.width - 1; ++ x2 = x1; ++ y1 = workArea.y + 1; ++ y2 = workArea.y + workArea.height - 1; ++ direction = Meta.BarrierDirection.NEGATIVE_X; ++ } ++ else if (this._position == St.Side.TOP) { ++ x1 = workArea.x + 1; ++ x2 = workArea.x + workArea.width - 1; ++ y1 = this._monitor.y; ++ y2 = y1; ++ direction = Meta.BarrierDirection.POSITIVE_Y; ++ } ++ else if (this._position == St.Side.BOTTOM) { ++ x1 = workArea.x + 1; ++ x2 = workArea.x + workArea.width - 1; ++ y1 = this._monitor.y + this._monitor.height; ++ y2 = y1; ++ direction = Meta.BarrierDirection.NEGATIVE_Y; ++ } ++ ++ this._barrier = new Meta.Barrier({ ++ display: global.display, ++ x1: x1, ++ x2: x2, ++ y1: y1, ++ y2: y2, ++ directions: direction ++ }); ++ if (this._pressureBarrier) ++ this._pressureBarrier.addBarrier(this._barrier); ++ } ++ }, ++ ++ _isPrimaryMonitor: function() { ++ return (this._monitorIndex == Main.layoutManager.primaryIndex); ++ }, ++ ++ _resetPosition: function() { ++ // Ensure variables linked to settings are updated. ++ this._updateVisibilityMode(); ++ ++ let extendHeight = this._settings.get_boolean('extend-height'); ++ ++ // Note: do not use the workarea coordinates in the direction on which the dock is placed, ++ // to avoid a loop [position change -> workArea change -> position change] with ++ // fixed dock. ++ let workArea = Main.layoutManager.getWorkAreaForMonitor(this._monitorIndex); ++ ++ // Reserve space for the dash on the overview ++ // if the dock is on the primary monitor ++ if (this._isPrimaryMonitor()) ++ this._dashSpacer.show(); ++ else ++ // No space is required in the overview of the dash ++ this._dashSpacer.hide(); ++ ++ let fraction = this._settings.get_double('height-fraction'); ++ ++ if (extendHeight) ++ fraction = 1; ++ else if ((fraction < 0) || (fraction > 1)) ++ fraction = 0.95; ++ ++ let anchor_point; ++ ++ if (this._isHorizontal) { ++ this.actor.width = Math.round( fraction * workArea.width); ++ ++ let pos_y; ++ if (this._position == St.Side.BOTTOM) { ++ pos_y = this._monitor.y + this._monitor.height; ++ anchor_point = Clutter.Gravity.SOUTH_WEST; ++ } ++ else { ++ pos_y = this._monitor.y; ++ anchor_point = Clutter.Gravity.NORTH_WEST; ++ } ++ ++ this.actor.move_anchor_point_from_gravity(anchor_point); ++ this.actor.x = workArea.x + Math.round((1 - fraction) / 2 * workArea.width); ++ this.actor.y = pos_y; ++ ++ if (extendHeight) { ++ this.dash._container.set_width(this.actor.width); ++ this.actor.add_style_class_name('extended'); ++ } ++ else { ++ this.dash._container.set_width(-1); ++ this.actor.remove_style_class_name('extended'); ++ } ++ } ++ else { ++ this.actor.height = Math.round(fraction * workArea.height); ++ ++ let pos_x; ++ if (this._position == St.Side.RIGHT) { ++ pos_x = this._monitor.x + this._monitor.width; ++ anchor_point = Clutter.Gravity.NORTH_EAST; ++ } ++ else { ++ pos_x = this._monitor.x; ++ anchor_point = Clutter.Gravity.NORTH_WEST; ++ } ++ ++ this.actor.move_anchor_point_from_gravity(anchor_point); ++ this.actor.x = pos_x; ++ this.actor.y = workArea.y + Math.round((1 - fraction) / 2 * workArea.height); ++ ++ if (extendHeight) { ++ this.dash._container.set_height(this.actor.height); ++ this.actor.add_style_class_name('extended'); ++ } ++ else { ++ this.dash._container.set_height(-1); ++ this.actor.remove_style_class_name('extended'); ++ } ++ } ++ ++ this._y0 = this.actor.y; ++ ++ this._adjustLegacyTray(); ++ }, ++ ++ // Set the dash at the correct depth in z ++ _resetDepth: function() { ++ // Keep the dash below the modalDialogGroup and the legacyTray ++ if (Main.legacyTray && Main.legacyTray.actor) ++ Main.layoutManager.uiGroup.set_child_below_sibling(this.actor, Main.legacyTray.actor); ++ else ++ Main.layoutManager.uiGroup.set_child_below_sibling(this.actor, Main.layoutManager.modalDialogGroup); ++ }, ++ ++ _adjustLegacyTray: function() { ++ // The legacyTray has been removed in GNOME Shell 3.26. ++ // Once we drop support for previous releases this fuction can be dropped too. ++ if (!Main.legacyTray) ++ return; ++ ++ let use_work_area = true; ++ ++ if (this._fixedIsEnabled && !this._settings.get_boolean('extend-height') ++ && this._isPrimaryMonitor() ++ && ((this._position == St.Side.BOTTOM) || (this._position == St.Side.LEFT))) ++ use_work_area = false; ++ ++ Main.legacyTray.actor.clear_constraints(); ++ let constraint = new Layout.MonitorConstraint({ ++ primary: true, ++ work_area: use_work_area ++ }); ++ Main.legacyTray.actor.add_constraint(constraint); ++ }, ++ ++ _resetLegacyTray: function() { ++ // The legacyTray has been removed in GNOME Shell 3.26. ++ // Once we drop support for previous releases this fuction can be dropped too. ++ if (!Main.legacyTray) ++ return; ++ Main.legacyTray.actor.clear_constraints(); ++ let constraint = new Layout.MonitorConstraint({ ++ primary: true, ++ work_area: true ++ }); ++ Main.legacyTray.actor.add_constraint(constraint); ++ }, ++ ++ _updateStaticBox: function() { ++ this.staticBox.init_rect( ++ this.actor.x + this._slider.actor.x - (this._position == St.Side.RIGHT ? this._box.width : 0), ++ this.actor.y + this._slider.actor.y - (this._position == St.Side.BOTTOM ? this._box.height : 0), ++ this._box.width, ++ this._box.height ++ ); ++ ++ this._intellihide.updateTargetBox(this.staticBox); ++ }, ++ ++ _removeAnimations: function() { ++ Tweener.removeTweens(this._slider); ++ }, ++ ++ _onDragStart: function() { ++ // The dash need to be above the top_window_group, otherwise it doesn't ++ // accept dnd of app icons when not in overiew mode. ++ Main.layoutManager.uiGroup.set_child_above_sibling(this.actor, global.top_window_group); ++ this._oldignoreHover = this._ignoreHover; ++ this._ignoreHover = true; ++ this._animateIn(this._settings.get_double('animation-time'), 0); ++ }, ++ ++ _onDragEnd: function() { ++ // Restore drag default dash stack order ++ this._resetDepth(); ++ if (this._oldignoreHover !== null) ++ this._ignoreHover = this._oldignoreHover; ++ this._oldignoreHover = null; ++ this._box.sync_hover(); ++ if (Main.overview._shown) ++ this._pageChanged(); ++ }, ++ ++ _pageChanged: function() { ++ let activePage = Main.overview.viewSelector.getActivePage(); ++ let dashVisible = (activePage == ViewSelector.ViewPage.WINDOWS || ++ activePage == ViewSelector.ViewPage.APPS); ++ ++ if (dashVisible) ++ this._animateIn(this._settings.get_double('animation-time'), 0); ++ else ++ this._animateOut(this._settings.get_double('animation-time'), 0); ++ }, ++ ++ _onPageEmpty: function() { ++ /* The dash spacer is required only in the WINDOWS view if in the default position. ++ * The 'page-empty' signal is emitted in between a change of view, ++ * signalling the spacer can be added and removed without visible effect, ++ * as it's done for the upstream dashSpacer. ++ * ++ * Moreover, hiding the spacer ensure the appGrid allocaton is triggered. ++ * This matter as the appview spring animation is triggered by to first reallocaton of the appGrid, ++ * (See appDisplay.js, line 202 on GNOME Shell 3.14: ++ * this._grid.actor.connect('notify::allocation', ...) ++ * which in turn seems to be triggered by changes in the other actors in the overview. ++ * Normally, as far as I could understand, either the dashSpacer being hidden or the workspacesThumbnails ++ * sliding out would trigger the allocation. However, with no stock dash ++ * and no thumbnails, which happen if the user configured only 1 and static workspace, ++ * the animation out of icons is not played. ++ */ ++ ++ let activePage = Main.overview.viewSelector.getActivePage(); ++ this._dashSpacer.visible = (this._isHorizontal || activePage == ViewSelector.ViewPage.WINDOWS); ++ }, ++ ++ /** ++ * Show dock and give key focus to it ++ */ ++ _onAccessibilityFocus: function() { ++ this._box.navigate_focus(null, Gtk.DirectionType.TAB_FORWARD, false); ++ this._animateIn(this._settings.get_double('animation-time'), 0); ++ }, ++ ++ /** ++ * Keep ShowAppsButton status in sync with the overview status ++ */ ++ _syncShowAppsButtonToggled: function() { ++ let status = Main.overview.viewSelector._showAppsButton.checked; ++ if (this.dash.showAppsButton.checked !== status) ++ this.dash.showAppsButton.checked = status; ++ }, ++ ++ // Optional features to be enabled only for the main Dock ++ _enableExtraFeatures: function() { ++ // Restore dash accessibility ++ Main.ctrlAltTabManager.addGroup( ++ this.dash.actor, _('Dash'), 'user-bookmarks-symbolic', ++ {focusCallback: Lang.bind(this, this._onAccessibilityFocus)}); ++ }, ++ ++ /** ++ * Switch workspace by scrolling over the dock ++ */ ++ _optionalScrollWorkspaceSwitch: function() { ++ let label = 'optionalScrollWorkspaceSwitch'; ++ ++ function isEnabled() { ++ return this._settings.get_enum('scroll-action') === scrollAction.SWITCH_WORKSPACE; ++ } ++ ++ this._settings.connect('changed::scroll-action', Lang.bind(this, function() { ++ if (Lang.bind(this, isEnabled)()) ++ Lang.bind(this, enable)(); ++ else ++ Lang.bind(this, disable)(); ++ })); ++ ++ if (Lang.bind(this, isEnabled)()) ++ Lang.bind(this, enable)(); ++ ++ function enable() { ++ this._signalsHandler.removeWithLabel(label); ++ ++ this._signalsHandler.addWithLabel(label, [ ++ this._box, ++ 'scroll-event', ++ Lang.bind(this, onScrollEvent) ++ ]); ++ ++ this._optionalScrollWorkspaceSwitchDeadTimeId = 0; ++ } ++ ++ function disable() { ++ this._signalsHandler.removeWithLabel(label); ++ ++ if (this._optionalScrollWorkspaceSwitchDeadTimeId > 0) { ++ Mainloop.source_remove(this._optionalScrollWorkspaceSwitchDeadTimeId); ++ this._optionalScrollWorkspaceSwitchDeadTimeId = 0; ++ } ++ } ++ ++ // This was inspired to desktop-scroller@obsidien.github.com ++ function onScrollEvent(actor, event) { ++ // When in overview change workscape only in windows view ++ if (Main.overview.visible && Main.overview.viewSelector.getActivePage() !== ViewSelector.ViewPage.WINDOWS) ++ return false; ++ ++ let activeWs = global.screen.get_active_workspace(); ++ let direction = null; ++ ++ switch (event.get_scroll_direction()) { ++ case Clutter.ScrollDirection.UP: ++ direction = Meta.MotionDirection.UP; ++ break; ++ case Clutter.ScrollDirection.DOWN: ++ direction = Meta.MotionDirection.DOWN; ++ break; ++ case Clutter.ScrollDirection.SMOOTH: ++ let [dx, dy] = event.get_scroll_delta(); ++ if (dy < 0) ++ direction = Meta.MotionDirection.UP; ++ else if (dy > 0) ++ direction = Meta.MotionDirection.DOWN; ++ break; ++ } ++ ++ if (direction !== null) { ++ // Prevent scroll events from triggering too many workspace switches ++ // by adding a 250ms deadtime between each scroll event. ++ // Usefull on laptops when using a touchpad. ++ ++ // During the deadtime do nothing ++ if (this._optionalScrollWorkspaceSwitchDeadTimeId > 0) ++ return false; ++ else ++ this._optionalScrollWorkspaceSwitchDeadTimeId = Mainloop.timeout_add(250, Lang.bind(this, function() { ++ this._optionalScrollWorkspaceSwitchDeadTimeId = 0; ++ })); ++ ++ let ws; ++ ++ ws = activeWs.get_neighbor(direction) ++ ++ if (Main.wm._workspaceSwitcherPopup == null) ++ Main.wm._workspaceSwitcherPopup = new WorkspaceSwitcherPopup.WorkspaceSwitcherPopup(); ++ // Set the actor non reactive, so that it doesn't prevent the ++ // clicks events from reaching the dash actor. I can't see a reason ++ // why it should be reactive. ++ Main.wm._workspaceSwitcherPopup.actor.reactive = false; ++ Main.wm._workspaceSwitcherPopup.connect('destroy', function() { ++ Main.wm._workspaceSwitcherPopup = null; ++ }); ++ ++ // Do not show wokspaceSwithcer in overview ++ if (!Main.overview.visible) ++ Main.wm._workspaceSwitcherPopup.display(direction, ws.index()); ++ Main.wm.actionMoveWorkspace(ws); ++ ++ return true; ++ } ++ else ++ return false; ++ } ++ }, ++ ++ _activateApp: function(appIndex) { ++ let children = this.dash._box.get_children().filter(function(actor) { ++ return actor.child && ++ actor.child._delegate && ++ actor.child._delegate.app; ++ }); ++ ++ // Apps currently in the dash ++ let apps = children.map(function(actor) { ++ return actor.child._delegate; ++ }); ++ ++ // Activate with button = 1, i.e. same as left click ++ let button = 1; ++ if (appIndex < apps.length) ++ apps[appIndex].activate(button); ++ } ++ ++}); ++ ++Signals.addSignalMethods(DockedDash.prototype); ++ ++/* ++ * Handle keybaord shortcuts ++ */ ++const KeyboardShortcuts = new Lang.Class({ ++ ++ Name: 'DashToDock.KeyboardShortcuts', ++ ++ _numHotkeys: 10, ++ ++ _init: function(settings, allDocks){ ++ ++ this._settings = settings; ++ this._allDocks = allDocks; ++ this._signalsHandler = new Utils.GlobalSignalsHandler(); ++ ++ this._hotKeysEnabled = false; ++ if (this._settings.get_boolean('hot-keys')) ++ this._enableHotKeys(); ++ ++ this._signalsHandler.add([ ++ this._settings, ++ 'changed::hot-keys', ++ Lang.bind(this, function() { ++ if (this._settings.get_boolean('hot-keys')) ++ Lang.bind(this, this._enableHotKeys)(); ++ else ++ Lang.bind(this, this._disableHotKeys)(); ++ }) ++ ]); ++ ++ this._optionalNumberOverlay(); ++ }, ++ ++ destroy: function (){ ++ // Remove keybindings ++ this._disableHotKeys(); ++ this._disableExtraShortcut(); ++ this._signalsHandler.destroy(); ++ }, ++ ++ _enableHotKeys: function() { ++ if (this._hotKeysEnabled) ++ return; ++ ++ // Setup keyboard bindings for dash elements ++ let keys = ['app-hotkey-', 'app-shift-hotkey-', 'app-ctrl-hotkey-']; ++ keys.forEach( function(key) { ++ for (let i = 0; i < this._numHotkeys; i++) { ++ let appNum = i; ++ Main.wm.addKeybinding(key + (i + 1), this._settings, ++ Meta.KeyBindingFlags.NONE, ++ Shell.ActionMode.NORMAL | Shell.ActionMode.OVERVIEW, ++ Lang.bind(this, function() { ++ this._allDocks[0]._activateApp(appNum); ++ this._showOverlay(); ++ })); ++ } ++ }, this); ++ ++ this._hotKeysEnabled = true; ++ }, ++ ++ _disableHotKeys: function() { ++ if (!this._hotKeysEnabled) ++ return; ++ ++ let keys = ['app-hotkey-', 'app-shift-hotkey-', 'app-ctrl-hotkey-']; ++ keys.forEach( function(key) { ++ for (let i = 0; i < this._numHotkeys; i++) ++ Main.wm.removeKeybinding(key + (i + 1)); ++ }, this); ++ ++ this._hotKeysEnabled = false; ++ }, ++ ++ _optionalNumberOverlay: function() { ++ this._shortcutIsSet = false; ++ // Enable extra shortcut if either 'overlay' or 'show-dock' are true ++ if (this._settings.get_boolean('hot-keys') && ++ (this._settings.get_boolean('hotkeys-overlay') || this._settings.get_boolean('hotkeys-show-dock'))) ++ this._enableExtraShortcut(); ++ ++ this._signalsHandler.add([ ++ this._settings, ++ 'changed::hot-keys', ++ Lang.bind(this, this._checkHotkeysOptions) ++ ], [ ++ this._settings, ++ 'changed::hotkeys-overlay', ++ Lang.bind(this, this._checkHotkeysOptions) ++ ], [ ++ this._settings, ++ 'changed::hotkeys-show-dock', ++ Lang.bind(this, this._checkHotkeysOptions) ++ ]); ++ }, ++ ++ _checkHotkeysOptions: function() { ++ if (this._settings.get_boolean('hot-keys') && ++ (this._settings.get_boolean('hotkeys-overlay') || this._settings.get_boolean('hotkeys-show-dock'))) ++ this._enableExtraShortcut(); ++ else ++ this._disableExtraShortcut(); ++ }, ++ ++ _enableExtraShortcut: function() { ++ if (!this._shortcutIsSet) { ++ Main.wm.addKeybinding('shortcut', this._settings, ++ Meta.KeyBindingFlags.NONE, ++ Shell.ActionMode.NORMAL | Shell.ActionMode.OVERVIEW, ++ Lang.bind(this, this._showOverlay)); ++ this._shortcutIsSet = true; ++ } ++ }, ++ ++ _disableExtraShortcut: function() { ++ if (this._shortcutIsSet) { ++ Main.wm.removeKeybinding('shortcut'); ++ this._shortcutIsSet = false; ++ } ++ }, ++ ++ _showOverlay: function() { ++ for (let i = 0; i < this._allDocks.length; i++) { ++ let dock = this._allDocks[i]; ++ if (dock._settings.get_boolean('hotkeys-overlay')) ++ dock.dash.toggleNumberOverlay(true); ++ ++ // Restart the counting if the shortcut is pressed again ++ if (dock._numberOverlayTimeoutId) { ++ Mainloop.source_remove(dock._numberOverlayTimeoutId); ++ dock._numberOverlayTimeoutId = 0; ++ } ++ ++ // Hide the overlay/dock after the timeout ++ let timeout = dock._settings.get_double('shortcut-timeout') * 1000; ++ dock._numberOverlayTimeoutId = Mainloop.timeout_add(timeout, Lang.bind(dock, function() { ++ dock._numberOverlayTimeoutId = 0; ++ dock.dash.toggleNumberOverlay(false); ++ // Hide the dock again if necessary ++ dock._updateDashVisibility(); ++ })); ++ ++ // Show the dock if it is hidden ++ if (dock._settings.get_boolean('hotkeys-show-dock')) { ++ let showDock = (dock._intellihideIsEnabled || dock._autohideIsEnabled); ++ if (showDock) ++ dock._show(); ++ } ++ } ++ } ++ ++}); ++ ++/** ++ * Isolate overview to open new windows for inactive apps ++ * Note: the future implementaion is not fully contained here. Some bits are around in other methods of other classes. ++ * This class just take care of enabling/disabling the option. ++ */ ++const WorkspaceIsolation = new Lang.Class({ ++ ++ Name: 'DashToDock.WorkspaceIsolation', ++ ++ _init: function(settings, allDocks) { ++ ++ this._settings = settings; ++ this._allDocks = allDocks; ++ ++ this._signalsHandler = new Utils.GlobalSignalsHandler(); ++ this._injectionsHandler = new Utils.InjectionsHandler(); ++ ++ this._signalsHandler.add([ ++ this._settings, ++ 'changed::isolate-workspaces', ++ Lang.bind(this, function() { ++ this._allDocks.forEach(function(dock) { ++ dock.dash.resetAppIcons(); ++ }); ++ if (this._settings.get_boolean('isolate-workspaces') || ++ this._settings.get_boolean('isolate-monitors')) ++ Lang.bind(this, this._enable)(); ++ else ++ Lang.bind(this, this._disable)(); ++ }) ++ ],[ ++ this._settings, ++ 'changed::isolate-monitors', ++ Lang.bind(this, function() { ++ this._allDocks.forEach(function(dock) { ++ dock.dash.resetAppIcons(); ++ }); ++ if (this._settings.get_boolean('isolate-workspaces') || ++ this._settings.get_boolean('isolate-monitors')) ++ Lang.bind(this, this._enable)(); ++ else ++ Lang.bind(this, this._disable)(); ++ }) ++ ]); ++ ++ if (this._settings.get_boolean('isolate-workspaces') || ++ this._settings.get_boolean('isolate-monitors')) ++ this._enable(); ++ ++ }, ++ ++ _enable: function() { ++ ++ // ensure I never double-register/inject ++ // although it should never happen ++ this._disable(); ++ ++ this._allDocks.forEach(function(dock) { ++ this._signalsHandler.addWithLabel('isolation', [ ++ global.screen, ++ 'restacked', ++ Lang.bind(dock.dash, dock.dash._queueRedisplay) ++ ], [ ++ global.window_manager, ++ 'switch-workspace', ++ Lang.bind(dock.dash, dock.dash._queueRedisplay) ++ ]); ++ ++ // This last signal is only needed for monitor isolation, as windows ++ // might migrate from one monitor to another without triggering 'restacked' ++ if (this._settings.get_boolean('isolate-monitors')) ++ this._signalsHandler.addWithLabel('isolation', [ ++ global.screen, ++ 'window-entered-monitor', ++ Lang.bind(dock.dash, dock.dash._queueRedisplay) ++ ]); ++ ++ }, this); ++ ++ // here this is the Shell.App ++ function IsolatedOverview() { ++ // These lines take care of Nautilus for icons on Desktop ++ let windows = this.get_windows().filter(function(w) { ++ return w.get_workspace().index() == global.screen.get_active_workspace_index(); ++ }); ++ if (windows.length == 1) ++ if (windows[0].skip_taskbar) ++ return this.open_new_window(-1); ++ ++ if (this.is_on_workspace(global.screen.get_active_workspace())) ++ return Main.activateWindow(windows[0]); ++ return this.open_new_window(-1); ++ } ++ ++ this._injectionsHandler.addWithLabel('isolation', [ ++ Shell.App.prototype, ++ 'activate', ++ IsolatedOverview ++ ]); ++ }, ++ ++ _disable: function () { ++ this._signalsHandler.removeWithLabel('isolation'); ++ this._injectionsHandler.removeWithLabel('isolation'); ++ }, ++ ++ destroy: function() { ++ this._signalsHandler.destroy(); ++ this._injectionsHandler.destroy(); ++ } ++ ++}); ++ ++ ++var DockManager = new Lang.Class({ ++ Name: 'DashToDock.DockManager', ++ ++ _init: function() { ++ this._remoteModel = new LauncherAPI.LauncherEntryRemoteModel(); ++ this._settings = Convenience.getSettings('org.gnome.shell.extensions.dash-to-dock'); ++ this._oldDash = Main.overview._dash; ++ /* Array of all the docks created */ ++ this._allDocks = []; ++ this._createDocks(); ++ ++ // status variable: true when the overview is shown through the dash ++ // applications button. ++ this._forcedOverview = false; ++ ++ // Connect relevant signals to the toggling function ++ this._bindSettingsChanges(); ++ }, ++ ++ _toggle: function() { ++ this._deleteDocks(); ++ this._createDocks(); ++ this.emit('toggled'); ++ }, ++ ++ _bindSettingsChanges: function() { ++ // Connect relevant signals to the toggling function ++ this._signalsHandler = new Utils.GlobalSignalsHandler(); ++ this._signalsHandler.add([ ++ global.screen, ++ 'monitors-changed', ++ Lang.bind(this, this._toggle) ++ ], [ ++ this._settings, ++ 'changed::multi-monitor', ++ Lang.bind(this, this._toggle) ++ ], [ ++ this._settings, ++ 'changed::preferred-monitor', ++ Lang.bind(this, this._toggle) ++ ], [ ++ this._settings, ++ 'changed::dock-position', ++ Lang.bind(this, this._toggle) ++ ], [ ++ this._settings, ++ 'changed::extend-height', ++ Lang.bind(this, this._adjustPanelCorners) ++ ], [ ++ this._settings, ++ 'changed::dock-fixed', ++ Lang.bind(this, this._adjustPanelCorners) ++ ]); ++ }, ++ ++ _createDocks: function() { ++ ++ this._preferredMonitorIndex = this._settings.get_int('preferred-monitor'); ++ // In case of multi-monitor, we consider the dock on the primary monitor to be the preferred (main) one ++ // regardless of the settings ++ // The dock goes on the primary monitor also if the settings are incosistent (e.g. desired monitor not connected). ++ if (this._settings.get_boolean('multi-monitor') || ++ this._preferredMonitorIndex < 0 || this._preferredMonitorIndex > Main.layoutManager.monitors.length - 1 ++ ) { ++ this._preferredMonitorIndex = Main.layoutManager.primaryIndex; ++ } else { ++ // Gdk and shell monitors numbering differ at least under wayland: ++ // While the primary monitor appears to be always index 0 in Gdk, ++ // the shell can assign a different number (Main.layoutManager.primaryMonitor) ++ // This ensure the indexing in the settings (Gdk) and in the shell are matched, ++ // i.e. that we start counting from the primaryMonitorIndex ++ this._preferredMonitorIndex = (Main.layoutManager.primaryIndex + this._preferredMonitorIndex) % Main.layoutManager.monitors.length ; ++ } ++ ++ // First we create the main Dock, to get the extra features to bind to this one ++ let dock = new DockedDash(this._settings, this._remoteModel, this._preferredMonitorIndex); ++ this._mainShowAppsButton = dock.dash.showAppsButton; ++ this._allDocks.push(dock); ++ ++ // connect app icon into the view selector ++ dock.dash.showAppsButton.connect('notify::checked', Lang.bind(this, this._onShowAppsButtonToggled)); ++ ++ // Make the necessary changes to Main.overview._dash ++ this._prepareMainDash(); ++ ++ // Adjust corners if necessary ++ this._adjustPanelCorners(); ++ ++ if (this._settings.get_boolean('multi-monitor')) { ++ let nMon = Main.layoutManager.monitors.length; ++ for (let iMon = 0; iMon < nMon; iMon++) { ++ if (iMon == this._preferredMonitorIndex) ++ continue; ++ let dock = new DockedDash(this._settings, this._remoteModel, iMon); ++ this._allDocks.push(dock); ++ // connect app icon into the view selector ++ dock.dash.showAppsButton.connect('notify::checked', Lang.bind(this, this._onShowAppsButtonToggled)); ++ } ++ } ++ ++ // Load optional features. We load *after* the docks are created, since ++ // we need to connect the signals to all dock instances. ++ this._workspaceIsolation = new WorkspaceIsolation(this._settings, this._allDocks); ++ this._keyboardShortcuts = new KeyboardShortcuts(this._settings, this._allDocks); ++ }, ++ ++ _prepareMainDash: function() { ++ // Pretend I'm the dash: meant to make appgrd swarm animation come from the ++ // right position of the appShowButton. ++ Main.overview._dash = this._allDocks[0].dash; ++ ++ // set stored icon size to the new dash ++ Main.overview.dashIconSize = this._allDocks[0].dash.iconSize; ++ ++ // Hide usual Dash ++ Main.overview._controls.dash.actor.hide(); ++ ++ // Also set dash width to 1, so it's almost not taken into account by code ++ // calculaing the reserved space in the overview. The reason to keep it at 1 is ++ // to allow its visibility change to trigger an allocaion of the appGrid which ++ // in turn is triggergin the appsIcon spring animation, required when no other ++ // actors has this effect, i.e in horizontal mode and without the workspaceThumnails ++ // 1 static workspace only) ++ Main.overview._controls.dash.actor.set_width(1); ++ }, ++ ++ _deleteDocks: function() { ++ // Remove extra features ++ this._workspaceIsolation.destroy(); ++ this._keyboardShortcuts.destroy(); ++ ++ // Delete all docks ++ let nDocks = this._allDocks.length; ++ for (let i = nDocks-1; i >= 0; i--) { ++ this._allDocks[i].destroy(); ++ this._allDocks.pop(); ++ } ++ }, ++ ++ _restoreDash: function() { ++ 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 ++ Main.overview._controls.dash._maxHeight = -1; ++ ++ // reset stored icon size to the default dash ++ Main.overview.dashIconSize = Main.overview._controls.dash.iconSize; ++ ++ Main.overview._dash = this._oldDash; ++ }, ++ ++ _onShowAppsButtonToggled: function(button) { ++ // Sync the status of the default appButtons. Only if the two statuses are ++ // different, that means the user interacted with the extension provided ++ // application button, cutomize the behaviour. Otherwise the shell has changed the ++ // status (due to the _syncShowAppsButtonToggled function below) and it ++ // has already performed the desired action. ++ ++ let animate = this._settings.get_boolean('animate-show-apps'); ++ let selector = Main.overview.viewSelector; ++ ++ if (selector._showAppsButton.checked !== button.checked) { ++ // find visible view ++ let visibleView; ++ Main.overview.viewSelector.appDisplay._views.every(function(v, index) { ++ if (v.view.actor.visible) { ++ visibleView = index; ++ return false; ++ } ++ else ++ return true; ++ }); ++ ++ if (button.checked) { ++ // force spring animation triggering.By default the animation only ++ // runs if we are already inside the overview. ++ if (!Main.overview._shown) { ++ this._forcedOverview = true; ++ let view = Main.overview.viewSelector.appDisplay._views[visibleView].view; ++ let grid = view._grid; ++ if (animate) { ++ // Animate in the the appview, hide the appGrid to avoiud flashing ++ // Go to the appView before entering the overview, skipping the workspaces. ++ // Do this manually avoiding opacity in transitions so that the setting of the opacity ++ // to 0 doesn't get overwritten. ++ Main.overview.viewSelector._activePage.opacity = 0; ++ Main.overview.viewSelector._activePage.hide(); ++ Main.overview.viewSelector._activePage = Main.overview.viewSelector._appsPage; ++ Main.overview.viewSelector._activePage.show(); ++ grid.actor.opacity = 0; ++ ++ // The animation has to be trigered manually because the AppDisplay.animate ++ // method is waiting for an allocation not happening, as we skip the workspace view ++ // and the appgrid could already be allocated from previous shown. ++ // It has to be triggered after the overview is shown as wrong coordinates are obtained ++ // otherwise. ++ let overviewShownId = Main.overview.connect('shown', Lang.bind(this, function() { ++ Main.overview.disconnect(overviewShownId); ++ Meta.later_add(Meta.LaterType.BEFORE_REDRAW, Lang.bind(this, function() { ++ grid.actor.opacity = 255; ++ grid.animateSpring(IconGrid.AnimationDirection.IN, this._allDocks[0].dash.showAppsButton); ++ })); ++ })); ++ } ++ else { ++ Main.overview.viewSelector._activePage = Main.overview.viewSelector._appsPage; ++ Main.overview.viewSelector._activePage.show(); ++ grid.actor.opacity = 255; ++ } ++ ++ } ++ ++ // Finally show the overview ++ selector._showAppsButton.checked = true; ++ Main.overview.show(); ++ } ++ else { ++ if (this._forcedOverview) { ++ // force exiting overview if needed ++ ++ if (animate) { ++ // Manually trigger springout animation without activating the ++ // workspaceView to avoid the zoomout animation. Hide the appPage ++ // onComplete to avoid ugly flashing of original icons. ++ let view = Main.overview.viewSelector.appDisplay._views[visibleView].view; ++ let grid = view._grid; ++ view.animate(IconGrid.AnimationDirection.OUT, Lang.bind(this, function() { ++ Main.overview.viewSelector._appsPage.hide(); ++ Main.overview.hide(); ++ selector._showAppsButton.checked = false; ++ this._forcedOverview = false; ++ })); ++ } ++ else { ++ Main.overview.hide(); ++ this._forcedOverview = false; ++ } ++ } ++ else { ++ selector._showAppsButton.checked = false; ++ this._forcedOverview = false; ++ } ++ } ++ } ++ ++ // whenever the button is unactivated even if not by the user still reset the ++ // forcedOverview flag ++ if (button.checked == false) ++ this._forcedOverview = false; ++ }, ++ ++ destroy: function() { ++ this._signalsHandler.destroy(); ++ this._deleteDocks(); ++ this._revertPanelCorners(); ++ this._restoreDash(); ++ this._remoteModel.destroy(); ++ }, ++ ++ /** ++ * Adjust Panel corners ++ */ ++ _adjustPanelCorners: function() { ++ let position = Utils.getPosition(this._settings); ++ let isHorizontal = ((position == St.Side.TOP) || (position == St.Side.BOTTOM)); ++ let extendHeight = this._settings.get_boolean('extend-height'); ++ let fixedIsEnabled = this._settings.get_boolean('dock-fixed'); ++ let dockOnPrimary = this._settings.get_boolean('multi-monitor') || ++ this._preferredMonitorIndex == Main.layoutManager.primaryIndex; ++ ++ if (!isHorizontal && dockOnPrimary && extendHeight && fixedIsEnabled) { ++ Main.panel._rightCorner.actor.hide(); ++ Main.panel._leftCorner.actor.hide(); ++ } ++ else ++ this._revertPanelCorners(); ++ }, ++ ++ _revertPanelCorners: function() { ++ Main.panel._leftCorner.actor.show(); ++ Main.panel._rightCorner.actor.show(); ++ } ++}); ++Signals.addSignalMethods(DockManager.prototype); +diff --git a/extensions/dash-to-dock/extension.js b/extensions/dash-to-dock/extension.js +new file mode 100644 +index 0000000..97c1dbb +--- /dev/null ++++ b/extensions/dash-to-dock/extension.js +@@ -0,0 +1,23 @@ ++// -*- mode: js; js-indent-level: 4; indent-tabs-mode: nil -*- ++ ++const Me = imports.misc.extensionUtils.getCurrentExtension(); ++const Docking = Me.imports.docking; ++const Convenience = Me.imports.convenience; ++ ++// We declare this with var so it can be accessed by other extensions in ++// GNOME Shell 3.26+ (mozjs52+). ++var dockManager; ++ ++function init() { ++ Convenience.initTranslations('dashtodock'); ++} ++ ++function enable() { ++ dockManager = new Docking.DockManager(); ++} ++ ++function disable() { ++ dockManager.destroy(); ++ ++ dockManager=null; ++} +diff --git a/extensions/dash-to-dock/intellihide.js b/extensions/dash-to-dock/intellihide.js +new file mode 100644 +index 0000000..1fd2699 +--- /dev/null ++++ b/extensions/dash-to-dock/intellihide.js +@@ -0,0 +1,323 @@ ++// -*- mode: js; js-indent-level: 4; indent-tabs-mode: nil -*- ++ ++const GLib = imports.gi.GLib; ++const Lang = imports.lang; ++const Mainloop = imports.mainloop; ++const Meta = imports.gi.Meta; ++const Shell = imports.gi.Shell; ++ ++const Main = imports.ui.main; ++const Signals = imports.signals; ++ ++const Me = imports.misc.extensionUtils.getCurrentExtension(); ++const Utils = Me.imports.utils; ++ ++// A good compromise between reactivity and efficiency; to be tuned. ++const INTELLIHIDE_CHECK_INTERVAL = 100; ++ ++const OverlapStatus = { ++ UNDEFINED: -1, ++ FALSE: 0, ++ TRUE: 1 ++}; ++ ++const IntellihideMode = { ++ ALL_WINDOWS: 0, ++ FOCUS_APPLICATION_WINDOWS: 1, ++ MAXIMIZED_WINDOWS : 2 ++}; ++ ++// List of windows type taken into account. Order is important (keep the original ++// enum order). ++const handledWindowTypes = [ ++ Meta.WindowType.NORMAL, ++ Meta.WindowType.DOCK, ++ Meta.WindowType.DIALOG, ++ Meta.WindowType.MODAL_DIALOG, ++ Meta.WindowType.TOOLBAR, ++ Meta.WindowType.MENU, ++ Meta.WindowType.UTILITY, ++ Meta.WindowType.SPLASHSCREEN ++]; ++ ++/** ++ * A rough and ugly implementation of the intellihide behaviour. ++ * Intallihide object: emit 'status-changed' signal when the overlap of windows ++ * with the provided targetBoxClutter.ActorBox changes; ++ */ ++var Intellihide = new Lang.Class({ ++ Name: 'DashToDock.Intellihide', ++ ++ _init: function(settings, monitorIndex) { ++ // Load settings ++ this._settings = settings; ++ this._monitorIndex = monitorIndex; ++ ++ this._signalsHandler = new Utils.GlobalSignalsHandler(); ++ this._tracker = Shell.WindowTracker.get_default(); ++ this._focusApp = null; // The application whose window is focused. ++ this._topApp = null; // The application whose window is on top on the monitor with the dock. ++ ++ this._isEnabled = false; ++ this.status = OverlapStatus.UNDEFINED; ++ this._targetBox = null; ++ ++ this._checkOverlapTimeoutContinue = false; ++ this._checkOverlapTimeoutId = 0; ++ ++ this._trackedWindows = new Map(); ++ ++ // Connect global signals ++ this._signalsHandler.add([ ++ // Add signals on windows created from now on ++ global.display, ++ 'window-created', ++ Lang.bind(this, this._windowCreated) ++ ], [ ++ // triggered for instance when the window list order changes, ++ // included when the workspace is switched ++ global.screen, ++ 'restacked', ++ Lang.bind(this, this._checkOverlap) ++ ], [ ++ // when windows are alwasy on top, the focus window can change ++ // without the windows being restacked. Thus monitor window focus change. ++ this._tracker, ++ 'notify::focus-app', ++ Lang.bind(this, this._checkOverlap) ++ ], [ ++ // update wne monitor changes, for instance in multimonitor when monitor are attached ++ global.screen, ++ 'monitors-changed', ++ Lang.bind(this, this._checkOverlap ) ++ ]); ++ }, ++ ++ destroy: function() { ++ // Disconnect global signals ++ this._signalsHandler.destroy(); ++ ++ // Remove residual windows signals ++ this.disable(); ++ }, ++ ++ enable: function() { ++ this._isEnabled = true; ++ this._status = OverlapStatus.UNDEFINED; ++ global.get_window_actors().forEach(function(wa) { ++ this._addWindowSignals(wa); ++ }, this); ++ this._doCheckOverlap(); ++ }, ++ ++ disable: function() { ++ this._isEnabled = false; ++ ++ for (let wa of this._trackedWindows.keys()) { ++ this._removeWindowSignals(wa); ++ } ++ this._trackedWindows.clear(); ++ ++ if (this._checkOverlapTimeoutId > 0) { ++ Mainloop.source_remove(this._checkOverlapTimeoutId); ++ this._checkOverlapTimeoutId = 0; ++ } ++ }, ++ ++ _windowCreated: function(display, metaWindow) { ++ this._addWindowSignals(metaWindow.get_compositor_private()); ++ }, ++ ++ _addWindowSignals: function(wa) { ++ if (!this._handledWindow(wa)) ++ return; ++ let signalId = wa.connect('allocation-changed', Lang.bind(this, this._checkOverlap, wa.get_meta_window())); ++ this._trackedWindows.set(wa, signalId); ++ wa.connect('destroy', Lang.bind(this, this._removeWindowSignals)); ++ }, ++ ++ _removeWindowSignals: function(wa) { ++ if (this._trackedWindows.get(wa)) { ++ wa.disconnect(this._trackedWindows.get(wa)); ++ this._trackedWindows.delete(wa); ++ } ++ ++ }, ++ ++ updateTargetBox: function(box) { ++ this._targetBox = box; ++ this._checkOverlap(); ++ }, ++ ++ forceUpdate: function() { ++ this._status = OverlapStatus.UNDEFINED; ++ this._doCheckOverlap(); ++ }, ++ ++ getOverlapStatus: function() { ++ return (this._status == OverlapStatus.TRUE); ++ }, ++ ++ _checkOverlap: function() { ++ if (!this._isEnabled || (this._targetBox == null)) ++ return; ++ ++ /* Limit the number of calls to the doCheckOverlap function */ ++ if (this._checkOverlapTimeoutId) { ++ this._checkOverlapTimeoutContinue = true; ++ return ++ } ++ ++ this._doCheckOverlap(); ++ ++ this._checkOverlapTimeoutId = Mainloop.timeout_add(INTELLIHIDE_CHECK_INTERVAL, Lang.bind(this, function() { ++ this._doCheckOverlap(); ++ if (this._checkOverlapTimeoutContinue) { ++ this._checkOverlapTimeoutContinue = false; ++ return GLib.SOURCE_CONTINUE; ++ } else { ++ this._checkOverlapTimeoutId = 0; ++ return GLib.SOURCE_REMOVE; ++ } ++ })); ++ }, ++ ++ _doCheckOverlap: function() { ++ ++ if (!this._isEnabled || (this._targetBox == null)) ++ return; ++ ++ let overlaps = OverlapStatus.FALSE; ++ let windows = global.get_window_actors(); ++ ++ if (windows.length > 0) { ++ /* ++ * Get the top window on the monitor where the dock is placed. ++ * The idea is that we dont want to overlap with the windows of the topmost application, ++ * event is it's not the focused app -- for instance because in multimonitor the user ++ * select a window in the secondary monitor. ++ */ ++ ++ let topWindow = null; ++ for (let i = windows.length - 1; i >= 0; i--) { ++ let meta_win = windows[i].get_meta_window(); ++ if (this._handledWindow(windows[i]) && (meta_win.get_monitor() == this._monitorIndex)) { ++ topWindow = meta_win; ++ break; ++ } ++ } ++ ++ if (topWindow !== null) { ++ this._topApp = this._tracker.get_window_app(topWindow); ++ // If there isn't a focused app, use that of the window on top ++ this._focusApp = this._tracker.focus_app || this._topApp ++ ++ windows = windows.filter(this._intellihideFilterInteresting, this); ++ ++ for (let i = 0; i < windows.length; i++) { ++ let win = windows[i].get_meta_window(); ++ ++ if (win) { ++ let rect = win.get_frame_rect(); ++ ++ let test = (rect.x < this._targetBox.x2) && ++ (rect.x + rect.width > this._targetBox.x1) && ++ (rect.y < this._targetBox.y2) && ++ (rect.y + rect.height > this._targetBox.y1); ++ ++ if (test) { ++ overlaps = OverlapStatus.TRUE; ++ break; ++ } ++ } ++ } ++ } ++ } ++ ++ if (this._status !== overlaps) { ++ this._status = overlaps; ++ this.emit('status-changed', this._status); ++ } ++ ++ }, ++ ++ // Filter interesting windows to be considered for intellihide. ++ // Consider all windows visible on the current workspace. ++ // Optionally skip windows of other applications ++ _intellihideFilterInteresting: function(wa) { ++ let meta_win = wa.get_meta_window(); ++ if (!this._handledWindow(wa)) ++ return false; ++ ++ let currentWorkspace = global.screen.get_active_workspace_index(); ++ let wksp = meta_win.get_workspace(); ++ let wksp_index = wksp.index(); ++ ++ // Depending on the intellihide mode, exclude non-relevent windows ++ switch (this._settings.get_enum('intellihide-mode')) { ++ case IntellihideMode.ALL_WINDOWS: ++ // Do nothing ++ break; ++ ++ case IntellihideMode.FOCUS_APPLICATION_WINDOWS: ++ // Skip windows of other apps ++ if (this._focusApp) { ++ // The DropDownTerminal extension is not an application per se ++ // so we match its window by wm class instead ++ if (meta_win.get_wm_class() == 'DropDownTerminalWindow') ++ return true; ++ ++ let currentApp = this._tracker.get_window_app(meta_win); ++ let focusWindow = global.display.get_focus_window() ++ ++ // Consider half maximized windows side by side ++ // and windows which are alwayson top ++ if((currentApp != this._focusApp) && (currentApp != this._topApp) ++ && !((focusWindow && focusWindow.maximized_vertically && !focusWindow.maximized_horizontally) ++ && (meta_win.maximized_vertically && !meta_win.maximized_horizontally) ++ && meta_win.get_monitor() == focusWindow.get_monitor()) ++ && !meta_win.is_above()) ++ return false; ++ } ++ break; ++ ++ case IntellihideMode.MAXIMIZED_WINDOWS: ++ // Skip unmaximized windows ++ if (!meta_win.maximized_vertically && !meta_win.maximized_horizontally) ++ return false; ++ break; ++ } ++ ++ if ( wksp_index == currentWorkspace && meta_win.showing_on_its_workspace() ) ++ return true; ++ else ++ return false; ++ ++ }, ++ ++ // Filter windows by type ++ // inspired by Opacify@gnome-shell.localdomain.pl ++ _handledWindow: function(wa) { ++ let metaWindow = wa.get_meta_window(); ++ ++ if (!metaWindow) ++ return false; ++ ++ // The DropDownTerminal extension uses the POPUP_MENU window type hint ++ // so we match its window by wm class instead ++ if (metaWindow.get_wm_class() == 'DropDownTerminalWindow') ++ return true; ++ ++ let wtype = metaWindow.get_window_type(); ++ for (let i = 0; i < handledWindowTypes.length; i++) { ++ var hwtype = handledWindowTypes[i]; ++ if (hwtype == wtype) ++ return true; ++ else if (hwtype > wtype) ++ return false; ++ } ++ return false; ++ } ++}); ++ ++Signals.addSignalMethods(Intellihide.prototype); +diff --git a/extensions/dash-to-dock/launcherAPI.js b/extensions/dash-to-dock/launcherAPI.js +new file mode 100644 +index 0000000..d051a70 +--- /dev/null ++++ b/extensions/dash-to-dock/launcherAPI.js +@@ -0,0 +1,244 @@ ++// -*- mode: js; js-indent-level: 4; indent-tabs-mode: nil -*- ++ ++const Gio = imports.gi.Gio; ++const Lang = imports.lang; ++const Signals = imports.signals; ++ ++var LauncherEntryRemoteModel = new Lang.Class({ ++ Name: 'DashToDock.LauncherEntryRemoteModel', ++ ++ _init: function () { ++ this._entriesByDBusName = {}; ++ ++ this._launcher_entry_dbus_signal_id = ++ Gio.DBus.session.signal_subscribe(null, // sender ++ 'com.canonical.Unity.LauncherEntry', // iface ++ null, // member ++ null, // path ++ null, // arg0 ++ Gio.DBusSignalFlags.NONE, ++ Lang.bind(this, this._onEntrySignalReceived)); ++ ++ this._dbus_name_owner_changed_signal_id = ++ Gio.DBus.session.signal_subscribe('org.freedesktop.DBus', // sender ++ 'org.freedesktop.DBus', // interface ++ 'NameOwnerChanged', // member ++ '/org/freedesktop/DBus', // path ++ null, // arg0 ++ Gio.DBusSignalFlags.NONE, ++ Lang.bind(this, this._onDBusNameOwnerChanged)); ++ ++ this._acquireUnityDBus(); ++ }, ++ ++ destroy: function () { ++ if (this._launcher_entry_dbus_signal_id) { ++ Gio.DBus.session.signal_unsubscribe(this._launcher_entry_dbus_signal_id); ++ } ++ ++ if (this._dbus_name_owner_changed_signal_id) { ++ Gio.DBus.session.signal_unsubscribe(this._dbus_name_owner_changed_signal_id); ++ } ++ ++ this._releaseUnityDBus(); ++ }, ++ ++ size: function () { ++ return Object.keys(this._entriesByDBusName).length; ++ }, ++ ++ lookupByDBusName: function (dbusName) { ++ return this._entriesByDBusName.hasOwnProperty(dbusName) ? this._entriesByDBusName[dbusName] : null; ++ }, ++ ++ lookupById: function (appId) { ++ let ret = []; ++ for (let dbusName in this._entriesByDBusName) { ++ let entry = this._entriesByDBusName[dbusName]; ++ if (entry && entry.appId() == appId) { ++ ret.push(entry); ++ } ++ } ++ ++ return ret; ++ }, ++ ++ addEntry: function (entry) { ++ let existingEntry = this.lookupByDBusName(entry.dbusName()); ++ if (existingEntry) { ++ existingEntry.update(entry); ++ } else { ++ this._entriesByDBusName[entry.dbusName()] = entry; ++ this.emit('entry-added', entry); ++ } ++ }, ++ ++ removeEntry: function (entry) { ++ delete this._entriesByDBusName[entry.dbusName()] ++ this.emit('entry-removed', entry); ++ }, ++ ++ _acquireUnityDBus: function () { ++ if (!this._unity_bus_id) { ++ Gio.DBus.session.own_name('com.canonical.Unity', ++ Gio.BusNameOwnerFlags.ALLOW_REPLACEMENT, null, null); ++ } ++ }, ++ ++ _releaseUnityDBus: function () { ++ if (this._unity_bus_id) { ++ Gio.DBus.session.unown_name(this._unity_bus_id); ++ this._unity_bus_id = 0; ++ } ++ }, ++ ++ _onEntrySignalReceived: function (connection, sender_name, object_path, ++ interface_name, signal_name, parameters, user_data) { ++ if (!parameters || !signal_name) ++ return; ++ ++ if (signal_name == 'Update') { ++ if (!sender_name) { ++ return; ++ } ++ ++ this._handleUpdateRequest(sender_name, parameters); ++ } ++ }, ++ ++ _onDBusNameOwnerChanged: function (connection, sender_name, object_path, ++ interface_name, signal_name, parameters, user_data) { ++ if (!parameters || !this.size()) ++ return; ++ ++ let [name, before, after] = parameters.deep_unpack(); ++ ++ if (!after) { ++ if (this._entriesByDBusName.hasOwnProperty(before)) { ++ this.removeEntry(this._entriesByDBusName[before]); ++ } ++ } ++ }, ++ ++ _handleUpdateRequest: function (senderName, parameters) { ++ if (!senderName || !parameters) { ++ return; ++ } ++ ++ let [appUri, properties] = parameters.deep_unpack(); ++ let appId = appUri.replace(/(^\w+:|^)\/\//, ''); ++ let entry = this.lookupByDBusName(senderName); ++ ++ if (entry) { ++ entry.setDBusName(senderName); ++ entry.update(properties); ++ } else { ++ let entry = new LauncherEntryRemote(senderName, appId, properties); ++ this.addEntry(entry); ++ } ++ }, ++}); ++ ++Signals.addSignalMethods(LauncherEntryRemoteModel.prototype); ++ ++var LauncherEntryRemote = new Lang.Class({ ++ Name: 'DashToDock.LauncherEntryRemote', ++ ++ _init: function (dbusName, appId, properties) { ++ this._dbusName = dbusName; ++ this._appId = appId; ++ this._count = 0; ++ this._countVisible = false; ++ this._progress = 0.0; ++ this._progressVisible = false; ++ this.update(properties); ++ }, ++ ++ appId: function () { ++ return this._appId; ++ }, ++ ++ dbusName: function () { ++ return this._dbusName; ++ }, ++ ++ count: function () { ++ return this._count; ++ }, ++ ++ setCount: function (count) { ++ if (this._count != count) { ++ this._count = count; ++ this.emit('count-changed', this._count); ++ } ++ }, ++ ++ countVisible: function () { ++ return this._countVisible; ++ }, ++ ++ setCountVisible: function (countVisible) { ++ if (this._countVisible != countVisible) { ++ this._countVisible = countVisible; ++ this.emit('count-visible-changed', this._countVisible); ++ } ++ }, ++ ++ progress: function () { ++ return this._progress; ++ }, ++ ++ setProgress: function (progress) { ++ if (this._progress != progress) { ++ this._progress = progress; ++ this.emit('progress-changed', this._progress); ++ } ++ }, ++ ++ progressVisible: function () { ++ return this._progressVisible; ++ }, ++ ++ setProgressVisible: function (progressVisible) { ++ if (this._progressVisible != progressVisible) { ++ this._progressVisible = progressVisible; ++ this.emit('progress-visible-changed', this._progressVisible); ++ } ++ }, ++ ++ setDBusName: function (dbusName) { ++ if (this._dbusName != dbusName) { ++ let oldName = this._dbusName; ++ this._dbusName = dbusName; ++ this.emit('dbus-name-changed', oldName); ++ } ++ }, ++ ++ update: function (other) { ++ if (other instanceof LauncherEntryRemote) { ++ this.setDBusName(other.dbusName()) ++ this.setCount(other.count()); ++ this.setCountVisible(other.countVisible()); ++ this.setProgress(other.progress()); ++ this.setProgressVisible(other.progressVisible()) ++ } else { ++ for (let property in other) { ++ if (other.hasOwnProperty(property)) { ++ if (property == 'count') { ++ this.setCount(other[property].get_int64()); ++ } else if (property == 'count-visible') { ++ this.setCountVisible(other[property].get_boolean()); ++ } if (property == 'progress') { ++ this.setProgress(other[property].get_double()); ++ } else if (property == 'progress-visible') { ++ this.setProgressVisible(other[property].get_boolean()); ++ } else { ++ // Not implemented yet ++ } ++ } ++ } ++ } ++ }, ++}); ++ ++Signals.addSignalMethods(LauncherEntryRemote.prototype); +diff --git a/extensions/dash-to-dock/media/glossy.svg b/extensions/dash-to-dock/media/glossy.svg +new file mode 100644 +index 0000000..55b71ba +--- /dev/null ++++ b/extensions/dash-to-dock/media/glossy.svg +@@ -0,0 +1,139 @@ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ image/svg+xml ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ +diff --git a/extensions/dash-to-dock/media/logo.svg b/extensions/dash-to-dock/media/logo.svg +new file mode 100644 +index 0000000..eebd0b1 +--- /dev/null ++++ b/extensions/dash-to-dock/media/logo.svg +@@ -0,0 +1,528 @@ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ image/svg+xml ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ Dash to Dock ++ Michele ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ +diff --git a/extensions/dash-to-dock/meson.build b/extensions/dash-to-dock/meson.build +new file mode 100644 +index 0000000..e0906fa +--- /dev/null ++++ b/extensions/dash-to-dock/meson.build +@@ -0,0 +1,23 @@ ++extension_data += configure_file( ++ input: metadata_name + '.in', ++ output: metadata_name, ++ configuration: metadata_conf ++) ++ ++extension_sources += files( ++ 'appIconIndicators.js', ++ 'appIcons.js', ++ 'convenience.js', ++ 'dash.js', ++ 'docking.js', ++ 'intellihide.js', ++ 'launcherAPI.js', ++ 'prefs.js', ++ 'Settings.ui', ++ 'theming.js', ++ 'utils.js', ++ 'windowPreview.js' ++) ++extension_schemas += files(metadata_conf.get('gschemaname') + '.gschema.xml') ++ ++install_data(['media/logo.svg', 'media/glossy.svg'], install_dir: join_paths(extensiondir, uuid, 'media')) +diff --git a/extensions/dash-to-dock/metadata.json.in b/extensions/dash-to-dock/metadata.json.in +new file mode 100644 +index 0000000..90eddb5 +--- /dev/null ++++ b/extensions/dash-to-dock/metadata.json.in +@@ -0,0 +1,12 @@ ++{ ++"extension-id": "@extension_id@", ++"uuid": "@uuid@", ++"settings-schema": "@gschemaname@", ++"gettext-domain": "@gettext_domain@", ++"original-author": "micxgx@gmail.com", ++"name": "Dash to Dock", ++"description": "A dock for the Gnome Shell. This extension moves the dash out of the overview transforming it in a dock for an easier launching of applications and a faster switching between windows and desktops. Side and bottom placement options are available.", ++"shell-version": [ "@shell_current@" ], ++"version": 45, ++"url": "https://micheleg.github.io/dash-to-dock/" ++} +diff --git a/extensions/dash-to-dock/org.gnome.shell.extensions.dash-to-dock.gschema.xml b/extensions/dash-to-dock/org.gnome.shell.extensions.dash-to-dock.gschema.xml +new file mode 100644 +index 0000000..3e4f68a +--- /dev/null ++++ b/extensions/dash-to-dock/org.gnome.shell.extensions.dash-to-dock.gschema.xml +@@ -0,0 +1,540 @@ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ 'LEFT' ++ Dock position ++ Dock is shown on the Left, Right, Top or Bottom side of the screen. ++ ++ ++ 0.2 ++ Animation time ++ Sets the time duration of the autohide effect. ++ ++ ++ 0.25 ++ Show delay ++ Sets the delay after the mouse reaches the screen border before showing the dock. ++ ++ ++ 0.20 ++ Show delay ++ Sets the delay after the mouse left the dock before hiding it. ++ ++ ++ false ++ Set a custom dash background background color ++ Sets the color for the dash background. ++ ++ ++ "#ffffff" ++ Dash background color. ++ Customize the background color of the dash. ++ ++ ++ 'DEFAULT' ++ Transparency mode for the dock ++ FIXED: constant transparency. ADAPTIVE: lock state with the top panel when not hidden. DYNAMIC: dock takes the opaque style only when windows are close to it. ++ ++ ++ 'DEFAULT' ++ ... ++ DEFAULT: .... DOTS: .... ++ ++ ++ false ++ Use application icon dominant color for the indicator color ++ ++ ++ ++ false ++ Manually set the min and max opacity ++ For Adaptive and Dynamic modes, the min/max opacity values will be given by 'min-alpha' and 'max-alpha'. ++ ++ ++ 0.2 ++ Opacity of the dash background when free-floating ++ Sets the opacity of the dash background when no windows are close. ++ ++ ++ 0.8 ++ Opacity of the dash background when windows are close. ++ Sets the opacity of the dash background when windows are close. ++ ++ ++ 0.8 ++ Opacity of the dash background ++ Sets the opacity of the dash background when in autohide mode. ++ ++ ++ true ++ Dock dodges windows ++ Enable or disable intellihide mode ++ ++ ++ 'FOCUS_APPLICATION_WINDOWS' ++ Define which windows are considered for intellihide. ++ ++ ++ ++ true ++ Dock shown on mouse over ++ Enable or disable autohide mode ++ ++ ++ true ++ Require pressure to show dash ++ Enable or disable requiring pressure to show the dash ++ ++ ++ 100 ++ Pressure threshold ++ Sets how much pressure is needed to show the dash. ++ ++ ++ false ++ Enable autohide in fullscreen mode. ++ Enable autohide in fullscreen mode. ++ ++ ++ false ++ Dock always visible ++ Dock is always visible ++ ++ ++ true ++ Switch workspace by scrolling over the dock ++ Add the possibility to switch workspace by mouse scrolling over the dock. ++ ++ ++ 48 ++ Maximum dash icon size ++ Set the allowed maximum dash icon size. Allowed range: 16..64. ++ ++ ++ false ++ Fixed icon size ++ Keep the icon size fived by scrolling the dock. ++ ++ ++ false ++ Apply custom theme ++ Apply customization to the dash appearance ++ ++ ++ false ++ TODO ++ TODO ++ ++ ++ false ++ Customize the style of the running application indicators. ++ Customize the style of the running application indicators. ++ ++ ++ "#ffffff" ++ Running application indicators color ++ Customize the color of the running application indicators. ++ ++ ++ "#ffffff" ++ Running application indicators border color. ++ Customize the border color of the running application indicators. ++ ++ ++ 0 ++ Running application indicators border width. ++ Customize the border width of the running application indicators. ++ ++ ++ true ++ Show running apps ++ Show or hide running appplications icons in the dash ++ ++ ++ false ++ Provide workspace isolation ++ Dash shows only windows from the currentworkspace ++ ++ ++ false ++ Provide monitor isolation ++ Dash shows only windows from the monitor ++ ++ ++ true ++ Show preview of the open windows ++ Replace open windows list with windows previews ++ ++ ++ true ++ Show favorites apps ++ Show or hide favorite appplications icons in the dash ++ ++ ++ true ++ Show applications button ++ Show appplications button in the dash ++ ++ ++ false ++ Show application button at top ++ Show appplication button at top of the dash ++ ++ ++ true ++ Animate Show Applications from the desktop ++ Animate Show Applications from the desktop ++ ++ ++ true ++ Basic compatibility with bolt extensions ++ Make the extension work properly when bolt extensions is enabled ++ ++ ++ 0.90 ++ Dock max height (fraction of available space) ++ ++ ++ false ++ Extend the dock container to all the available height ++ ++ ++ -1 ++ Monitor on which putting the dock ++ Set on which monitor to put the dock, use -1 for the primary one ++ ++ ++ false ++ Enable multi-monitor docks ++ Show a dock on every monitor ++ ++ ++ true ++ Minimize on shift+click ++ ++ ++ true ++ Activate only one window ++ ++ ++ 'cycle-windows' ++ Action when clicking on a running app ++ Set the action that is executed when clicking on the icon of a running application ++ ++ ++ 'do-nothing' ++ Action when scrolling app ++ Set the action that is executed when scrolling on the application icon ++ ++ ++ 'minimize' ++ Action when shit+clicking on a running app ++ Set the action that is executed when shift+clicking on the icon of a running application ++ ++ ++ 'launch' ++ Action when clicking on a running app ++ Set the action that is executed when middle-clicking on the icon of a running application ++ ++ ++ 'launch' ++ Action when clicking on a running app ++ Set the action that is executed when shift+middle-clicking on the icon of a running application ++ ++ ++ true ++ Super Hot-Keys ++ Launch and switch between dash items using Super+(0-9) ++ ++ ++ true ++ Show the dock when using the hotkeys ++ The dock will be quickly shown so that the number-overlay is visible and app activation is easier ++ ++ ++ "<Super>q" ++ Keybinding to show the dock and the number overlay. ++ Behavior depends on hotkeys-show-dock and hotkeys-overlay. ++ ++ ++ q']]]> ++ Keybinding to show the dock and the number overlay. ++ Behavior depends on hotkeys-show-dock and hotkeys-overlay. ++ ++ ++ 2 ++ Timeout to hide the dock ++ Sets the time duration before the dock is hidden again. ++ ++ ++ true ++ Show the dock when using the hotkeys ++ The dock will be quickly shown so that the number-overlay is visible and app activation is easier ++ ++ ++ 1']]]> ++ Keybinding to launch 1st dash app ++ ++ Keybinding to launch 1st app. ++ ++ ++ ++ 2']]]> ++ Keybinding to launch 2nd dash app ++ ++ Keybinding to launch 2nd app. ++ ++ ++ ++ 3']]]> ++ Keybinding to launch 3rd dash app ++ ++ Keybinding to launch 3rd app. ++ ++ ++ ++ 4']]]> ++ Keybinding to launch 4th dash app ++ ++ Keybinding to launch 4th app. ++ ++ ++ ++ 5']]]> ++ Keybinding to launch 5th dash app ++ ++ Keybinding to launch 5th app. ++ ++ ++ ++ 6']]]> ++ Keybinding to launch 6th dash app ++ ++ Keybinding to launch 6th app. ++ ++ ++ ++ 7']]]> ++ Keybinding to launch 7th dash app ++ ++ Keybinding to launch 7th app. ++ ++ ++ ++ 8']]]> ++ Keybinding to launch 8th dash app ++ ++ Keybinding to launch 8th app. ++ ++ ++ ++ 9']]]> ++ Keybinding to launch 9th dash app ++ ++ Keybinding to launch 9th app. ++ ++ ++ ++ 0']]]> ++ Keybinding to launch 10th dash app ++ ++ Keybinding to launch 10th app. ++ ++ ++ ++ 1']]]> ++ Keybinding to trigger 1st dash app with shift behavior ++ ++ Keybinding to trigger 1st app with shift behavior. ++ ++ ++ ++ 2']]]> ++ Keybinding to trigger 2nd dash app with shift behavior ++ ++ Keybinding to trigger 2nd app with shift behavior. ++ ++ ++ ++ 3']]]> ++ Keybinding to trigger 3rd dash app with shift behavior ++ ++ Keybinding to trigger 3rd app with shift behavior. ++ ++ ++ ++ 4']]]> ++ Keybinding to trigger 4th dash app with shift behavior ++ ++ Keybinding to trigger 4th app with shift behavior. ++ ++ ++ ++ 5']]]> ++ Keybinding to trigger 5th dash app with shift behavior ++ ++ Keybinding to trigger 5th app with shift behavior. ++ ++ ++ ++ 6']]]> ++ Keybinding to trigger 6th dash app with shift behavior ++ ++ Keybinding to trigger 6th app with shift behavior. ++ ++ ++ ++ 7']]]> ++ Keybinding to trigger 7th dash app with shift behavior ++ ++ Keybinding to trigger 7th app with shift behavior. ++ ++ ++ ++ 8']]]> ++ Keybinding to trigger 8th dash app with shift behavior ++ ++ Keybinding to trigger 8th app with shift behavior. ++ ++ ++ ++ 9']]]> ++ Keybinding to trigger 9th dash app with shift behavior ++ ++ Keybinding to trigger 9th app with shift behavior. ++ ++ ++ ++ 0']]]> ++ Keybinding to trigger 10th dash app with shift behavior ++ ++ Keybinding to trigger 10th app with shift behavior. ++ ++ ++ ++ 1']]]> ++ Keybinding to trigger 1st dash app ++ ++ Keybinding to either show or launch the 1st application in the dash. ++ ++ ++ ++ 2']]]> ++ Keybinding to trigger 2nd dash app ++ ++ Keybinding to either show or launch the 2nd application in the dash. ++ ++ ++ ++ 3']]]> ++ Keybinding to trigger 3rd dash app ++ ++ Keybinding to either show or launch the 3rd application in the dash. ++ ++ ++ ++ 4']]]> ++ Keybinding to trigger 4th dash app ++ ++ Keybinding to either show or launch the 4th application in the dash. ++ ++ ++ ++ 5']]]> ++ Keybinding to trigger 5th dash app ++ ++ Keybinding to either show or launch the 5th application in the dash. ++ ++ ++ ++ 6']]]> ++ Keybinding to trigger 6th dash app ++ ++ Keybinding to either show or launch the 6th application in the dash. ++ ++ ++ ++ 7']]]> ++ Keybinding to trigger 7th dash app ++ ++ Keybinding to either show or launch the 7th application in the dash. ++ ++ ++ ++ 8']]]> ++ Keybinding to trigger 8th dash app ++ ++ Keybinding to either show or launch the 8th application in the dash. ++ ++ ++ ++ 9']]]> ++ Keybinding to trigger 9th dash app ++ ++ Keybinding to either show or launch the 9th application in the dash. ++ ++ ++ ++ 0']]]> ++ Keybinding to trigger 10th dash app ++ ++ Keybinding to either show or launch the 10th application in the dash. ++ ++ ++ ++ false ++ Force straight corners in dash ++ Make the borders in the dash non rounded ++ ++ ++ false ++ Enable unity7 like glossy backlit items ++ Emulate the unity7 backlit glossy items behaviour ++ ++ ++ +diff --git a/extensions/dash-to-dock/prefs.js b/extensions/dash-to-dock/prefs.js +new file mode 100644 +index 0000000..d8d8b94 +--- /dev/null ++++ b/extensions/dash-to-dock/prefs.js +@@ -0,0 +1,868 @@ ++// -*- mode: js; js-indent-level: 4; indent-tabs-mode: nil -*- ++ ++const Gio = imports.gi.Gio; ++const GLib = imports.gi.GLib; ++const GObject = imports.gi.GObject; ++const Gtk = imports.gi.Gtk; ++const Gdk = imports.gi.Gdk; ++const Lang = imports.lang; ++const Mainloop = imports.mainloop; ++ ++// Use __ () and N__() for the extension gettext domain, and reuse ++// the shell domain with the default _() and N_() ++const Gettext = imports.gettext.domain('dashtodock'); ++const __ = Gettext.gettext; ++const N__ = function(e) { return e }; ++ ++const ExtensionUtils = imports.misc.extensionUtils; ++const Me = ExtensionUtils.getCurrentExtension(); ++const Convenience = Me.imports.convenience; ++ ++const SCALE_UPDATE_TIMEOUT = 500; ++const DEFAULT_ICONS_SIZES = [ 128, 96, 64, 48, 32, 24, 16 ]; ++ ++const TransparencyMode = { ++ DEFAULT: 0, ++ FIXED: 1, ++ ADAPTIVE: 2, ++ DYNAMIC: 3 ++}; ++ ++const RunningIndicatorStyle = { ++ DEFAULT: 0, ++ DOTS: 1, ++ SQUARES: 2, ++ DASHES: 3, ++ SEGMENTED: 4, ++ SOLID: 5, ++ CILIORA: 6, ++ METRO: 7 ++}; ++ ++/** ++ * This function was copied from the activities-config extension ++ * https://github.com/nls1729/acme-code/tree/master/activities-config ++ * by Norman L. Smith. ++ */ ++function cssHexString(css) { ++ let rrggbb = '#'; ++ let start; ++ for (let loop = 0; loop < 3; loop++) { ++ let end = 0; ++ let xx = ''; ++ for (let loop = 0; loop < 2; loop++) { ++ while (true) { ++ let x = css.slice(end, end + 1); ++ if ((x == '(') || (x == ',') || (x == ')')) ++ break; ++ end++; ++ } ++ if (loop == 0) { ++ end++; ++ start = end; ++ } ++ } ++ xx = parseInt(css.slice(start, end)).toString(16); ++ if (xx.length == 1) ++ xx = '0' + xx; ++ rrggbb += xx; ++ css = css.slice(end); ++ } ++ return rrggbb; ++} ++ ++function setShortcut(settings) { ++ let shortcut_text = settings.get_string('shortcut-text'); ++ let [key, mods] = Gtk.accelerator_parse(shortcut_text); ++ ++ if (Gtk.accelerator_valid(key, mods)) { ++ let shortcut = Gtk.accelerator_name(key, mods); ++ settings.set_strv('shortcut', [shortcut]); ++ } ++ else { ++ settings.set_strv('shortcut', []); ++ } ++} ++ ++const Settings = new Lang.Class({ ++ Name: 'DashToDock.Settings', ++ ++ _init: function() { ++ this._settings = Convenience.getSettings('org.gnome.shell.extensions.dash-to-dock'); ++ ++ this._rtl = (Gtk.Widget.get_default_direction() == Gtk.TextDirection.RTL); ++ ++ this._builder = new Gtk.Builder(); ++ this._builder.set_translation_domain(Me.metadata['gettext-domain']); ++ this._builder.add_from_file(Me.path + '/Settings.ui'); ++ ++ this.widget = new Gtk.ScrolledWindow({ hscrollbar_policy: Gtk.PolicyType.NEVER }); ++ this._notebook = this._builder.get_object('settings_notebook'); ++ this.widget.add(this._notebook); ++ ++ // Set a reasonable initial window height ++ this.widget.connect('realize', Lang.bind(this, function() { ++ let window = this.widget.get_toplevel(); ++ let [default_width, default_height] = window.get_default_size(); ++ window.resize(default_width, 650); ++ })); ++ ++ // Timeout to delay the update of the settings ++ this._dock_size_timeout = 0; ++ this._icon_size_timeout = 0; ++ this._opacity_timeout = 0; ++ ++ this._bindSettings(); ++ ++ this._builder.connect_signals_full(Lang.bind(this, this._connector)); ++ }, ++ ++ /** ++ * Connect signals ++ */ ++ _connector: function(builder, object, signal, handler) { ++ object.connect(signal, Lang.bind(this, this._SignalHandler[handler])); ++ }, ++ ++ _bindSettings: function() { ++ // Position and size panel ++ ++ // Monitor options ++ ++ this._monitors = []; ++ // Build options based on the number of monitors and the current settings. ++ let n_monitors = Gdk.Screen.get_default().get_n_monitors(); ++ let primary_monitor = Gdk.Screen.get_default().get_primary_monitor(); ++ ++ let monitor = this._settings.get_int('preferred-monitor'); ++ ++ // Add primary monitor with index 0, because in GNOME Shell the primary monitor is always 0 ++ this._builder.get_object('dock_monitor_combo').append_text(__('Primary monitor')); ++ this._monitors.push(0); ++ ++ // Add connected monitors ++ let ctr = 0; ++ for (let i = 0; i < n_monitors; i++) { ++ if (i !== primary_monitor) { ++ ctr++; ++ this._monitors.push(ctr); ++ this._builder.get_object('dock_monitor_combo').append_text(__('Secondary monitor ') + ctr); ++ } ++ } ++ ++ // If one of the external monitor is set as preferred, show it even if not attached ++ if ((monitor >= n_monitors) && (monitor !== primary_monitor)) { ++ this._monitors.push(monitor) ++ this._builder.get_object('dock_monitor_combo').append_text(__('Secondary monitor ') + ++ctr); ++ } ++ ++ this._builder.get_object('dock_monitor_combo').set_active(this._monitors.indexOf(monitor)); ++ ++ // Position option ++ let position = this._settings.get_enum('dock-position'); ++ ++ switch (position) { ++ case 0: ++ this._builder.get_object('position_top_button').set_active(true); ++ break; ++ case 1: ++ this._builder.get_object('position_right_button').set_active(true); ++ break; ++ case 2: ++ this._builder.get_object('position_bottom_button').set_active(true); ++ break; ++ case 3: ++ this._builder.get_object('position_left_button').set_active(true); ++ break; ++ } ++ ++ if (this._rtl) { ++ /* Left is Right in rtl as a setting */ ++ this._builder.get_object('position_left_button').set_label(__('Right')); ++ this._builder.get_object('position_right_button').set_label(__('Left')); ++ } ++ ++ // Intelligent autohide options ++ this._settings.bind('dock-fixed', ++ this._builder.get_object('intelligent_autohide_switch'), ++ 'active', ++ Gio.SettingsBindFlags.INVERT_BOOLEAN); ++ this._settings.bind('dock-fixed', ++ this._builder.get_object('intelligent_autohide_button'), ++ 'sensitive', ++ Gio.SettingsBindFlags.INVERT_BOOLEAN); ++ this._settings.bind('autohide', ++ this._builder.get_object('autohide_switch'), ++ 'active', ++ Gio.SettingsBindFlags.DEFAULT); ++ this._settings.bind('autohide-in-fullscreen', ++ this._builder.get_object('autohide_enable_in_fullscreen_checkbutton'), ++ 'active', ++ Gio.SettingsBindFlags.DEFAULT); ++ this._settings.bind('require-pressure-to-show', ++ this._builder.get_object('require_pressure_checkbutton'), ++ 'active', ++ Gio.SettingsBindFlags.DEFAULT); ++ this._settings.bind('intellihide', ++ this._builder.get_object('intellihide_switch'), ++ 'active', ++ Gio.SettingsBindFlags.DEFAULT); ++ this._settings.bind('animation-time', ++ this._builder.get_object('animation_duration_spinbutton'), ++ 'value', ++ Gio.SettingsBindFlags.DEFAULT); ++ this._settings.bind('hide-delay', ++ this._builder.get_object('hide_timeout_spinbutton'), ++ 'value', ++ Gio.SettingsBindFlags.DEFAULT); ++ this._settings.bind('show-delay', ++ this._builder.get_object('show_timeout_spinbutton'), ++ 'value', ++ Gio.SettingsBindFlags.DEFAULT); ++ this._settings.bind('pressure-threshold', ++ this._builder.get_object('pressure_threshold_spinbutton'), ++ 'value', ++ Gio.SettingsBindFlags.DEFAULT); ++ ++ //this._builder.get_object('animation_duration_spinbutton').set_value(this._settings.get_double('animation-time')); ++ ++ // Create dialog for intelligent autohide advanced settings ++ this._builder.get_object('intelligent_autohide_button').connect('clicked', Lang.bind(this, function() { ++ ++ let dialog = new Gtk.Dialog({ title: __('Intelligent autohide customization'), ++ transient_for: this.widget.get_toplevel(), ++ use_header_bar: true, ++ modal: true }); ++ ++ // GTK+ leaves positive values for application-defined response ids. ++ // Use +1 for the reset action ++ dialog.add_button(__('Reset to defaults'), 1); ++ ++ let box = this._builder.get_object('intelligent_autohide_advanced_settings_box'); ++ dialog.get_content_area().add(box); ++ ++ this._settings.bind('intellihide', ++ this._builder.get_object('intellihide_mode_box'), ++ 'sensitive', ++ Gio.SettingsBindFlags.GET); ++ ++ // intellihide mode ++ ++ let intellihideModeRadioButtons = [ ++ this._builder.get_object('all_windows_radio_button'), ++ this._builder.get_object('focus_application_windows_radio_button'), ++ this._builder.get_object('maximized_windows_radio_button') ++ ]; ++ ++ intellihideModeRadioButtons[this._settings.get_enum('intellihide-mode')].set_active(true); ++ ++ this._settings.bind('autohide', ++ this._builder.get_object('require_pressure_checkbutton'), ++ 'sensitive', ++ Gio.SettingsBindFlags.GET); ++ ++ this._settings.bind('autohide', ++ this._builder.get_object('autohide_enable_in_fullscreen_checkbutton'), ++ 'sensitive', ++ Gio.SettingsBindFlags.GET); ++ ++ this._settings.bind('require-pressure-to-show', ++ this._builder.get_object('show_timeout_spinbutton'), ++ 'sensitive', ++ Gio.SettingsBindFlags.INVERT_BOOLEAN); ++ this._settings.bind('require-pressure-to-show', ++ this._builder.get_object('show_timeout_label'), ++ 'sensitive', ++ Gio.SettingsBindFlags.INVERT_BOOLEAN); ++ this._settings.bind('require-pressure-to-show', ++ this._builder.get_object('pressure_threshold_spinbutton'), ++ 'sensitive', ++ Gio.SettingsBindFlags.DEFAULT); ++ this._settings.bind('require-pressure-to-show', ++ this._builder.get_object('pressure_threshold_label'), ++ 'sensitive', ++ Gio.SettingsBindFlags.DEFAULT); ++ ++ dialog.connect('response', Lang.bind(this, function(dialog, id) { ++ if (id == 1) { ++ // restore default settings for the relevant keys ++ let keys = ['intellihide', 'autohide', 'intellihide-mode', 'autohide-in-fullscreen', 'require-pressure-to-show', ++ 'animation-time', 'show-delay', 'hide-delay', 'pressure-threshold']; ++ keys.forEach(function(val) { ++ this._settings.set_value(val, this._settings.get_default_value(val)); ++ }, this); ++ intellihideModeRadioButtons[this._settings.get_enum('intellihide-mode')].set_active(true); ++ } else { ++ // remove the settings box so it doesn't get destroyed; ++ dialog.get_content_area().remove(box); ++ dialog.destroy(); ++ } ++ return; ++ })); ++ ++ dialog.show_all(); ++ ++ })); ++ ++ // size options ++ this._builder.get_object('dock_size_scale').set_value(this._settings.get_double('height-fraction')); ++ this._builder.get_object('dock_size_scale').add_mark(0.9, Gtk.PositionType.TOP, null); ++ let icon_size_scale = this._builder.get_object('icon_size_scale'); ++ icon_size_scale.set_range(8, DEFAULT_ICONS_SIZES[0]); ++ icon_size_scale.set_value(this._settings.get_int('dash-max-icon-size')); ++ DEFAULT_ICONS_SIZES.forEach(function(val) { ++ icon_size_scale.add_mark(val, Gtk.PositionType.TOP, val.toString()); ++ }); ++ ++ // Corrent for rtl languages ++ if (this._rtl) { ++ // Flip value position: this is not done automatically ++ this._builder.get_object('dock_size_scale').set_value_pos(Gtk.PositionType.LEFT); ++ icon_size_scale.set_value_pos(Gtk.PositionType.LEFT); ++ // I suppose due to a bug, having a more than one mark and one above a value of 100 ++ // makes the rendering of the marks wrong in rtl. This doesn't happen setting the scale as not flippable ++ // and then manually inverting it ++ icon_size_scale.set_flippable(false); ++ icon_size_scale.set_inverted(true); ++ } ++ ++ this._settings.bind('icon-size-fixed', this._builder.get_object('icon_size_fixed_checkbutton'), 'active', Gio.SettingsBindFlags.DEFAULT); ++ this._settings.bind('extend-height', this._builder.get_object('dock_size_extend_checkbutton'), 'active', Gio.SettingsBindFlags.DEFAULT); ++ this._settings.bind('extend-height', this._builder.get_object('dock_size_scale'), 'sensitive', Gio.SettingsBindFlags.INVERT_BOOLEAN); ++ ++ ++ // Apps panel ++ ++ this._settings.bind('show-running', ++ this._builder.get_object('show_running_switch'), ++ 'active', ++ Gio.SettingsBindFlags.DEFAULT); ++ this._settings.bind('isolate-workspaces', ++ this._builder.get_object('application_button_isolation_button'), ++ 'active', ++ Gio.SettingsBindFlags.DEFAULT); ++ this._settings.bind('isolate-monitors', ++ this._builder.get_object('application_button_monitor_isolation_button'), ++ 'active', ++ Gio.SettingsBindFlags.DEFAULT); ++ this._settings.bind('show-windows-preview', ++ this._builder.get_object('windows_preview_button'), ++ 'active', ++ Gio.SettingsBindFlags.DEFAULT); ++ this._settings.bind('multi-monitor', ++ this._builder.get_object('multi_monitor_button'), ++ 'active', ++ Gio.SettingsBindFlags.DEFAULT); ++ this._settings.bind('show-favorites', ++ this._builder.get_object('show_favorite_switch'), ++ 'active', ++ Gio.SettingsBindFlags.DEFAULT); ++ this._settings.bind('show-show-apps-button', ++ this._builder.get_object('show_applications_button_switch'), ++ 'active', ++ Gio.SettingsBindFlags.DEFAULT); ++ this._settings.bind('show-apps-at-top', ++ this._builder.get_object('application_button_first_button'), ++ 'active', ++ Gio.SettingsBindFlags.DEFAULT); ++ this._settings.bind('show-show-apps-button', ++ this._builder.get_object('application_button_first_button'), ++ 'sensitive', ++ Gio.SettingsBindFlags.DEFAULT); ++ this._settings.bind('animate-show-apps', ++ this._builder.get_object('application_button_animation_button'), ++ 'active', ++ Gio.SettingsBindFlags.DEFAULT); ++ this._settings.bind('show-show-apps-button', ++ this._builder.get_object('application_button_animation_button'), ++ 'sensitive', ++ Gio.SettingsBindFlags.DEFAULT); ++ ++ ++ // Behavior panel ++ ++ this._settings.bind('hot-keys', ++ this._builder.get_object('hot_keys_switch'), ++ 'active', ++ Gio.SettingsBindFlags.DEFAULT); ++ this._settings.bind('hot-keys', ++ this._builder.get_object('overlay_button'), ++ 'sensitive', ++ Gio.SettingsBindFlags.DEFAULT); ++ ++ this._builder.get_object('click_action_combo').set_active(this._settings.get_enum('click-action')); ++ this._builder.get_object('click_action_combo').connect('changed', Lang.bind (this, function(widget) { ++ this._settings.set_enum('click-action', widget.get_active()); ++ })); ++ ++ this._builder.get_object('scroll_action_combo').set_active(this._settings.get_enum('scroll-action')); ++ this._builder.get_object('scroll_action_combo').connect('changed', Lang.bind (this, function(widget) { ++ this._settings.set_enum('scroll-action', widget.get_active()); ++ })); ++ ++ this._builder.get_object('shift_click_action_combo').connect('changed', Lang.bind (this, function(widget) { ++ this._settings.set_enum('shift-click-action', widget.get_active()); ++ })); ++ ++ this._builder.get_object('middle_click_action_combo').connect('changed', Lang.bind (this, function(widget) { ++ this._settings.set_enum('middle-click-action', widget.get_active()); ++ })); ++ this._builder.get_object('shift_middle_click_action_combo').connect('changed', Lang.bind (this, function(widget) { ++ this._settings.set_enum('shift-middle-click-action', widget.get_active()); ++ })); ++ ++ // Create dialog for number overlay options ++ this._builder.get_object('overlay_button').connect('clicked', Lang.bind(this, function() { ++ ++ let dialog = new Gtk.Dialog({ title: __('Show dock and application numbers'), ++ transient_for: this.widget.get_toplevel(), ++ use_header_bar: true, ++ modal: true }); ++ ++ // GTK+ leaves positive values for application-defined response ids. ++ // Use +1 for the reset action ++ dialog.add_button(__('Reset to defaults'), 1); ++ ++ let box = this._builder.get_object('box_overlay_shortcut'); ++ dialog.get_content_area().add(box); ++ ++ this._builder.get_object('overlay_switch').set_active(this._settings.get_boolean('hotkeys-overlay')); ++ this._builder.get_object('show_dock_switch').set_active(this._settings.get_boolean('hotkeys-show-dock')); ++ ++ // We need to update the shortcut 'strv' when the text is modified ++ this._settings.connect('changed::shortcut-text', Lang.bind(this, function() {setShortcut(this._settings);})); ++ this._settings.bind('shortcut-text', ++ this._builder.get_object('shortcut_entry'), ++ 'text', ++ Gio.SettingsBindFlags.DEFAULT); ++ ++ this._settings.bind('hotkeys-overlay', ++ this._builder.get_object('overlay_switch'), ++ 'active', ++ Gio.SettingsBindFlags.DEFAULT); ++ this._settings.bind('hotkeys-show-dock', ++ this._builder.get_object('show_dock_switch'), ++ 'active', ++ Gio.SettingsBindFlags.DEFAULT); ++ this._settings.bind('shortcut-timeout', ++ this._builder.get_object('timeout_spinbutton'), ++ 'value', ++ Gio.SettingsBindFlags.DEFAULT); ++ ++ dialog.connect('response', Lang.bind(this, function(dialog, id) { ++ if (id == 1) { ++ // restore default settings for the relevant keys ++ let keys = ['shortcut-text', 'hotkeys-overlay', 'hotkeys-show-dock', 'shortcut-timeout']; ++ keys.forEach(function(val) { ++ this._settings.set_value(val, this._settings.get_default_value(val)); ++ }, this); ++ } else { ++ // remove the settings box so it doesn't get destroyed; ++ dialog.get_content_area().remove(box); ++ dialog.destroy(); ++ } ++ return; ++ })); ++ ++ dialog.show_all(); ++ ++ })); ++ ++ // Create dialog for middle-click options ++ this._builder.get_object('middle_click_options_button').connect('clicked', Lang.bind(this, function() { ++ ++ let dialog = new Gtk.Dialog({ title: __('Customize middle-click behavior'), ++ transient_for: this.widget.get_toplevel(), ++ use_header_bar: true, ++ modal: true }); ++ ++ // GTK+ leaves positive values for application-defined response ids. ++ // Use +1 for the reset action ++ dialog.add_button(__('Reset to defaults'), 1); ++ ++ let box = this._builder.get_object('box_middle_click_options'); ++ dialog.get_content_area().add(box); ++ ++ this._builder.get_object('shift_click_action_combo').set_active(this._settings.get_enum('shift-click-action')); ++ ++ this._builder.get_object('middle_click_action_combo').set_active(this._settings.get_enum('middle-click-action')); ++ ++ this._builder.get_object('shift_middle_click_action_combo').set_active(this._settings.get_enum('shift-middle-click-action')); ++ ++ this._settings.bind('shift-click-action', ++ this._builder.get_object('shift_click_action_combo'), ++ 'active-id', ++ Gio.SettingsBindFlags.DEFAULT); ++ this._settings.bind('middle-click-action', ++ this._builder.get_object('middle_click_action_combo'), ++ 'active-id', ++ Gio.SettingsBindFlags.DEFAULT); ++ this._settings.bind('shift-middle-click-action', ++ this._builder.get_object('shift_middle_click_action_combo'), ++ 'active-id', ++ Gio.SettingsBindFlags.DEFAULT); ++ ++ dialog.connect('response', Lang.bind(this, function(dialog, id) { ++ if (id == 1) { ++ // restore default settings for the relevant keys ++ let keys = ['shift-click-action', 'middle-click-action', 'shift-middle-click-action']; ++ keys.forEach(function(val) { ++ this._settings.set_value(val, this._settings.get_default_value(val)); ++ }, this); ++ this._builder.get_object('shift_click_action_combo').set_active(this._settings.get_enum('shift-click-action')); ++ this._builder.get_object('middle_click_action_combo').set_active(this._settings.get_enum('middle-click-action')); ++ this._builder.get_object('shift_middle_click_action_combo').set_active(this._settings.get_enum('shift-middle-click-action')); ++ } else { ++ // remove the settings box so it doesn't get destroyed; ++ dialog.get_content_area().remove(box); ++ dialog.destroy(); ++ } ++ return; ++ })); ++ ++ dialog.show_all(); ++ ++ })); ++ ++ // Appearance Panel ++ ++ this._settings.bind('apply-custom-theme', this._builder.get_object('customize_theme'), 'sensitive', Gio.SettingsBindFlags.INVERT_BOOLEAN | Gio.SettingsBindFlags.GET); ++ this._settings.bind('apply-custom-theme', this._builder.get_object('builtin_theme_switch'), 'active', Gio.SettingsBindFlags.DEFAULT); ++ this._settings.bind('custom-theme-shrink', this._builder.get_object('shrink_dash_switch'), 'active', Gio.SettingsBindFlags.DEFAULT); ++ ++ // Running indicators ++ this._builder.get_object('running_indicators_combo').set_active( ++ this._settings.get_enum('running-indicator-style') ++ ); ++ this._builder.get_object('running_indicators_combo').connect( ++ 'changed', ++ Lang.bind (this, function(widget) { ++ this._settings.set_enum('running-indicator-style', widget.get_active()); ++ }) ++ ); ++ ++ if (this._settings.get_enum('running-indicator-style') == RunningIndicatorStyle.DEFAULT) ++ this._builder.get_object('running_indicators_advance_settings_button').set_sensitive(false); ++ ++ this._settings.connect('changed::running-indicator-style', Lang.bind(this, function() { ++ if (this._settings.get_enum('running-indicator-style') == RunningIndicatorStyle.DEFAULT) ++ this._builder.get_object('running_indicators_advance_settings_button').set_sensitive(false); ++ else ++ this._builder.get_object('running_indicators_advance_settings_button').set_sensitive(true); ++ })); ++ ++ // Create dialog for running indicators advanced settings ++ this._builder.get_object('running_indicators_advance_settings_button').connect('clicked', Lang.bind(this, function() { ++ ++ let dialog = new Gtk.Dialog({ title: __('Customize running indicators'), ++ transient_for: this.widget.get_toplevel(), ++ use_header_bar: true, ++ modal: true }); ++ ++ let box = this._builder.get_object('running_dots_advance_settings_box'); ++ dialog.get_content_area().add(box); ++ ++ this._settings.bind('running-indicator-dominant-color', ++ this._builder.get_object('dominant_color_switch'), ++ 'active', ++ Gio.SettingsBindFlags.DEFAULT); ++ ++ this._settings.bind('custom-theme-customize-running-dots', ++ this._builder.get_object('dot_style_switch'), ++ 'active', ++ Gio.SettingsBindFlags.DEFAULT); ++ this._settings.bind('custom-theme-customize-running-dots', ++ this._builder.get_object('dot_style_settings_box'), ++ 'sensitive', Gio.SettingsBindFlags.DEFAULT); ++ ++ let rgba = new Gdk.RGBA(); ++ rgba.parse(this._settings.get_string('custom-theme-running-dots-color')); ++ this._builder.get_object('dot_color_colorbutton').set_rgba(rgba); ++ ++ this._builder.get_object('dot_color_colorbutton').connect('notify::color', Lang.bind(this, function(button) { ++ let rgba = button.get_rgba(); ++ let css = rgba.to_string(); ++ let hexString = cssHexString(css); ++ this._settings.set_string('custom-theme-running-dots-color', hexString); ++ })); ++ ++ rgba.parse(this._settings.get_string('custom-theme-running-dots-border-color')); ++ this._builder.get_object('dot_border_color_colorbutton').set_rgba(rgba); ++ ++ this._builder.get_object('dot_border_color_colorbutton').connect('notify::color', Lang.bind(this, function(button) { ++ let rgba = button.get_rgba(); ++ let css = rgba.to_string(); ++ let hexString = cssHexString(css); ++ this._settings.set_string('custom-theme-running-dots-border-color', hexString); ++ })); ++ ++ this._settings.bind('custom-theme-running-dots-border-width', ++ this._builder.get_object('dot_border_width_spin_button'), ++ 'value', ++ Gio.SettingsBindFlags.DEFAULT); ++ ++ ++ dialog.connect('response', Lang.bind(this, function(dialog, id) { ++ // remove the settings box so it doesn't get destroyed; ++ dialog.get_content_area().remove(box); ++ dialog.destroy(); ++ return; ++ })); ++ ++ dialog.show_all(); ++ ++ })); ++ ++ this._settings.bind('custom-background-color', this._builder.get_object('custom_background_color_switch'), 'active', Gio.SettingsBindFlags.DEFAULT); ++ this._settings.bind('custom-background-color', this._builder.get_object('custom_background_color'), 'sensitive', Gio.SettingsBindFlags.DEFAULT); ++ ++ let rgba = new Gdk.RGBA(); ++ rgba.parse(this._settings.get_string('background-color')); ++ this._builder.get_object('custom_background_color').set_rgba(rgba); ++ ++ this._builder.get_object('custom_background_color').connect('notify::color', Lang.bind(this, function(button) { ++ let rgba = button.get_rgba(); ++ let css = rgba.to_string(); ++ let hexString = cssHexString(css); ++ this._settings.set_string('background-color', hexString); ++ })); ++ ++ // Opacity ++ this._builder.get_object('customize_opacity_combo').set_active( ++ this._settings.get_enum('transparency-mode') ++ ); ++ this._builder.get_object('customize_opacity_combo').connect( ++ 'changed', ++ Lang.bind (this, function(widget) { ++ this._settings.set_enum('transparency-mode', widget.get_active()); ++ }) ++ ); ++ ++ this._builder.get_object('custom_opacity_scale').set_value(this._settings.get_double('background-opacity')); ++ ++ if (this._settings.get_enum('transparency-mode') !== TransparencyMode.FIXED) ++ this._builder.get_object('custom_opacity_scale').set_sensitive(false); ++ ++ this._settings.connect('changed::transparency-mode', Lang.bind(this, function() { ++ if (this._settings.get_enum('transparency-mode') !== TransparencyMode.FIXED) ++ this._builder.get_object('custom_opacity_scale').set_sensitive(false); ++ else ++ this._builder.get_object('custom_opacity_scale').set_sensitive(true); ++ })); ++ ++ if (this._settings.get_enum('transparency-mode') !== TransparencyMode.ADAPTIVE && ++ this._settings.get_enum('transparency-mode') !== TransparencyMode.DYNAMIC) { ++ this._builder.get_object('dynamic_opacity_button').set_sensitive(false); ++ } ++ ++ this._settings.connect('changed::transparency-mode', Lang.bind(this, function() { ++ if (this._settings.get_enum('transparency-mode') !== TransparencyMode.ADAPTIVE && ++ this._settings.get_enum('transparency-mode') !== TransparencyMode.DYNAMIC) { ++ this._builder.get_object('dynamic_opacity_button').set_sensitive(false); ++ } ++ else { ++ this._builder.get_object('dynamic_opacity_button').set_sensitive(true); ++ } ++ })); ++ ++ // Create dialog for transparency advanced settings ++ this._builder.get_object('dynamic_opacity_button').connect('clicked', Lang.bind(this, function() { ++ ++ let dialog = new Gtk.Dialog({ title: __('Cutomize opacity'), ++ transient_for: this.widget.get_toplevel(), ++ use_header_bar: true, ++ modal: true }); ++ ++ let box = this._builder.get_object('advanced_transparency_dialog'); ++ dialog.get_content_area().add(box); ++ ++ this._settings.bind( ++ 'customize-alphas', ++ this._builder.get_object('customize_alphas_switch'), ++ 'active', ++ Gio.SettingsBindFlags.DEFAULT ++ ); ++ this._settings.bind( ++ 'customize-alphas', ++ this._builder.get_object('min_alpha_scale'), ++ 'sensitive', ++ Gio.SettingsBindFlags.DEFAULT ++ ); ++ this._settings.bind( ++ 'customize-alphas', ++ this._builder.get_object('max_alpha_scale'), ++ 'sensitive', ++ Gio.SettingsBindFlags.DEFAULT ++ ); ++ ++ this._builder.get_object('min_alpha_scale').set_value( ++ this._settings.get_double('min-alpha') ++ ); ++ this._builder.get_object('max_alpha_scale').set_value( ++ this._settings.get_double('max-alpha') ++ ); ++ ++ dialog.connect('response', Lang.bind(this, function(dialog, id) { ++ // remove the settings box so it doesn't get destroyed; ++ dialog.get_content_area().remove(box); ++ dialog.destroy(); ++ return; ++ })); ++ ++ dialog.show_all(); ++ })); ++ ++ ++ this._settings.bind('unity-backlit-items', ++ this._builder.get_object('unity_backlit_items_switch'), ++ 'active', Gio.SettingsBindFlags.DEFAULT ++ ); ++ ++ this._settings.bind('force-straight-corner', ++ this._builder.get_object('force_straight_corner_switch'), ++ 'active', Gio.SettingsBindFlags.DEFAULT); ++ ++ // About Panel ++ ++ this._builder.get_object('extension_version').set_label(Me.metadata.version.toString()); ++ }, ++ ++ /** ++ * Object containing all signals defined in the glade file ++ */ ++ _SignalHandler: { ++ dock_display_combo_changed_cb: function(combo) { ++ this._settings.set_int('preferred-monitor', this._monitors[combo.get_active()]); ++ }, ++ ++ position_top_button_toggled_cb: function(button) { ++ if (button.get_active()) ++ this._settings.set_enum('dock-position', 0); ++ }, ++ ++ position_right_button_toggled_cb: function(button) { ++ if (button.get_active()) ++ this._settings.set_enum('dock-position', 1); ++ }, ++ ++ position_bottom_button_toggled_cb: function(button) { ++ if (button.get_active()) ++ this._settings.set_enum('dock-position', 2); ++ }, ++ ++ position_left_button_toggled_cb: function(button) { ++ if (button.get_active()) ++ this._settings.set_enum('dock-position', 3); ++ }, ++ ++ icon_size_combo_changed_cb: function(combo) { ++ this._settings.set_int('dash-max-icon-size', this._allIconSizes[combo.get_active()]); ++ }, ++ ++ dock_size_scale_format_value_cb: function(scale, value) { ++ return Math.round(value*100)+ ' %'; ++ }, ++ ++ dock_size_scale_value_changed_cb: function(scale) { ++ // Avoid settings the size consinuosly ++ if (this._dock_size_timeout > 0) ++ Mainloop.source_remove(this._dock_size_timeout); ++ ++ this._dock_size_timeout = Mainloop.timeout_add(SCALE_UPDATE_TIMEOUT, Lang.bind(this, function() { ++ this._settings.set_double('height-fraction', scale.get_value()); ++ this._dock_size_timeout = 0; ++ return GLib.SOURCE_REMOVE; ++ })); ++ }, ++ ++ icon_size_scale_format_value_cb: function(scale, value) { ++ return value+ ' px'; ++ }, ++ ++ icon_size_scale_value_changed_cb: function(scale) { ++ // Avoid settings the size consinuosly ++ if (this._icon_size_timeout > 0) ++ Mainloop.source_remove(this._icon_size_timeout); ++ ++ this._icon_size_timeout = Mainloop.timeout_add(SCALE_UPDATE_TIMEOUT, Lang.bind(this, function() { ++ this._settings.set_int('dash-max-icon-size', scale.get_value()); ++ this._icon_size_timeout = 0; ++ return GLib.SOURCE_REMOVE; ++ })); ++ }, ++ ++ custom_opacity_scale_value_changed_cb: function(scale) { ++ // Avoid settings the opacity consinuosly as it's change is animated ++ if (this._opacity_timeout > 0) ++ Mainloop.source_remove(this._opacity_timeout); ++ ++ this._opacity_timeout = Mainloop.timeout_add(SCALE_UPDATE_TIMEOUT, Lang.bind(this, function() { ++ this._settings.set_double('background-opacity', scale.get_value()); ++ this._opacity_timeout = 0; ++ return GLib.SOURCE_REMOVE; ++ })); ++ }, ++ ++ min_opacity_scale_value_changed_cb: function(scale) { ++ // Avoid settings the opacity consinuosly as it's change is animated ++ if (this._opacity_timeout > 0) ++ Mainloop.source_remove(this._opacity_timeout); ++ ++ this._opacity_timeout = Mainloop.timeout_add(SCALE_UPDATE_TIMEOUT, Lang.bind(this, function() { ++ this._settings.set_double('min-alpha', scale.get_value()); ++ this._opacity_timeout = 0; ++ return GLib.SOURCE_REMOVE; ++ })); ++ }, ++ ++ max_opacity_scale_value_changed_cb: function(scale) { ++ // Avoid settings the opacity consinuosly as it's change is animated ++ if (this._opacity_timeout > 0) ++ Mainloop.source_remove(this._opacity_timeout); ++ ++ this._opacity_timeout = Mainloop.timeout_add(SCALE_UPDATE_TIMEOUT, Lang.bind(this, function() { ++ this._settings.set_double('max-alpha', scale.get_value()); ++ this._opacity_timeout = 0; ++ return GLib.SOURCE_REMOVE; ++ })); ++ }, ++ ++ custom_opacity_scale_format_value_cb: function(scale, value) { ++ return Math.round(value*100) + ' %'; ++ }, ++ ++ min_opacity_scale_format_value_cb: function(scale, value) { ++ return Math.round(value*100) + ' %'; ++ }, ++ ++ max_opacity_scale_format_value_cb: function(scale, value) { ++ return Math.round(value*100) + ' %'; ++ }, ++ ++ all_windows_radio_button_toggled_cb: function(button) { ++ if (button.get_active()) ++ this._settings.set_enum('intellihide-mode', 0); ++ }, ++ ++ focus_application_windows_radio_button_toggled_cb: function(button) { ++ if (button.get_active()) ++ this._settings.set_enum('intellihide-mode', 1); ++ }, ++ ++ maximized_windows_radio_button_toggled_cb: function(button) { ++ if (button.get_active()) ++ this._settings.set_enum('intellihide-mode', 2); ++ } ++ } ++}); ++ ++function init() { ++ Convenience.initTranslations(); ++} ++ ++function buildPrefsWidget() { ++ let settings = new Settings(); ++ let widget = settings.widget; ++ widget.show_all(); ++ return widget; ++} +diff --git a/extensions/dash-to-dock/stylesheet.css b/extensions/dash-to-dock/stylesheet.css +new file mode 100644 +index 0000000..6e9bf38 +--- /dev/null ++++ b/extensions/dash-to-dock/stylesheet.css +@@ -0,0 +1,109 @@ ++/* Shrink the dash by reducing padding and border radius */ ++#dashtodockContainer.shrink #dash, ++#dashtodockContainer.dashtodock #dash { ++ border:1px; ++ padding:0px; ++} ++ ++#dashtodockContainer.shrink.left #dash, ++#dashtodockContainer.dashtodock.left #dash { ++ border-left: 0px; ++ border-radius: 0px 9px 9px 0px; ++} ++ ++ ++#dashtodockContainer.shrink.right #dash, ++#dashtodockContainer.dashtodock.right #dash { ++ border-right: 0px; ++ border-radius: 9px 0px 0px 9px; ++} ++ ++ ++#dashtodockContainer.shrink.top #dash, ++#dashtodockContainer.dashtodock.top #dash { ++ border-top: 0px; ++ border-radius: 0px 0px 9px 9px; ++} ++ ++#dashtodockContainer.shrink.bottom #dash, ++#dashtodockContainer.dashtodock.bottom #dash { ++ border-bottom: 0px; ++ border-radius: 9px 9px 0px 0px; ++} ++ ++#dashtodockContainer.straight-corner #dash, ++#dashtodockContainer.shrink.straight-corner #dash { ++ border-radius: 0px; ++} ++ ++/* Scrollview style */ ++.bottom #dashtodockDashScrollview, ++.top #dashtodockDashScrollview { ++ -st-hfade-offset: 24px; ++} ++ ++.left #dashtodockDashScrollview, ++.right #dashtodockDashScrollview { ++ -st-vfade-offset: 24px; ++} ++ ++#dashtodockContainer.running-dots .dash-item-container > StButton, ++#dashtodockContainer.dashtodock .dash-item-container > StButton { ++ transition-duration: 250; ++ background-size: contain; ++} ++ ++#dashtodockContainer.shrink .dash-item-container > StButton, ++#dashtodockContainer.dashtodock .dash-item-container > StButton { ++ padding: 1px 2px; ++} ++ ++/* Dash height extended to the whole available vertical space */ ++#dashtodockContainer.extended.top #dash, ++#dashtodockContainer.extended.right #dash, ++#dashtodockContainer.extended.bottom #dash, ++#dashtodockContainer.extended.left #dash { ++ border-radius: 0; ++} ++ ++#dashtodockContainer.extended.top #dash, ++#dashtodockContainer.extended.bottom #dash { ++ border-left:0px; ++ border-right:0px; ++} ++ ++#dashtodockContainer.extended.right #dash, ++#dashtodockContainer.extended.left #dash { ++ border-top:0px; ++ border-bottom:0px; ++} ++ ++/* Running and focused application style */ ++ ++#dashtodockContainer.running-dots .app-well-app.running > .overview-icon, ++#dashtodockContainer.dashtodock .app-well-app.running > .overview-icon { ++ background-image:none; ++} ++ ++ ++#dashtodockContainer.running-dots .app-well-app.focused .overview-icon, ++#dashtodockContainer.dashtodock .app-well-app.focused .overview-icon { ++ background-color: rgba(238, 238, 236, 0.1); ++} ++ ++#dashtodockContainer.dashtodock #dash { ++ background: #2e3436; ++} ++ ++#dashtodockContainer .number-overlay { ++ color: rgba(255,255,255,1); ++ background-color: rgba(0,0,0,0.8); ++ text-align: center; ++} ++ ++#dashtodockPreviewSeparator.popup-separator-menu-item-horizontal { ++ width: 1px; ++ height: auto; ++ border-right-width: 1px; ++ margin: 32px 0px; ++} +diff --git a/extensions/dash-to-dock/theming.js b/extensions/dash-to-dock/theming.js +new file mode 100644 +index 0000000..4b18d1a +--- /dev/null ++++ b/extensions/dash-to-dock/theming.js +@@ -0,0 +1,672 @@ ++// -*- mode: js; js-indent-level: 4; indent-tabs-mode: nil -*- ++ ++const Clutter = imports.gi.Clutter; ++const Gio = imports.gi.Gio; ++const GLib = imports.gi.GLib; ++const Gtk = imports.gi.Gtk; ++const Signals = imports.signals; ++const Lang = imports.lang; ++const Meta = imports.gi.Meta; ++const Shell = imports.gi.Shell; ++const St = imports.gi.St; ++const Mainloop = imports.mainloop; ++ ++const AppDisplay = imports.ui.appDisplay; ++const AppFavorites = imports.ui.appFavorites; ++const Dash = imports.ui.dash; ++const DND = imports.ui.dnd; ++const IconGrid = imports.ui.iconGrid; ++const Main = imports.ui.main; ++const PopupMenu = imports.ui.popupMenu; ++const Tweener = imports.ui.tweener; ++const Util = imports.misc.util; ++const Workspace = imports.ui.workspace; ++ ++const Me = imports.misc.extensionUtils.getCurrentExtension(); ++const Dock = Me.imports.docking; ++const Utils = Me.imports.utils; ++ ++/* ++ * DEFAULT: transparency given by theme ++ * FIXED: constant transparency chosen by user ++ * ADAPTIVE: apply 'transparent' style to dock AND panel when ++ * no windows are close to the dock OR panel. ++ * When dock is hidden, the dock 'transparent' style only ++ * apply to itself. ++ * DYNAMIC: apply 'transparent' style when no windows are close to the dock ++ * */ ++const TransparencyMode = { ++ DEFAULT: 0, ++ FIXED: 1, ++ ADAPTIVE: 2, ++ DYNAMIC: 3 ++}; ++ ++/** ++ * Manage theme customization and custom theme support ++ */ ++var ThemeManager = new Lang.Class({ ++ Name: 'DashToDock.ThemeManager', ++ ++ _init: function(settings, dock) { ++ this._settings = settings; ++ this._signalsHandler = new Utils.GlobalSignalsHandler(); ++ this._bindSettingsChanges(); ++ this._actor = dock.actor; ++ this._dash = dock.dash; ++ ++ // initialize colors with generic values ++ this._customizedBackground = {red: 0, green: 0, blue: 0, alpha: 0}; ++ this._customizedBorder = {red: 0, green: 0, blue: 0, alpha: 0}; ++ this._transparency = new Transparency(this._settings, dock); ++ ++ this._signalsHandler.add([ ++ // When theme changes re-obtain default background color ++ St.ThemeContext.get_for_stage (global.stage), ++ 'changed', ++ Lang.bind(this, this.updateCustomTheme) ++ ], [ ++ // update :overview pseudoclass ++ Main.overview, ++ 'showing', ++ Lang.bind(this, this._onOverviewShowing) ++ ], [ ++ Main.overview, ++ 'hiding', ++ Lang.bind(this, this._onOverviewHiding) ++ ]); ++ ++ this._updateCustomStyleClasses(); ++ ++ // destroy themeManager when the managed actor is destroyed (e.g. extension unload) ++ // in order to disconnect signals ++ this._actor.connect('destroy', Lang.bind(this, this.destroy)); ++ ++ }, ++ ++ destroy: function() { ++ this._signalsHandler.destroy(); ++ this._transparency.destroy(); ++ }, ++ ++ _onOverviewShowing: function() { ++ this._actor.add_style_pseudo_class('overview'); ++ }, ++ ++ _onOverviewHiding: function() { ++ this._actor.remove_style_pseudo_class('overview'); ++ }, ++ ++ _updateDashOpacity: function() { ++ let newAlpha = this._settings.get_double('background-opacity'); ++ ++ let [backgroundColor, borderColor] = this._getDefaultColors(); ++ ++ if (backgroundColor==null) ++ return; ++ ++ // Get the background and border alphas. We check the background alpha ++ // for a minimum of .001 to prevent division by 0 errors ++ let backgroundAlpha = Math.max(Math.round(backgroundColor.alpha/2.55)/100, .001); ++ let borderAlpha = Math.round(borderColor.alpha/2.55)/100; ++ ++ // The border and background alphas should remain in sync ++ // We also limit the borderAlpha to a maximum of 1 (full opacity) ++ borderAlpha = Math.min((borderAlpha/backgroundAlpha)*newAlpha, 1); ++ ++ this._customizedBackground = 'rgba(' + ++ backgroundColor.red + ',' + ++ backgroundColor.green + ',' + ++ backgroundColor.blue + ',' + ++ newAlpha + ')'; ++ ++ this._customizedBorder = 'rgba(' + ++ borderColor.red + ',' + ++ borderColor.green + ',' + ++ borderColor.blue + ',' + ++ borderAlpha + ')'; ++ ++ }, ++ ++ _getDefaultColors: function() { ++ // Prevent shell crash if the actor is not on the stage. ++ // It happens enabling/disabling repeatedly the extension ++ if (!this._dash._container.get_stage()) ++ return [null, null]; ++ ++ // Remove custom style ++ let oldStyle = this._dash._container.get_style(); ++ this._dash._container.set_style(null); ++ ++ let themeNode = this._dash._container.get_theme_node(); ++ this._dash._container.set_style(oldStyle); ++ ++ let backgroundColor = themeNode.get_background_color(); ++ ++ // Just in case the theme has different border colors .. ++ // We want to find the inside border-color of the dock because it is ++ // the side most visible to the user. We do this by finding the side ++ // opposite the position ++ let position = Utils.getPosition(this._settings); ++ let side = position + 2; ++ if (side > 3) ++ side = Math.abs(side - 4); ++ ++ let borderColor = themeNode.get_border_color(side); ++ ++ return [backgroundColor, borderColor]; ++ }, ++ ++ _updateDashColor: function() { ++ // Retrieve the color. If needed we will adjust it before passing it to ++ // this._transparency. ++ let [backgroundColor, borderColor] = this._getDefaultColors(); ++ ++ if (backgroundColor==null) ++ return; ++ ++ if (this._settings.get_boolean('custom-background-color')) { ++ // When applying a custom color, we need to check the alpha value, ++ // if not the opacity will always be overridden by the color below. ++ // Note that if using 'adaptive' or 'dynamic' transparency modes, ++ // the opacity will be set by the opaque/transparent styles anyway. ++ let newAlpha = Math.round(backgroundColor.alpha/2.55)/100; ++ if (this._settings.get_enum('transparency-mode') == TransparencyMode.FIXED) ++ newAlpha = this._settings.get_double('background-opacity'); ++ ++ backgroundColor = Clutter.color_from_string(this._settings.get_string('background-color'))[1]; ++ this._customizedBackground = 'rgba(' + ++ backgroundColor.red + ',' + ++ backgroundColor.green + ',' + ++ backgroundColor.blue + ',' + ++ newAlpha + ')'; ++ ++ this._customizedBorder = this._customizedBackground; ++ } ++ this._transparency.setColor(backgroundColor); ++ }, ++ ++ _updateCustomStyleClasses: function() { ++ if (this._settings.get_boolean('apply-custom-theme')) ++ this._actor.add_style_class_name('dashtodock'); ++ else ++ this._actor.remove_style_class_name('dashtodock'); ++ ++ if (this._settings.get_boolean('custom-theme-shrink')) ++ this._actor.add_style_class_name('shrink'); ++ else ++ this._actor.remove_style_class_name('shrink'); ++ ++ if (this._settings.get_enum('running-indicator-style') !== 0) ++ this._actor.add_style_class_name('running-dots'); ++ else ++ this._actor.remove_style_class_name('running-dots'); ++ ++ // If not the built-in theme option is not selected ++ if (!this._settings.get_boolean('apply-custom-theme')) { ++ if (this._settings.get_boolean('force-straight-corner')) ++ this._actor.add_style_class_name('straight-corner'); ++ else ++ this._actor.remove_style_class_name('straight-corner'); ++ } else { ++ this._actor.remove_style_class_name('straight-corner'); ++ } ++ }, ++ ++ updateCustomTheme: function() { ++ this._updateCustomStyleClasses(); ++ this._updateDashOpacity(); ++ this._updateDashColor(); ++ this._adjustTheme(); ++ this._dash._redisplay(); ++ }, ++ ++ /** ++ * Reimported back and adapted from atomdock ++ */ ++ _adjustTheme: function() { ++ // Prevent shell crash if the actor is not on the stage. ++ // It happens enabling/disabling repeatedly the extension ++ if (!this._dash._container.get_stage()) ++ return; ++ ++ // Remove prior style edits ++ this._dash._container.set_style(null); ++ this._transparency.disable(); ++ ++ // If built-in theme is enabled do nothing else ++ if (this._settings.get_boolean('apply-custom-theme')) ++ return; ++ ++ let newStyle = ''; ++ let position = Utils.getPosition(this._settings); ++ ++ if (!this._settings.get_boolean('custom-theme-shrink')) { ++ // obtain theme border settings ++ let themeNode = this._dash._container.get_theme_node(); ++ let borderColor = themeNode.get_border_color(St.Side.TOP); ++ let borderWidth = themeNode.get_border_width(St.Side.TOP); ++ let borderRadius = themeNode.get_border_radius(St.Corner.TOPRIGHT); ++ ++ // We're copying border and corner styles to left border and top-left ++ // corner, also removing bottom border and bottom-right corner styles ++ let borderInner = ''; ++ let borderRadiusValue = ''; ++ let borderMissingStyle = ''; ++ ++ if (this._rtl && (position != St.Side.RIGHT)) ++ borderMissingStyle = 'border-right: ' + borderWidth + 'px solid ' + ++ borderColor.to_string() + ';'; ++ else if (!this._rtl && (position != St.Side.LEFT)) ++ borderMissingStyle = 'border-left: ' + borderWidth + 'px solid ' + ++ borderColor.to_string() + ';'; ++ ++ switch (position) { ++ case St.Side.LEFT: ++ borderInner = 'border-left'; ++ borderRadiusValue = '0 ' + borderRadius + 'px ' + borderRadius + 'px 0;'; ++ break; ++ case St.Side.RIGHT: ++ borderInner = 'border-right'; ++ borderRadiusValue = borderRadius + 'px 0 0 ' + borderRadius + 'px;'; ++ break; ++ case St.Side.TOP: ++ borderInner = 'border-top'; ++ borderRadiusValue = '0 0 ' + borderRadius + 'px ' + borderRadius + 'px;'; ++ break; ++ case St.Side.BOTTOM: ++ borderInner = 'border-bottom'; ++ borderRadiusValue = borderRadius + 'px ' + borderRadius + 'px 0 0;'; ++ break; ++ } ++ ++ newStyle = borderInner + ': none;' + ++ 'border-radius: ' + borderRadiusValue + ++ borderMissingStyle; ++ ++ // I do call set_style possibly twice so that only the background gets the transition. ++ // The transition-property css rules seems to be unsupported ++ this._dash._container.set_style(newStyle); ++ } ++ ++ // Customize background ++ let fixedTransparency = this._settings.get_enum('transparency-mode') == TransparencyMode.FIXED; ++ let defaultTransparency = this._settings.get_enum('transparency-mode') == TransparencyMode.DEFAULT; ++ if (!defaultTransparency && !fixedTransparency) { ++ this._transparency.enable(); ++ } ++ else if (!defaultTransparency || this._settings.get_boolean('custom-background-color')) { ++ newStyle = newStyle + 'background-color:'+ this._customizedBackground + '; ' + ++ 'border-color:'+ this._customizedBorder + '; ' + ++ 'transition-delay: 0s; transition-duration: 0.250s;'; ++ this._dash._container.set_style(newStyle); ++ } ++ }, ++ ++ _bindSettingsChanges: function() { ++ let keys = ['transparency-mode', ++ 'customize-alphas', ++ 'min-alpha', ++ 'max-alpha', ++ 'background-opacity', ++ 'custom-background-color', ++ 'background-color', ++ 'apply-custom-theme', ++ 'custom-theme-shrink', ++ 'custom-theme-running-dots', ++ 'extend-height', ++ 'force-straight-corner']; ++ ++ keys.forEach(function(key) { ++ this._signalsHandler.add([ ++ this._settings, ++ 'changed::' + key, ++ Lang.bind(this, this.updateCustomTheme) ++ ]); ++ }, this); ++ } ++}); ++ ++/** ++ * The following class is based on the following upstream commit: ++ * https://git.gnome.org/browse/gnome-shell/commit/?id=447bf55e45b00426ed908b1b1035f472c2466956 ++ * Transparency when free-floating ++ */ ++const Transparency = new Lang.Class({ ++ Name: 'DashToDock.Transparency', ++ ++ _init: function(settings, dock) { ++ this._settings = settings; ++ this._dash = dock.dash; ++ this._actor = this._dash._container; ++ this._dockActor = dock.actor; ++ this._dock = dock; ++ this._panel = Main.panel; ++ this._position = Utils.getPosition(this._settings); ++ ++ this._backgroundColor = '0,0,0'; ++ this._transparentAlpha = '0.2'; ++ this._opaqueAlpha = '1'; ++ this._transparentAlphaBorder = '0.1'; ++ this._opaqueAlphaBorder = '0.5'; ++ this._transparentTransition = '0ms'; ++ this._opaqueTransition = '0ms'; ++ ++ this._updateStyles(); ++ ++ this._signalsHandler = new Utils.GlobalSignalsHandler(); ++ this._injectionsHandler = new Utils.InjectionsHandler(); ++ this._trackedWindows = new Map(); ++ }, ++ ++ enable: function() { ++ // ensure I never double-register/inject ++ // although it should never happen ++ this.disable(); ++ ++ this._signalsHandler.addWithLabel('transparency', [ ++ global.window_group, ++ 'actor-added', ++ Lang.bind(this, this._onWindowActorAdded) ++ ], [ ++ global.window_group, ++ 'actor-removed', ++ Lang.bind(this, this._onWindowActorRemoved) ++ ], [ ++ global.window_manager, ++ 'switch-workspace', ++ Lang.bind(this, this._updateSolidStyle) ++ ], [ ++ Main.overview, ++ 'hiding', ++ Lang.bind(this, this._updateSolidStyle) ++ ], [ ++ Main.overview, ++ 'showing', ++ Lang.bind(this, this._updateSolidStyle) ++ ]); ++ ++ // Window signals ++ global.get_window_actors().forEach(function(win) { ++ // An irrelevant window actor ('Gnome-shell') produces an error when the signals are ++ // disconnected, therefore do not add signals to it. ++ if (win.get_meta_window().get_wm_class() !== 'Gnome-shell') ++ this._onWindowActorAdded(null, win); ++ }, this); ++ ++ if (this._settings.get_enum('transparency-mode') === TransparencyMode.ADAPTIVE) ++ this._enableAdaptive(); ++ ++ if (this._actor.get_stage()) ++ this._updateSolidStyle(); ++ ++ this.emit('transparency-enabled'); ++ }, ++ ++ disable: function() { ++ this._disableAdaptive(); ++ ++ // ensure I never double-register/inject ++ // although it should never happen ++ this._signalsHandler.removeWithLabel('transparency'); ++ ++ for (let key of this._trackedWindows.keys()) ++ this._trackedWindows.get(key).forEach(id => { ++ key.disconnect(id); ++ }); ++ this._trackedWindows.clear(); ++ ++ this.emit('transparency-disabled'); ++ }, ++ ++ destroy: function() { ++ this.disable(); ++ this._signalsHandler.destroy(); ++ this._injectionsHandler.destroy(); ++ }, ++ ++ _onWindowActorAdded: function(container, metaWindowActor) { ++ let signalIds = []; ++ ['allocation-changed', 'notify::visible'].forEach(s => { ++ signalIds.push(metaWindowActor.connect(s, Lang.bind(this, this._updateSolidStyle))); ++ }); ++ this._trackedWindows.set(metaWindowActor, signalIds); ++ }, ++ ++ _onWindowActorRemoved: function(container, metaWindowActor) { ++ if (!this._trackedWindows.get(metaWindowActor)) ++ return; ++ ++ this._trackedWindows.get(metaWindowActor).forEach(id => { ++ metaWindowActor.disconnect(id); ++ }); ++ this._trackedWindows.delete(metaWindowActor); ++ this._updateSolidStyle(); ++ }, ++ ++ _updateSolidStyle: function() { ++ let isNear = this._dockIsNear() || this._panelIsNear(); ++ if (isNear) { ++ this._actor.set_style(this._opaque_style); ++ if (this._panel._updateSolidStyle && this._adaptiveEnabled) { ++ if (this._settings.get_boolean('dock-fixed') || this._panelIsNear()) ++ this._panel._addStyleClassName('solid'); ++ else ++ this._panel._removeStyleClassName('solid'); ++ } ++ } ++ else { ++ this._actor.set_style(this._transparent_style); ++ if (this._panel._updateSolidStyle && this._adaptiveEnabled) ++ this._panel._removeStyleClassName('solid'); ++ } ++ ++ this.emit('solid-style-updated', isNear); ++ }, ++ ++ _dockIsNear: function() { ++ if (this._dockActor.has_style_pseudo_class('overview')) ++ return false; ++ /* Get all the windows in the active workspace that are in the primary monitor and visible */ ++ let activeWorkspace = global.screen.get_active_workspace(); ++ let dash = this._dash; ++ let windows = activeWorkspace.list_windows().filter(function(metaWindow) { ++ return metaWindow.get_monitor() === dash._monitorIndex && ++ metaWindow.showing_on_its_workspace() && ++ metaWindow.get_window_type() != Meta.WindowType.DESKTOP; ++ }); ++ ++ /* Check if at least one window is near enough to the panel. ++ * If the dock is hidden, we need to account for the space it would take ++ * up when it slides out. This is avoid an ugly transition. ++ * */ ++ let factor = 0; ++ if (!this._settings.get_boolean('dock-fixed') && ++ this._dock.getDockState() == Dock.State.HIDDEN) ++ factor = 1; ++ let [leftCoord, topCoord] = this._actor.get_transformed_position(); ++ let threshold; ++ if (this._position === St.Side.LEFT) ++ threshold = leftCoord + this._actor.get_width() * (factor + 1); ++ else if (this._position === St.Side.RIGHT) ++ threshold = leftCoord - this._actor.get_width() * factor; ++ else if (this._position === St.Side.TOP) ++ threshold = topCoord + this._actor.get_height() * (factor + 1); ++ else ++ threshold = topCoord - this._actor.get_height() * factor; ++ ++ let scale = St.ThemeContext.get_for_stage(global.stage).scale_factor; ++ let isNearEnough = windows.some(Lang.bind(this, function(metaWindow) { ++ let coord; ++ if (this._position === St.Side.LEFT) { ++ coord = metaWindow.get_frame_rect().x; ++ return coord < threshold + 5 * scale; ++ } ++ else if (this._position === St.Side.RIGHT) { ++ coord = metaWindow.get_frame_rect().x + metaWindow.get_frame_rect().width; ++ return coord > threshold - 5 * scale; ++ } ++ else if (this._position === St.Side.TOP) { ++ coord = metaWindow.get_frame_rect().y; ++ return coord < threshold + 5 * scale; ++ } ++ else { ++ coord = metaWindow.get_frame_rect().y + metaWindow.get_frame_rect().height; ++ return coord > threshold - 5 * scale; ++ } ++ })); ++ ++ return isNearEnough; ++ }, ++ ++ _panelIsNear: function() { ++ if (!this._panel._updateSolidStyle || ++ this._settings.get_enum('transparency-mode') !== TransparencyMode.ADAPTIVE) ++ return false; ++ ++ if (this._panel.actor.has_style_pseudo_class('overview') || !Main.sessionMode.hasWindows) { ++ this._panel._removeStyleClassName('solid'); ++ return false; ++ } ++ ++ /* Get all the windows in the active workspace that are in the ++ * primary monitor and visible */ ++ let activeWorkspace = global.screen.get_active_workspace(); ++ let windows = activeWorkspace.list_windows().filter(function(metaWindow) { ++ return metaWindow.is_on_primary_monitor() && ++ metaWindow.showing_on_its_workspace() && ++ metaWindow.get_window_type() != Meta.WindowType.DESKTOP; ++ }); ++ ++ /* Check if at least one window is near enough to the panel */ ++ let [, panelTop] = this._panel.actor.get_transformed_position(); ++ let panelBottom = panelTop + this._panel.actor.get_height(); ++ let scale = St.ThemeContext.get_for_stage(global.stage).scale_factor; ++ let isNearEnough = windows.some(Lang.bind(this._panel, function(metaWindow) { ++ let verticalPosition = metaWindow.get_frame_rect().y; ++ return verticalPosition < panelBottom + 5 * scale; ++ })); ++ ++ return isNearEnough; ++ }, ++ ++ _updateStyles: function() { ++ this._getAlphas(); ++ ++ this._transparent_style = ++ 'background-color: rgba(' + ++ this._backgroundColor + ', ' + this._transparentAlpha + ');' + ++ 'border-color: rgba(' + ++ this._backgroundColor + ', ' + this._transparentAlphaBorder + ');' + ++ 'transition-duration: ' + this._transparentTransition + 'ms;'; ++ ++ this._opaque_style = ++ 'background-color: rgba(' + ++ this._backgroundColor + ', ' + this._opaqueAlpha + ');' + ++ 'border-color: rgba(' + ++ this._backgroundColor + ',' + this._opaqueAlphaBorder + ');' + ++ 'transition-duration: ' + this._opaqueTransition + 'ms;'; ++ ++ this.emit('styles-updated'); ++ }, ++ ++ setColor: function(color) { ++ this._backgroundColor = color.red + ',' + color.green + ',' + color.blue; ++ this._updateStyles(); ++ }, ++ ++ _getAlphas: function() { ++ // Create dummy object and add to the uiGroup to get it to the stage ++ let dummyObject = new St.Bin({ ++ name: 'dashtodockContainer', ++ }); ++ Main.uiGroup.add_child(dummyObject); ++ ++ dummyObject.add_style_class_name('opaque'); ++ let themeNode = dummyObject.get_theme_node(); ++ this._opaqueAlpha = themeNode.get_background_color().alpha / 255; ++ this._opaqueAlphaBorder = themeNode.get_border_color(0).alpha / 255; ++ this._opaqueTransition = themeNode.get_transition_duration(); ++ ++ dummyObject.add_style_class_name('transparent'); ++ themeNode = dummyObject.get_theme_node(); ++ this._transparentAlpha = themeNode.get_background_color().alpha / 255; ++ this._transparentAlphaBorder = themeNode.get_border_color(0).alpha / 255; ++ this._transparentTransition = themeNode.get_transition_duration(); ++ ++ Main.uiGroup.remove_child(dummyObject); ++ ++ if (this._settings.get_boolean('customize-alphas')) { ++ this._opaqueAlpha = this._settings.get_double('max-alpha'); ++ this._opaqueAlphaBorder = this._opaqueAlpha / 2; ++ this._transparentAlpha = this._settings.get_double('min-alpha'); ++ this._transparentAlphaBorder = this._transparentAlpha / 2; ++ } ++ ++ if (this._settings.get_enum('transparency-mode') === TransparencyMode.ADAPTIVE && ++ this._panel._updateSolidStyle) { ++ themeNode = this._panel.actor.get_theme_node(); ++ if (this._panel.actor.has_style_class_name('solid')) { ++ this._opaqueTransition = themeNode.get_transition_duration(); ++ this._panel._removeStyleClassName('solid'); ++ themeNode = this._panel.actor.get_theme_node(); ++ this._transparentTransition = themeNode.get_transition_duration(); ++ this._panel._addStyleClassName('solid'); ++ } ++ else { ++ this._transparentTransition = themeNode.get_transition_duration(); ++ this._panel._addStyleClassName('solid'); ++ themeNode = this._panel.actor.get_theme_node(); ++ this._opaqueTransition = themeNode.get_transition_duration(); ++ this._panel._removeStyleClassName('solid'); ++ } ++ } ++ }, ++ ++ _enableAdaptive: function() { ++ if (!this._panel._updateSolidStyle || ++ this._dash._monitorIndex !== Main.layoutManager.primaryIndex) ++ return; ++ ++ this._adaptiveEnabled = true; ++ ++ function UpdateSolidStyle() { ++ return; ++ } ++ ++ this._injectionsHandler.addWithLabel('adaptive', [ ++ this._panel, ++ '_updateSolidStyle', ++ UpdateSolidStyle ++ ]); ++ ++ // Once we injected the new function, we need to disconnect and ++ // reconnect all window signals. ++ for (let key of this._panel._trackedWindows.keys()) ++ this._panel._trackedWindows.get(key).forEach(id => { ++ key.disconnect(id); ++ }); ++ ++ for (let win of this._panel._trackedWindows.keys()) ++ this._panel._onWindowActorAdded(null, win); ++ }, ++ ++ _disableAdaptive: function() { ++ if (!this._adaptiveEnabled) ++ return; ++ ++ this._injectionsHandler.removeWithLabel('adaptive'); ++ this._adaptiveEnabled = false; ++ ++ // Once we removed the injection, we need to disconnect and ++ // reconnect all window signals. ++ for (let key of this._panel._trackedWindows.keys()) ++ this._panel._trackedWindows.get(key).forEach(id => { ++ key.disconnect(id); ++ }); ++ ++ for (let win of this._panel._trackedWindows.keys()) ++ this._panel._onWindowActorAdded(null, win); ++ } ++}); ++Signals.addSignalMethods(Transparency.prototype); +diff --git a/extensions/dash-to-dock/utils.js b/extensions/dash-to-dock/utils.js +new file mode 100644 +index 0000000..6514649 +--- /dev/null ++++ b/extensions/dash-to-dock/utils.js +@@ -0,0 +1,255 @@ ++const Clutter = imports.gi.Clutter; ++const Lang = imports.lang; ++const St = imports.gi.St; ++ ++/** ++ * Simplify global signals and function injections handling ++ * abstract class ++ */ ++const BasicHandler = new Lang.Class({ ++ Name: 'DashToDock.BasicHandler', ++ ++ _init: function() { ++ this._storage = new Object(); ++ }, ++ ++ add: function(/* unlimited 3-long array arguments */) { ++ // Convert arguments object to array, concatenate with generic ++ let args = Array.concat('generic', Array.slice(arguments)); ++ // Call addWithLabel with ags as if they were passed arguments ++ this.addWithLabel.apply(this, args); ++ }, ++ ++ destroy: function() { ++ for( let label in this._storage ) ++ this.removeWithLabel(label); ++ }, ++ ++ addWithLabel: function(label /* plus unlimited 3-long array arguments*/) { ++ if (this._storage[label] == undefined) ++ this._storage[label] = new Array(); ++ ++ // Skip first element of the arguments ++ for (let i = 1; i < arguments.length; i++) { ++ let item = this._storage[label]; ++ item.push(this._create(arguments[i])); ++ } ++ }, ++ ++ removeWithLabel: function(label) { ++ if (this._storage[label]) { ++ for (let i = 0; i < this._storage[label].length; i++) ++ this._remove(this._storage[label][i]); ++ ++ delete this._storage[label]; ++ } ++ }, ++ ++ // Virtual methods to be implemented by subclass ++ ++ /** ++ * Create single element to be stored in the storage structure ++ */ ++ _create: function(item) { ++ throw new Error('no implementation of _create in ' + this); ++ }, ++ ++ /** ++ * Correctly delete single element ++ */ ++ _remove: function(item) { ++ throw new Error('no implementation of _remove in ' + this); ++ } ++}); ++ ++/** ++ * Manage global signals ++ */ ++var GlobalSignalsHandler = new Lang.Class({ ++ Name: 'DashToDock.GlobalSignalHandler', ++ Extends: BasicHandler, ++ ++ _create: function(item) { ++ let object = item[0]; ++ let event = item[1]; ++ let callback = item[2] ++ let id = object.connect(event, callback); ++ ++ return [object, id]; ++ }, ++ ++ _remove: function(item) { ++ item[0].disconnect(item[1]); ++ } ++}); ++ ++/** ++ * Color manipulation utilities ++ */ ++var ColorUtils = { ++ ++ // Darken or brigthen color by a fraction dlum ++ // Each rgb value is modified by the same fraction. ++ // Return "#rrggbb" string ++ ColorLuminance: function(r, g, b, dlum) { ++ let rgbString = '#'; ++ ++ rgbString += Math.round(Math.min(Math.max(r*(1+dlum), 0), 255)).toString(16); ++ rgbString += Math.round(Math.min(Math.max(g*(1+dlum), 0), 255)).toString(16); ++ rgbString += Math.round(Math.min(Math.max(b*(1+dlum), 0), 255)).toString(16); ++ ++ return rgbString; ++ }, ++ ++ // Convert hsv ([0-1, 0-1, 0-1]) to rgb ([0-255, 0-255, 0-255]). ++ // Following algorithm in https://en.wikipedia.org/wiki/HSL_and_HSV ++ // here with h = [0,1] instead of [0, 360] ++ // Accept either (h,s,v) independently or {h:h, s:s, v:v} object. ++ // Return {r:r, g:g, b:b} object. ++ HSVtoRGB: function(h, s, v) { ++ if (arguments.length === 1) { ++ s = h.s; ++ v = h.v; ++ h = h.h; ++ } ++ ++ let r,g,b; ++ let c = v*s; ++ let h1 = h*6; ++ let x = c*(1 - Math.abs(h1 % 2 - 1)); ++ let m = v - c; ++ ++ if (h1 <=1) ++ r = c + m, g = x + m, b = m; ++ else if (h1 <=2) ++ r = x + m, g = c + m, b = m; ++ else if (h1 <=3) ++ r = m, g = c + m, b = x + m; ++ else if (h1 <=4) ++ r = m, g = x + m, b = c + m; ++ else if (h1 <=5) ++ r = x + m, g = m, b = c + m; ++ else ++ r = c + m, g = m, b = x + m; ++ ++ return { ++ r: Math.round(r * 255), ++ g: Math.round(g * 255), ++ b: Math.round(b * 255) ++ }; ++ }, ++ ++ // Convert rgb ([0-255, 0-255, 0-255]) to hsv ([0-1, 0-1, 0-1]). ++ // Following algorithm in https://en.wikipedia.org/wiki/HSL_and_HSV ++ // here with h = [0,1] instead of [0, 360] ++ // Accept either (r,g,b) independently or {r:r, g:g, b:b} object. ++ // Return {h:h, s:s, v:v} object. ++ RGBtoHSV: function (r, g, b) { ++ if (arguments.length === 1) { ++ r = r.r; ++ g = r.g; ++ b = r.b; ++ } ++ ++ let h,s,v; ++ ++ let M = Math.max(r, g, b); ++ let m = Math.min(r, g, b); ++ let c = M - m; ++ ++ if (c == 0) ++ h = 0; ++ else if (M == r) ++ h = ((g-b)/c) % 6; ++ else if (M == g) ++ h = (b-r)/c + 2; ++ else ++ h = (r-g)/c + 4; ++ ++ h = h/6; ++ v = M/255; ++ if (M !== 0) ++ s = c/M; ++ else ++ s = 0; ++ ++ return { ++ h: h, ++ s: s, ++ v: v ++ }; ++ } ++}; ++ ++/** ++ * Manage function injection: both instances and prototype can be overridden ++ * and restored ++ */ ++var InjectionsHandler = new Lang.Class({ ++ Name: 'DashToDock.InjectionsHandler', ++ Extends: BasicHandler, ++ ++ _create: function(item) { ++ let object = item[0]; ++ let name = item[1]; ++ let injectedFunction = item[2]; ++ let original = object[name]; ++ ++ object[name] = injectedFunction; ++ return [object, name, injectedFunction, original]; ++ }, ++ ++ _remove: function(item) { ++ let object = item[0]; ++ let name = item[1]; ++ let original = item[3]; ++ object[name] = original; ++ } ++}); ++ ++/** ++ * Return the actual position reverseing left and right in rtl ++ */ ++function getPosition(settings) { ++ let position = settings.get_enum('dock-position'); ++ if (Clutter.get_default_text_direction() == Clutter.TextDirection.RTL) { ++ if (position == St.Side.LEFT) ++ position = St.Side.RIGHT; ++ else if (position == St.Side.RIGHT) ++ position = St.Side.LEFT; ++ } ++ return position; ++} ++ ++function drawRoundedLine(cr, x, y, width, height, isRoundLeft, isRoundRight, stroke, fill) { ++ if (height > width) { ++ y += Math.floor((height - width) / 2.0); ++ height = width; ++ } ++ ++ height = 2.0 * Math.floor(height / 2.0); ++ ++ var leftRadius = isRoundLeft ? height / 2.0 : 0.0; ++ var rightRadius = isRoundRight ? height / 2.0 : 0.0; ++ ++ cr.moveTo(x + width - rightRadius, y); ++ cr.lineTo(x + leftRadius, y); ++ if (isRoundLeft) ++ cr.arcNegative(x + leftRadius, y + leftRadius, leftRadius, -Math.PI/2, Math.PI/2); ++ else ++ cr.lineTo(x, y + height); ++ cr.lineTo(x + width - rightRadius, y + height); ++ if (isRoundRight) ++ cr.arcNegative(x + width - rightRadius, y + rightRadius, rightRadius, Math.PI/2, -Math.PI/2); ++ else ++ cr.lineTo(x + width, y); ++ cr.closePath(); ++ ++ if (fill != null) { ++ cr.setSource(fill); ++ cr.fillPreserve(); ++ } ++ if (stroke != null) ++ cr.setSource(stroke); ++ cr.stroke(); ++} +diff --git a/extensions/dash-to-dock/windowPreview.js b/extensions/dash-to-dock/windowPreview.js +new file mode 100644 +index 0000000..4b99aa8 +--- /dev/null ++++ b/extensions/dash-to-dock/windowPreview.js +@@ -0,0 +1,630 @@ ++/* ++ * Credits: ++ * This file is based on code from the Dash to Panel extension by Jason DeRose ++ * and code from the Taskbar extension by Zorin OS ++ * Some code was also adapted from the upstream Gnome Shell source code. ++ */ ++const Clutter = imports.gi.Clutter; ++const GLib = imports.gi.GLib; ++const Lang = imports.lang; ++const St = imports.gi.St; ++const Mainloop = imports.mainloop; ++const Main = imports.ui.main; ++const Gtk = imports.gi.Gtk; ++ ++const Params = imports.misc.params; ++const PopupMenu = imports.ui.popupMenu; ++const Tweener = imports.ui.tweener; ++const Workspace = imports.ui.workspace; ++ ++const Me = imports.misc.extensionUtils.getCurrentExtension(); ++const Utils = Me.imports.utils; ++ ++const PREVIEW_MAX_WIDTH = 250; ++const PREVIEW_MAX_HEIGHT = 150; ++ ++const WindowPreviewMenu = new Lang.Class({ ++ Name: 'WindowPreviewMenu', ++ Extends: PopupMenu.PopupMenu, ++ ++ _init: function(source, settings) { ++ this._dtdSettings = settings; ++ ++ let side = Utils.getPosition(settings); ++ ++ this.parent(source.actor, 0.5, side); ++ ++ // We want to keep the item hovered while the menu is up ++ this.blockSourceEvents = true; ++ ++ this._source = source; ++ this._app = this._source.app; ++ let monitorIndex = this._source.monitorIndex; ++ ++ this.actor.add_style_class_name('app-well-menu'); ++ this.actor.set_style('max-width: ' + (Main.layoutManager.monitors[monitorIndex].width - 22) + 'px; ' + ++ 'max-height: ' + (Main.layoutManager.monitors[monitorIndex].height - 22) + 'px;'); ++ this.actor.hide(); ++ ++ // Chain our visibility and lifecycle to that of the source ++ this._mappedId = this._source.actor.connect('notify::mapped', Lang.bind(this, function () { ++ if (!this._source.actor.mapped) ++ this.close(); ++ })); ++ this._destroyId = this._source.actor.connect('destroy', Lang.bind(this, this.destroy)); ++ ++ Main.uiGroup.add_actor(this.actor); ++ ++ // Change the initialized side where required. ++ this._arrowSide = side; ++ this._boxPointer._arrowSide = side; ++ this._boxPointer._userArrowSide = side; ++ ++ this._previewBox = new WindowPreviewList(this._source, this._dtdSettings); ++ this.addMenuItem(this._previewBox); ++ }, ++ ++ _redisplay: function() { ++ this._previewBox._shownInitially = false; ++ this._previewBox._redisplay(); ++ }, ++ ++ popup: function() { ++ let windows = this._source.getInterestingWindows(); ++ if (windows.length > 0) { ++ this._redisplay(); ++ this.open(); ++ this.actor.navigate_focus(null, Gtk.DirectionType.TAB_FORWARD, false); ++ this._source.emit('sync-tooltip'); ++ } ++ }, ++ ++ destroy: function () { ++ if (this._mappedId) ++ this._source.actor.disconnect(this._mappedId); ++ ++ if (this._destroyId) ++ this._source.actor.disconnect(this._destroyId); ++ ++ this.parent(); ++ } ++ ++}); ++ ++const WindowPreviewList = new Lang.Class({ ++ Name: 'WindowPreviewMenuSection', ++ Extends: PopupMenu.PopupMenuSection, ++ ++ _init: function(source, settings) { ++ this._dtdSettings = settings; ++ ++ this.parent(); ++ ++ this.actor = new St.ScrollView({ name: 'dashtodockWindowScrollview', ++ hscrollbar_policy: Gtk.PolicyType.NEVER, ++ vscrollbar_policy: Gtk.PolicyType.NEVER, ++ enable_mouse_scrolling: true }); ++ ++ this.actor.connect('scroll-event', Lang.bind(this, this._onScrollEvent )); ++ ++ let position = Utils.getPosition(this._dtdSettings); ++ this.isHorizontal = position == St.Side.BOTTOM || position == St.Side.TOP; ++ this.box.set_vertical(!this.isHorizontal); ++ this.box.set_name('dashtodockWindowList'); ++ this.actor.add_actor(this.box); ++ this.actor._delegate = this; ++ ++ this._shownInitially = false; ++ ++ this._source = source; ++ this.app = source.app; ++ ++ this._redisplayId = Main.initializeDeferredWork(this.actor, Lang.bind(this, this._redisplay)); ++ ++ this.actor.connect('destroy', Lang.bind(this, this._onDestroy)); ++ this._stateChangedId = this.app.connect('windows-changed', ++ Lang.bind(this, ++ this._queueRedisplay)); ++ }, ++ ++ _queueRedisplay: function () { ++ Main.queueDeferredWork(this._redisplayId); ++ }, ++ ++ _onScrollEvent: function(actor, event) { ++ // Event coordinates are relative to the stage but can be transformed ++ // as the actor will only receive events within his bounds. ++ let stage_x, stage_y, ok, event_x, event_y, actor_w, actor_h; ++ [stage_x, stage_y] = event.get_coords(); ++ [ok, event_x, event_y] = actor.transform_stage_point(stage_x, stage_y); ++ [actor_w, actor_h] = actor.get_size(); ++ ++ // If the scroll event is within a 1px margin from ++ // the relevant edge of the actor, let the event propagate. ++ if (event_y >= actor_h - 2) ++ return Clutter.EVENT_PROPAGATE; ++ ++ // Skip to avoid double events mouse ++ if (event.is_pointer_emulated()) ++ return Clutter.EVENT_STOP; ++ ++ let adjustment, delta; ++ ++ if (this.isHorizontal) ++ adjustment = this.actor.get_hscroll_bar().get_adjustment(); ++ else ++ adjustment = this.actor.get_vscroll_bar().get_adjustment(); ++ ++ let increment = adjustment.step_increment; ++ ++ switch ( event.get_scroll_direction() ) { ++ case Clutter.ScrollDirection.UP: ++ delta = -increment; ++ break; ++ case Clutter.ScrollDirection.DOWN: ++ delta = +increment; ++ break; ++ case Clutter.ScrollDirection.SMOOTH: ++ let [dx, dy] = event.get_scroll_delta(); ++ delta = dy*increment; ++ delta += dx*increment; ++ break; ++ ++ } ++ ++ adjustment.set_value(adjustment.get_value() + delta); ++ ++ return Clutter.EVENT_STOP; ++ }, ++ ++ _onDestroy: function() { ++ this.app.disconnect(this._stateChangedId); ++ this._stateChangedId = 0; ++ }, ++ ++ _createPreviewItem: function(window) { ++ let preview = new WindowPreviewMenuItem(window); ++ return preview; ++ }, ++ ++ _redisplay: function () { ++ // Remove separator ++ let nonWinItem = this._getMenuItems().filter(function(actor) { ++ return !actor._window; ++ }); ++ for (let i = 0; i < nonWinItem.length; i++) { ++ let item = nonWinItem[i]; ++ item.destroy(); ++ } ++ ++ let children = this._getMenuItems().filter(function(actor) { ++ return actor._window; ++ }); ++ ++ // Windows currently on the menu ++ let oldWin = children.map(function(actor) { ++ return actor._window; ++ }); ++ ++ // All app windows ++ let newWin = this._source.getInterestingWindows().sort(this.sortWindowsCompareFunction); ++ ++ let addedItems = []; ++ let removedActors = []; ++ ++ let newIndex = 0; ++ let oldIndex = 0; ++ ++ while (newIndex < newWin.length || oldIndex < oldWin.length) { ++ // No change at oldIndex/newIndex ++ if (oldWin[oldIndex] && ++ oldWin[oldIndex] == newWin[newIndex]) { ++ oldIndex++; ++ newIndex++; ++ continue; ++ } ++ ++ // Window removed at oldIndex ++ if (oldWin[oldIndex] && ++ newWin.indexOf(oldWin[oldIndex]) == -1) { ++ removedActors.push(children[oldIndex]); ++ oldIndex++; ++ continue; ++ } ++ ++ // Window added at newIndex ++ if (newWin[newIndex] && ++ oldWin.indexOf(newWin[newIndex]) == -1) { ++ addedItems.push({ item: this._createPreviewItem(newWin[newIndex]), ++ pos: newIndex }); ++ newIndex++; ++ continue; ++ } ++ ++ // Window moved ++ let insertHere = newWin[newIndex + 1] && ++ newWin[newIndex + 1] == oldWin[oldIndex]; ++ let alreadyRemoved = removedActors.reduce(function(result, actor) { ++ let removedWin = actor._window; ++ return result || removedWin == newWin[newIndex]; ++ }, false); ++ ++ if (insertHere || alreadyRemoved) { ++ addedItems.push({ item: this._createPreviewItem(newWin[newIndex]), ++ pos: newIndex + removedActors.length }); ++ newIndex++; ++ } else { ++ removedActors.push(children[oldIndex]); ++ oldIndex++; ++ } ++ } ++ ++ for (let i = 0; i < addedItems.length; i++) ++ this.addMenuItem(addedItems[i].item, ++ addedItems[i].pos); ++ ++ for (let i = 0; i < removedActors.length; i++) { ++ let item = removedActors[i]; ++ if (this._shownInitially) ++ item._animateOutAndDestroy(); ++ else ++ item.actor.destroy(); ++ } ++ ++ // Separate windows from other workspaces ++ let ws_index = global.screen.get_active_workspace_index(); ++ let separator_index = 0; ++ for (let i = 0; i < newWin.length; i++) ++ if (newWin[i].get_workspace().index() == ws_index) ++ separator_index++; ++ ++ if (separator_index > 0 && separator_index !== newWin.length) { ++ let separatorItem = new PopupMenu.PopupSeparatorMenuItem(); ++ if (this.isHorizontal) { ++ separatorItem._separator.set_x_expand(false); ++ separatorItem._separator.set_y_expand(true); ++ separatorItem._separator.set_name('dashtodockPreviewSeparator'); ++ separatorItem._separator.add_style_class_name('popup-separator-menu-item-horizontal'); ++ separatorItem._separator.set_x_align(Clutter.ActorAlign.CENTER); ++ separatorItem._separator.set_y_align(Clutter.ActorAlign.FILL); ++ } ++ this.addMenuItem(separatorItem, separator_index); ++ } ++ ++ // Skip animations on first run when adding the initial set ++ // of items, to avoid all items zooming in at once ++ let animate = this._shownInitially; ++ ++ if (!this._shownInitially) ++ this._shownInitially = true; ++ ++ for (let i = 0; i < addedItems.length; i++) ++ addedItems[i].item.show(animate); ++ ++ // Workaround for https://bugzilla.gnome.org/show_bug.cgi?id=692744 ++ // Without it, StBoxLayout may use a stale size cache ++ this.box.queue_relayout(); ++ ++ if (newWin.length < 1) ++ this._getTopMenu().close(~0); ++ ++ // As for upstream: ++ // St.ScrollView always requests space horizontally for a possible vertical ++ // scrollbar if in AUTOMATIC mode. Doing better would require implementation ++ // of width-for-height in St.BoxLayout and St.ScrollView. This looks bad ++ // when we *don't* need it, so turn off the scrollbar when that's true. ++ // Dynamic changes in whether we need it aren't handled properly. ++ let needsScrollbar = this._needsScrollbar(); ++ let scrollbar_policy = needsScrollbar ? Gtk.PolicyType.AUTOMATIC : Gtk.PolicyType.NEVER; ++ if (this.isHorizontal) ++ this.actor.hscrollbar_policy = scrollbar_policy; ++ else ++ this.actor.vscrollbar_policy = scrollbar_policy; ++ ++ if (needsScrollbar) ++ this.actor.add_style_pseudo_class('scrolled'); ++ else ++ this.actor.remove_style_pseudo_class('scrolled'); ++ }, ++ ++ _needsScrollbar: function() { ++ let topMenu = this._getTopMenu(); ++ let topThemeNode = topMenu.actor.get_theme_node(); ++ if (this.isHorizontal) { ++ let [topMinWidth, topNaturalWidth] = topMenu.actor.get_preferred_width(-1); ++ let topMaxWidth = topThemeNode.get_max_width(); ++ return topMaxWidth >= 0 && topNaturalWidth >= topMaxWidth; ++ } else { ++ let [topMinHeight, topNaturalHeight] = topMenu.actor.get_preferred_height(-1); ++ let topMaxHeight = topThemeNode.get_max_height(); ++ return topMaxHeight >= 0 && topNaturalHeight >= topMaxHeight; ++ } ++ ++ }, ++ ++ isAnimatingOut: function() { ++ return this.actor.get_children().reduce(function(result, actor) { ++ return result || actor.animatingOut; ++ }, false); ++ }, ++ ++ sortWindowsCompareFunction: function(windowA, windowB) { ++ let ws_index = global.screen.get_active_workspace_index(); ++ let winA_inActiveWS = windowA.get_workspace().index() == ws_index; ++ let winB_inActiveWS = windowB.get_workspace().index() == ws_index; ++ ++ // Only change the order if winA is not in the current WS, while winB is ++ if (!winA_inActiveWS && winB_inActiveWS) ++ return 1; ++ ++ return 0; ++ } ++}); ++ ++const WindowPreviewMenuItem = new Lang.Class({ ++ Name: 'WindowPreviewMenuItem', ++ Extends: PopupMenu.PopupBaseMenuItem, ++ ++ _init: function(window, params) { ++ this._window = window; ++ this._destroyId = 0; ++ this._windowAddedId = 0; ++ this.parent(params); ++ ++ // We don't want this: it adds spacing on the left of the item. ++ this.actor.remove_child(this._ornamentLabel); ++ this.actor.add_style_class_name('dashtodock-app-well-preview-menu-item'); ++ ++ this._cloneBin = new St.Bin(); ++ this._cloneBin.set_size(PREVIEW_MAX_WIDTH, PREVIEW_MAX_HEIGHT); ++ ++ // TODO: improve the way the closebutton is layout. Just use some padding ++ // for the moment. ++ this._cloneBin.set_style('padding-bottom: 0.5em'); ++ ++ this.closeButton = new St.Button({ style_class: 'window-close', ++ x_expand: true, ++ y_expand: true}); ++ this.closeButton.set_x_align(Clutter.ActorAlign.END); ++ this.closeButton.set_y_align(Clutter.ActorAlign.START); ++ ++ ++ this.closeButton.opacity = 0; ++ this.closeButton.connect('clicked', Lang.bind(this, this._closeWindow)); ++ ++ let overlayGroup = new Clutter.Actor({layout_manager: new Clutter.BinLayout() }); ++ ++ overlayGroup.add_actor(this._cloneBin); ++ overlayGroup.add_actor(this.closeButton); ++ ++ let label = new St.Label({ text: window.get_title()}); ++ label.set_style('max-width: '+PREVIEW_MAX_WIDTH +'px'); ++ let labelBin = new St.Bin({ child: label, ++ x_align: St.Align.MIDDLE}); ++ ++ this._windowTitleId = this._window.connect('notify::title', Lang.bind(this, function() { ++ label.set_text(this._window.get_title()); ++ })); ++ ++ let box = new St.BoxLayout({ vertical: true, ++ reactive:true, ++ x_expand:true }); ++ box.add(overlayGroup); ++ box.add(labelBin); ++ this.actor.add_actor(box); ++ ++ this.actor.connect('enter-event', ++ Lang.bind(this, this._onEnter)); ++ this.actor.connect('leave-event', ++ Lang.bind(this, this._onLeave)); ++ this.actor.connect('key-focus-in', ++ Lang.bind(this, this._onEnter)); ++ this.actor.connect('key-focus-out', ++ Lang.bind(this, this._onLeave)); ++ ++ this._cloneTexture(window); ++ ++ }, ++ ++ _cloneTexture: function(metaWin){ ++ ++ let mutterWindow = metaWin.get_compositor_private(); ++ ++ // Newly-created windows are added to a workspace before ++ // the compositor finds out about them... ++ // Moreover sometimes they return an empty texture, thus as a workarounf also check for it size ++ if (!mutterWindow || !mutterWindow.get_texture() || !mutterWindow.get_texture().get_size()[0]) { ++ let id = Mainloop.idle_add(Lang.bind(this, ++ function () { ++ // Check if there's still a point in getting the texture, ++ // otherwise this could go on indefinitely ++ if (this.actor && metaWin.get_workspace()) ++ this._cloneTexture(metaWin); ++ return GLib.SOURCE_REMOVE; ++ })); ++ GLib.Source.set_name_by_id(id, '[dash-to-dock] this._cloneTexture'); ++ return; ++ } ++ ++ let windowTexture = mutterWindow.get_texture(); ++ let [width, height] = windowTexture.get_size(); ++ ++ let scale = Math.min(1.0, PREVIEW_MAX_WIDTH/width, PREVIEW_MAX_HEIGHT/height); ++ ++ let clone = new Clutter.Clone ({ source: windowTexture, ++ reactive: true, ++ width: width * scale, ++ height: height * scale }); ++ ++ // when the source actor is destroyed, i.e. the window closed, first destroy the clone ++ // and then destroy the menu item (do this animating out) ++ this._destroyId = mutterWindow.connect('destroy', Lang.bind(this, function() { ++ clone.destroy(); ++ this._destroyId = 0; // avoid to try to disconnect this signal from mutterWindow in _onDestroy(), ++ // as the object was just destroyed ++ this._animateOutAndDestroy(); ++ })); ++ ++ this._clone = clone; ++ this._mutterWindow = mutterWindow; ++ this._cloneBin.set_child(this._clone); ++ }, ++ ++ _windowCanClose: function() { ++ return this._window.can_close() && ++ !this._hasAttachedDialogs(); ++ }, ++ ++ _closeWindow: function(actor) { ++ this._workspace = this._window.get_workspace(); ++ ++ // This mechanism is copied from the workspace.js upstream code ++ // It forces window activation if the windows don't get closed, ++ // for instance because asking user confirmation, by monitoring the opening of ++ // such additional confirmation window ++ this._windowAddedId = this._workspace.connect('window-added', ++ Lang.bind(this, ++ this._onWindowAdded)); ++ ++ this.deleteAllWindows(); ++ }, ++ ++ deleteAllWindows: function() { ++ // Delete all windows, starting from the bottom-most (most-modal) one ++ //let windows = this._window.get_compositor_private().get_children(); ++ let windows = this._clone.get_children(); ++ for (let i = windows.length - 1; i >= 1; i--) { ++ let realWindow = windows[i].source; ++ let metaWindow = realWindow.meta_window; ++ ++ metaWindow.delete(global.get_current_time()); ++ } ++ ++ this._window.delete(global.get_current_time()); ++ }, ++ ++ _onWindowAdded: function(workspace, win) { ++ let metaWindow = this._window; ++ ++ if (win.get_transient_for() == metaWindow) { ++ workspace.disconnect(this._windowAddedId); ++ this._windowAddedId = 0; ++ ++ // use an idle handler to avoid mapping problems - ++ // see comment in Workspace._windowAdded ++ let id = Mainloop.idle_add(Lang.bind(this, ++ function() { ++ this.emit('activate'); ++ return GLib.SOURCE_REMOVE; ++ })); ++ GLib.Source.set_name_by_id(id, '[dash-to-dock] this.emit'); ++ } ++ }, ++ ++ _hasAttachedDialogs: function() { ++ // count trasient windows ++ let n=0; ++ this._window.foreach_transient(function(){n++;}); ++ return n>0; ++ }, ++ ++ _onEnter: function() { ++ this._showCloseButton(); ++ return Clutter.EVENT_PROPAGATE; ++ }, ++ ++ _onLeave: function() { ++ if (!this._cloneBin.has_pointer && ++ !this.closeButton.has_pointer) ++ this._hideCloseButton(); ++ ++ return Clutter.EVENT_PROPAGATE; ++ }, ++ ++ _idleToggleCloseButton: function() { ++ this._idleToggleCloseId = 0; ++ ++ if (!this._cloneBin.has_pointer && ++ !this.closeButton.has_pointer) ++ this._hideCloseButton(); ++ ++ return GLib.SOURCE_REMOVE; ++ }, ++ ++ _showCloseButton: function() { ++ ++ if (this._windowCanClose()) { ++ this.closeButton.show(); ++ Tweener.addTween(this.closeButton, ++ { opacity: 255, ++ time: Workspace.CLOSE_BUTTON_FADE_TIME, ++ transition: 'easeOutQuad' }); ++ } ++ }, ++ ++ _hideCloseButton: function() { ++ Tweener.addTween(this.closeButton, ++ { opacity: 0, ++ time: Workspace.CLOSE_BUTTON_FADE_TIME, ++ transition: 'easeInQuad' }); ++ }, ++ ++ show: function(animate) { ++ let fullWidth = this.actor.get_width(); ++ ++ this.actor.opacity = 0; ++ this.actor.set_width(0); ++ ++ let time = animate ? 0.25 : 0; ++ Tweener.addTween(this.actor, ++ { opacity: 255, ++ width: fullWidth, ++ time: time, ++ transition: 'easeInOutQuad' ++ }); ++ }, ++ ++ _animateOutAndDestroy: function() { ++ Tweener.addTween(this.actor, ++ { opacity: 0, ++ time: 0.25, ++ }); ++ ++ Tweener.addTween(this.actor, ++ { height: 0, ++ width: 0, ++ time: 0.25, ++ delay: 0.25, ++ onCompleteScope: this, ++ onComplete: function() { ++ this.actor.destroy(); ++ } ++ }); ++ }, ++ ++ activate: function() { ++ this._getTopMenu().close(); ++ Main.activateWindow(this._window); ++ }, ++ ++ _onDestroy: function() { ++ ++ this.parent(); ++ ++ if (this._windowAddedId > 0) { ++ this._workspace.disconnect(this._windowAddedId); ++ this._windowAddedId = 0; ++ } ++ ++ if (this._destroyId > 0) { ++ this._mutterWindow.disconnect(this._destroyId); ++ this._destroyId = 0; ++ } ++ ++ if (this._windowTitleId > 0) { ++ this._window.disconnect(this._windowTitleId); ++ this._windowTitleId = 0; ++ } ++ } ++ ++}); +diff --git a/meson.build b/meson.build +index c16bde1..f9b56cf 100644 +--- a/meson.build ++++ b/meson.build +@@ -52,6 +52,7 @@ default_extensions += [ + all_extensions = default_extensions + all_extensions += [ + 'auto-move-windows', ++ 'dash-to-dock', + 'example', + 'native-window-placement', + 'top-icons', +-- +2.21.0 + + +From 8175177aa1a3fd8a40d41df1f23beee1f99d06b9 Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Florian=20M=C3=BCllner?= +Date: Wed, 20 May 2015 18:55:47 +0200 +Subject: [PATCH 3/7] Add panel-favorites extension + +--- + extensions/panel-favorites/extension.js | 267 ++++++++++++++++++++ + extensions/panel-favorites/meson.build | 5 + + extensions/panel-favorites/metadata.json.in | 10 + + extensions/panel-favorites/stylesheet.css | 14 + + meson.build | 1 + + 5 files changed, 297 insertions(+) + create mode 100644 extensions/panel-favorites/extension.js + create mode 100644 extensions/panel-favorites/meson.build + create mode 100644 extensions/panel-favorites/metadata.json.in + create mode 100644 extensions/panel-favorites/stylesheet.css + +diff --git a/extensions/panel-favorites/extension.js b/extensions/panel-favorites/extension.js +new file mode 100644 +index 0000000..b817dbb +--- /dev/null ++++ b/extensions/panel-favorites/extension.js +@@ -0,0 +1,267 @@ ++// Copyright (C) 2011-2013 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 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 Tweener = imports.ui.tweener; ++ ++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', ++ ++ _init: function(app) { ++ this.actor = new St.Button({ style_class: 'panel-button', ++ reactive: true }); ++ this.iconSize = 24; ++ let icon = app.create_icon_texture(this.iconSize); ++ this.actor.set_child(icon); ++ this.actor._delegate = this; ++ let text = app.get_name(); ++ if ( app.get_description() ) { ++ text += '\n' + app.get_description(); ++ } ++ ++ this.label = new St.Label({ style_class: 'panel-launcher-label'}); ++ this.label.set_text(text); ++ Main.layoutManager.addChrome(this.label); ++ this.label.hide(); ++ this.actor.label_actor = this.label; ++ ++ this._app = app; ++ this.actor.connect('clicked', Lang.bind(this, function() { ++ this._app.open_new_window(-1); ++ })); ++ this.actor.connect('notify::hover', ++ Lang.bind(this, this._onHoverChanged)); ++ this.actor.opacity = 207; ++ ++ this.actor.connect('notify::allocation', Lang.bind(this, this._alloc)); ++ }, ++ ++ _onHoverChanged: function(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); ++ } ++ }, ++ ++ showLabel: function() { ++ this.label.opacity = 0; ++ this.label.show(); ++ ++ let [stageX, stageY] = this.actor.get_transformed_position(); ++ ++ let itemHeight = this.actor.allocation.y2 - this.actor.allocation.y1; ++ let itemWidth = this.actor.allocation.x2 - this.actor.allocation.x1; ++ let labelWidth = this.label.get_width(); ++ ++ let node = this.label.get_theme_node(); ++ let yOffset = node.get_length('-y-offset'); ++ ++ let y = stageY + itemHeight + yOffset; ++ let x = Math.floor(stageX + itemWidth/2 - labelWidth/2); ++ ++ let parent = this.label.get_parent(); ++ let parentWidth = parent.allocation.x2 - parent.allocation.x1; ++ ++ if ( Clutter.get_default_text_direction() == Clutter.TextDirection.LTR ) { ++ // stop long tooltips falling off the right of the screen ++ x = Math.min(x, parentWidth-labelWidth-6); ++ // but whatever happens don't let them fall of the left ++ x = Math.max(x, 6); ++ } ++ else { ++ x = Math.max(x, 6); ++ x = Math.min(x, parentWidth-labelWidth-6); ++ } ++ ++ this.label.set_position(x, y); ++ Tweener.addTween(this.label, ++ { opacity: 255, ++ time: PANEL_LAUNCHER_LABEL_SHOW_TIME, ++ transition: 'easeOutQuad', ++ }); ++ }, ++ ++ hideLabel: function() { ++ 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(); ++ }) ++ }); ++ }, ++ ++ destroy: function() { ++ this.label.destroy(); ++ this.actor.destroy(); ++ } ++}); ++ ++const PanelFavorites = new Lang.Class({ ++ Name: 'PanelFavorites', ++ ++ _init: function() { ++ this._showLabelTimeoutId = 0; ++ this._resetHoverTimeoutId = 0; ++ this._labelShowing = false; ++ ++ this.actor = new St.BoxLayout({ name: 'panelFavorites', ++ x_expand: true, y_expand: true, ++ style_class: 'panel-favorites' }); ++ this._display(); ++ ++ this.container = new St.Bin({ y_fill: true, ++ x_fill: true, ++ child: this.actor }); ++ ++ 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)); ++ }, ++ ++ _redisplay: function() { ++ for ( let i=0; i 0) { ++ Mainloop.source_remove(this._resetHoverTimeoutId); ++ this._resetHoverTimeoutId = 0; ++ } ++ } ++ } else { ++ if (this._showLabelTimeoutId > 0) { ++ Mainloop.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._labelShowing = false; ++ this._resetHoverTimeoutId = 0; ++ return GLib.SOURCE_REMOVE; ++ })); ++ } ++ } ++ }, ++ ++ _onDestroy: function() { ++ if ( this._installChangedId != 0 ) { ++ Shell.AppSystem.get_default().disconnect(this._installChangedId); ++ this._installChangedId = 0; ++ } ++ ++ if ( this._changedId != 0 ) { ++ AppFavorites.getAppFavorites().disconnect(this._changedId); ++ this._changedId = 0; ++ } ++ } ++}); ++Signals.addSignalMethods(PanelFavorites.prototype); ++ ++let myAddToStatusArea; ++let panelFavorites; ++ ++function enable() { ++ Panel.Panel.prototype.myAddToStatusArea = myAddToStatusArea; ++ ++ // 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; ++ ++ panelFavorites = new PanelFavorites(); ++ Main.panel.myAddToStatusArea('panel-favorites', panelFavorites, ++ pos, 'left'); ++} ++ ++function disable() { ++ delete Panel.Panel.prototype.myAddToStatusArea; ++ ++ panelFavorites.actor.destroy(); ++ panelFavorites.emit('destroy'); ++ panelFavorites = null; ++} ++ ++function init() { ++ myAddToStatusArea = function(role, indicator, position, box) { ++ if (this.statusArea[role]) ++ throw new Error('Extension point conflict: there is already a status indicator for role ' + role); ++ ++ position = position || 0; ++ let boxes = { ++ left: this._leftBox, ++ center: this._centerBox, ++ right: this._rightBox ++ }; ++ let boxContainer = boxes[box] || this._rightBox; ++ this.statusArea[role] = indicator; ++ this._addToPanelBox(role, indicator, position, boxContainer); ++ return indicator; ++ }; ++} +diff --git a/extensions/panel-favorites/meson.build b/extensions/panel-favorites/meson.build +new file mode 100644 +index 0000000..48504f6 +--- /dev/null ++++ b/extensions/panel-favorites/meson.build +@@ -0,0 +1,5 @@ ++extension_data += configure_file( ++ input: metadata_name + '.in', ++ output: metadata_name, ++ configuration: metadata_conf ++) +diff --git a/extensions/panel-favorites/metadata.json.in b/extensions/panel-favorites/metadata.json.in +new file mode 100644 +index 0000000..037f281 +--- /dev/null ++++ b/extensions/panel-favorites/metadata.json.in +@@ -0,0 +1,10 @@ ++{ ++"extension-id": "@extension_id@", ++"uuid": "@uuid@", ++"settings-schema": "@gschemaname@", ++"gettext-domain": "@gettext_domain@", ++"name": "Frippery Panel Favorites", ++"description": "Add launchers for Favorites to the panel", ++"shell-version": [ "@shell_current@" ], ++"url": "http://intgat.tigress.co.uk/rmy/extensions/index.html" ++} +diff --git a/extensions/panel-favorites/stylesheet.css b/extensions/panel-favorites/stylesheet.css +new file mode 100644 +index 0000000..120adac +--- /dev/null ++++ b/extensions/panel-favorites/stylesheet.css +@@ -0,0 +1,14 @@ ++.panel-favorites { ++ spacing: 6px; ++} ++ ++.panel-launcher-label { ++ border-radius: 7px; ++ padding: 4px 12px; ++ background-color: rgba(0,0,0,0.9); ++ color: white; ++ text-align: center; ++ font-size: 9pt; ++ font-weight: bold; ++ -y-offset: 6px; ++} +diff --git a/meson.build b/meson.build +index f9b56cf..3451585 100644 +--- a/meson.build ++++ b/meson.build +@@ -55,6 +55,7 @@ all_extensions += [ + 'dash-to-dock', + 'example', + 'native-window-placement', ++ 'panel-favorites', + 'top-icons', + 'user-theme' + ] +-- +2.21.0 + + +From 0bec6fd48fc857c54ef4917b1648f507c8142270 Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Florian=20M=C3=BCllner?= +Date: Fri, 4 Mar 2016 17:07:21 +0100 +Subject: [PATCH 4/7] Add updates-dialog extension + +--- + extensions/updates-dialog/extension.js | 490 ++++++++++++++++++ + extensions/updates-dialog/meson.build | 7 + + extensions/updates-dialog/metadata.json.in | 10 + + ...hell.extensions.updates-dialog.gschema.xml | 30 ++ + extensions/updates-dialog/stylesheet.css | 1 + + meson.build | 1 + + po/POTFILES.in | 2 + + 7 files changed, 541 insertions(+) + create mode 100644 extensions/updates-dialog/extension.js + create mode 100644 extensions/updates-dialog/meson.build + create mode 100644 extensions/updates-dialog/metadata.json.in + create mode 100644 extensions/updates-dialog/org.gnome.shell.extensions.updates-dialog.gschema.xml + create mode 100644 extensions/updates-dialog/stylesheet.css + +diff --git a/extensions/updates-dialog/extension.js b/extensions/updates-dialog/extension.js +new file mode 100644 +index 0000000..2fa62a5 +--- /dev/null ++++ b/extensions/updates-dialog/extension.js +@@ -0,0 +1,490 @@ ++/* ++ * Copyright (c) 2015 Red Hat, Inc. ++ * ++ * 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 . ++ */ ++ ++const Clutter = imports.gi.Clutter; ++const Gio = imports.gi.Gio; ++const GLib = imports.gi.GLib; ++const Lang = imports.lang; ++const Pango = imports.gi.Pango; ++const PkgKit = imports.gi.PackageKitGlib; ++const Polkit = imports.gi.Polkit; ++const Signals = imports.signals; ++const St = imports.gi.St; ++ ++const EndSessionDialog = imports.ui.endSessionDialog; ++const Main = imports.ui.main; ++const ModalDialog = imports.ui.modalDialog; ++ ++const ExtensionUtils = imports.misc.extensionUtils; ++const Me = ExtensionUtils.getCurrentExtension(); ++const Convenience = Me.imports.convenience; ++ ++const PkIface = ' \ ++ \ ++ \ ++ \ ++ \ ++ \ ++ \ ++'; ++ ++const PkOfflineIface = ' \ ++ \ ++ \ ++ \ ++ \ ++ \ ++ \ ++ \ ++ \ ++'; ++ ++const PkTransactionIface = ' \ ++ \ ++ \ ++ \ ++ \ ++ \ ++ \ ++ \ ++ \ ++ \ ++ \ ++ \ ++ \ ++ \ ++ \ ++ \ ++ \ ++ \ ++ \ ++ \ ++ \ ++ \ ++'; ++ ++const LoginManagerIface = ' \ ++ \ ++ \ ++ \ ++ \ ++ \ ++ \ ++ \ ++ \ ++'; ++ ++const PkProxy = Gio.DBusProxy.makeProxyWrapper(PkIface); ++const PkOfflineProxy = Gio.DBusProxy.makeProxyWrapper(PkOfflineIface); ++const PkTransactionProxy = Gio.DBusProxy.makeProxyWrapper(PkTransactionIface); ++const LoginManagerProxy = Gio.DBusProxy.makeProxyWrapper(LoginManagerIface); ++ ++let pkProxy = null; ++let pkOfflineProxy = null; ++let loginManagerProxy = null; ++let updatesDialog = null; ++let extensionSettings = null; ++let cancellable = null; ++ ++let updatesCheckInProgress = false; ++let updatesCheckRequested = false; ++let securityUpdates = []; ++ ++function getDetailText(period) { ++ let text = _("Important security updates need to be installed.\n"); ++ if (period < 60) ++ text += ngettext("You can close this dialog and get %d minute to finish your work.", ++ "You can close this dialog and get %d minutes to finish your work.", ++ period).format(period); ++ else ++ text += ngettext("You can close this dialog and get %d hour to finish your work.", ++ "You can close this dialog and get %d hours to finish your work.", ++ Math.floor(period / 60)).format(Math.floor(period / 60)); ++ return text; ++} ++ ++const UpdatesDialog = new Lang.Class({ ++ Name: 'UpdatesDialog', ++ Extends: ModalDialog.ModalDialog, ++ ++ _init: function(settings) { ++ this.parent({ styleClass: 'end-session-dialog', ++ destroyOnClose: false }); ++ ++ this._gracePeriod = settings.get_uint('grace-period'); ++ this._gracePeriod = Math.min(Math.max(10, this._gracePeriod), 24*60); ++ this._lastWarningPeriod = settings.get_uint('last-warning-period'); ++ this._lastWarningPeriod = Math.min(Math.max(1, this._lastWarningPeriod), this._gracePeriod - 1); ++ this._lastWarnings = settings.get_uint('last-warnings'); ++ this._lastWarnings = Math.min(Math.max(1, this._lastWarnings), ++ Math.floor((this._gracePeriod - 1) / this._lastWarningPeriod)); ++ ++ let messageLayout = new St.BoxLayout({ vertical: true, ++ style_class: 'end-session-dialog-layout' }); ++ this.contentLayout.add(messageLayout, ++ { x_fill: true, ++ y_fill: true, ++ y_expand: true }); ++ ++ let subjectLabel = new St.Label({ style_class: 'end-session-dialog-subject', ++ style: 'padding-bottom: 1em;', ++ text: _("Important security updates") }); ++ messageLayout.add(subjectLabel, ++ { x_fill: false, ++ y_fill: false, ++ x_align: St.Align.START, ++ y_align: St.Align.START }); ++ ++ this._detailLabel = new St.Label({ style_class: 'end-session-dialog-description', ++ style: 'padding-bottom: 0em;', ++ text: getDetailText(this._gracePeriod) }); ++ this._detailLabel.clutter_text.ellipsize = Pango.EllipsizeMode.NONE; ++ this._detailLabel.clutter_text.line_wrap = true; ++ ++ messageLayout.add(this._detailLabel, ++ { y_fill: true, ++ y_align: St.Align.START }); ++ ++ let buttons = [{ action: Lang.bind(this, this.close), ++ label: _("Close"), ++ key: Clutter.Escape }, ++ { action: Lang.bind(this, this._done), ++ label: _("Restart & Install") }]; ++ ++ this.setButtons(buttons); ++ ++ this._openTimeoutId = 0; ++ this.connect('destroy', Lang.bind(this, this._clearOpenTimeout)); ++ ++ this._startTimer(); ++ }, ++ ++ _clearOpenTimeout: function() { ++ if (this._openTimeoutId > 0) { ++ GLib.source_remove(this._openTimeoutId); ++ this._openTimeoutId = 0; ++ } ++ }, ++ ++ tryOpen: function() { ++ if (this._openTimeoutId > 0 || this.open()) ++ return; ++ ++ this._openTimeoutId = GLib.timeout_add_seconds(GLib.PRIORITY_DEFAULT, 1, ++ Lang.bind(this, function() { ++ if (!this.open()) ++ return GLib.SOURCE_CONTINUE; ++ ++ this._clearOpenTimeout(); ++ return GLib.SOURCE_REMOVE; ++ })); ++ }, ++ ++ _startTimer: function() { ++ this._secondsLeft = this._gracePeriod*60; ++ ++ this._timerId = GLib.timeout_add_seconds(GLib.PRIORITY_DEFAULT, 1, Lang.bind(this, ++ function() { ++ this._secondsLeft -= 1; ++ let minutesLeft = this._secondsLeft / 60; ++ let periodLeft = Math.floor(minutesLeft); ++ ++ if (this._secondsLeft == 60 || ++ (periodLeft > 0 && periodLeft <= this._lastWarningPeriod * this._lastWarnings && ++ minutesLeft % this._lastWarningPeriod == 0)) { ++ this.tryOpen(); ++ this._detailLabel.text = getDetailText(periodLeft); ++ } ++ ++ if (this._secondsLeft > 0) { ++ if (this._secondsLeft < 60) { ++ let seconds = EndSessionDialog._roundSecondsToInterval(this._gracePeriod*60, this._secondsLeft, 10); ++ this._detailLabel.text = ++ _("Important security updates need to be installed now.\n") + ++ ngettext("This computer will restart in %d second.", ++ "This computer will restart in %d seconds.", ++ seconds).format(seconds); ++ } ++ return GLib.SOURCE_CONTINUE; ++ } ++ ++ this._done(); ++ return GLib.SOURCE_REMOVE; ++ })); ++ this.connect('destroy', Lang.bind(this, function() { ++ if (this._timerId > 0) { ++ GLib.source_remove(this._timerId); ++ this._timerId = 0; ++ } ++ })); ++ }, ++ ++ _done: function() { ++ this.emit('done'); ++ this.destroy(); ++ }, ++ ++ getState: function() { ++ return [this._gracePeriod, this._lastWarningPeriod, this._lastWarnings, this._secondsLeft]; ++ }, ++ ++ setState: function(state) { ++ [this._gracePeriod, this._lastWarningPeriod, this._lastWarnings, this._secondsLeft] = state; ++ }, ++}); ++Signals.addSignalMethods(UpdatesDialog.prototype); ++ ++function showDialog() { ++ if (updatesDialog) ++ return; ++ ++ updatesDialog = new UpdatesDialog(extensionSettings); ++ updatesDialog.tryOpen(); ++ updatesDialog.connect('destroy', function() { updatesDialog = null; }); ++ updatesDialog.connect('done', function() { ++ if (pkOfflineProxy.TriggerAction == 'power-off' || ++ pkOfflineProxy.TriggerAction == 'reboot') { ++ loginManagerProxy.RebootRemote(false); ++ } else { ++ pkOfflineProxy.TriggerRemote('reboot', function(result, error) { ++ if (!error) ++ loginManagerProxy.RebootRemote(false); ++ else ++ log('Failed to trigger offline update: %s'.format(error.message)); ++ }); ++ } ++ }); ++} ++ ++function cancelDialog(save) { ++ if (!updatesDialog) ++ return; ++ ++ if (save) { ++ let state = GLib.Variant.new('(uuuu)', updatesDialog.getState()); ++ global.set_runtime_state(Me.uuid, state); ++ } ++ updatesDialog.destroy(); ++} ++ ++function restoreExistingState() { ++ let state = global.get_runtime_state('(uuuu)', Me.uuid); ++ if (state === null) ++ return false; ++ ++ global.set_runtime_state(Me.uuid, null); ++ showDialog(); ++ updatesDialog.setState(state.deep_unpack()); ++ return true; ++} ++ ++function syncState() { ++ if (!pkOfflineProxy || !loginManagerProxy) ++ return; ++ ++ if (restoreExistingState()) ++ return; ++ ++ if (!updatesCheckInProgress && ++ securityUpdates.length > 0 && ++ pkOfflineProxy.UpdatePrepared) ++ showDialog(); ++ else ++ cancelDialog(); ++} ++ ++function doPkTransaction(callback) { ++ if (!pkProxy) ++ return; ++ ++ pkProxy.CreateTransactionRemote(function(result, error) { ++ if (error) { ++ log('Error creating PackageKit transaction: %s'.format(error.message)); ++ checkUpdatesDone(); ++ return; ++ } ++ ++ new PkTransactionProxy(Gio.DBus.system, ++ 'org.freedesktop.PackageKit', ++ String(result), ++ function(proxy, error) { ++ if (!error) { ++ proxy.SetHintsRemote( ++ ['background=true', 'interactive=false'], ++ function(result, error) { ++ if (error) { ++ log('Error connecting to PackageKit: %s'.format(error.message)); ++ checkUpdatesDone(); ++ return; ++ } ++ callback(proxy); ++ }); ++ } else { ++ log('Error connecting to PackageKit: %s'.format(error.message)); ++ } ++ }); ++ }); ++} ++ ++function pkUpdatePackages(proxy) { ++ proxy.connectSignal('Finished', function(p, e, params) { ++ let [exit, runtime] = params; ++ ++ if (exit == PkgKit.ExitEnum.CANCELLED_PRIORITY) { ++ // try again ++ checkUpdates(); ++ } else if (exit != PkgKit.ExitEnum.SUCCESS) { ++ log('UpdatePackages failed: %s'.format(PkgKit.ExitEnum.to_string(exit))); ++ } ++ ++ checkUpdatesDone(); ++ }); ++ proxy.UpdatePackagesRemote(1 << PkgKit.TransactionFlagEnum.ONLY_DOWNLOAD, securityUpdates); ++} ++ ++function pkGetUpdates(proxy) { ++ proxy.connectSignal('Package', function(p, e, params) { ++ let [info, packageId, summary] = params; ++ ++ if (info == PkgKit.InfoEnum.SECURITY) ++ securityUpdates.push(packageId); ++ }); ++ proxy.connectSignal('Finished', function(p, e, params) { ++ let [exit, runtime] = params; ++ ++ if (exit == PkgKit.ExitEnum.SUCCESS) { ++ if (securityUpdates.length > 0) { ++ doPkTransaction(pkUpdatePackages); ++ return; ++ } ++ } else if (exit == PkgKit.ExitEnum.CANCELLED_PRIORITY) { ++ // try again ++ checkUpdates(); ++ } else { ++ log('GetUpdates failed: %s'.format(PkgKit.ExitEnum.to_string(exit))); ++ } ++ ++ checkUpdatesDone(); ++ }); ++ proxy.GetUpdatesRemote(0); ++} ++ ++function checkUpdatesDone() { ++ updatesCheckInProgress = false; ++ if (updatesCheckRequested) { ++ updatesCheckRequested = false; ++ checkUpdates(); ++ } else { ++ syncState(); ++ } ++} ++ ++function checkUpdates() { ++ if (updatesCheckInProgress) { ++ updatesCheckRequested = true; ++ return; ++ } ++ updatesCheckInProgress = true; ++ securityUpdates = []; ++ doPkTransaction(pkGetUpdates); ++} ++ ++function initSystemProxies() { ++ new PkProxy(Gio.DBus.system, ++ 'org.freedesktop.PackageKit', ++ '/org/freedesktop/PackageKit', ++ function(proxy, error) { ++ if (!error) { ++ pkProxy = proxy; ++ let id = pkProxy.connectSignal('UpdatesChanged', checkUpdates); ++ pkProxy._signalId = id; ++ checkUpdates(); ++ } else { ++ log('Error connecting to PackageKit: %s'.format(error.message)); ++ } ++ }, ++ cancellable); ++ new PkOfflineProxy(Gio.DBus.system, ++ 'org.freedesktop.PackageKit', ++ '/org/freedesktop/PackageKit', ++ function(proxy, error) { ++ if (!error) { ++ pkOfflineProxy = proxy; ++ let id = pkOfflineProxy.connect('g-properties-changed', syncState); ++ pkOfflineProxy._signalId = id; ++ syncState(); ++ } else { ++ log('Error connecting to PackageKit: %s'.format(error.message)); ++ } ++ }, ++ cancellable); ++ new LoginManagerProxy(Gio.DBus.system, ++ 'org.freedesktop.login1', ++ '/org/freedesktop/login1', ++ function(proxy, error) { ++ if (!error) { ++ proxy.CanRebootRemote(cancellable, function(result, error) { ++ if (!error && result == 'yes') { ++ loginManagerProxy = proxy; ++ syncState(); ++ } else { ++ log('Reboot is not available'); ++ } ++ }); ++ } else { ++ log('Error connecting to Login manager: %s'.format(error.message)); ++ } ++ }, ++ cancellable); ++} ++ ++function init(metadata) { ++} ++ ++function enable() { ++ cancellable = new Gio.Cancellable(); ++ extensionSettings = Convenience.getSettings(); ++ Polkit.Permission.new("org.freedesktop.packagekit.trigger-offline-update", ++ null, cancellable, function(p, result) { ++ try { ++ let permission = Polkit.Permission.new_finish(result); ++ if (permission && permission.allowed) ++ initSystemProxies(); ++ else ++ throw(new Error('not allowed')); ++ } catch(e) { ++ log('No permission to trigger offline updates: %s'.format(e.toString())); ++ } ++ }); ++} ++ ++function disable() { ++ cancelDialog(true); ++ cancellable.cancel(); ++ cancellable = null; ++ extensionSettings = null; ++ updatesDialog = null; ++ loginManagerProxy = null; ++ if (pkOfflineProxy) { ++ pkOfflineProxy.disconnect(pkOfflineProxy._signalId); ++ pkOfflineProxy = null; ++ } ++ if (pkProxy) { ++ pkProxy.disconnectSignal(pkProxy._signalId); ++ pkProxy = null; ++ } ++} +diff --git a/extensions/updates-dialog/meson.build b/extensions/updates-dialog/meson.build +new file mode 100644 +index 0000000..585c02d +--- /dev/null ++++ b/extensions/updates-dialog/meson.build +@@ -0,0 +1,7 @@ ++extension_data += configure_file( ++ input: metadata_name + '.in', ++ output: metadata_name, ++ configuration: metadata_conf ++) ++ ++extension_schemas += files(metadata_conf.get('gschemaname') + '.gschema.xml') +diff --git a/extensions/updates-dialog/metadata.json.in b/extensions/updates-dialog/metadata.json.in +new file mode 100644 +index 0000000..9946abb +--- /dev/null ++++ b/extensions/updates-dialog/metadata.json.in +@@ -0,0 +1,10 @@ ++{ ++"extension-id": "@extension_id@", ++"uuid": "@uuid@", ++"settings-schema": "@gschemaname@", ++"gettext-domain": "@gettext_domain@", ++"name": "Updates Dialog", ++"description": "Shows a modal dialog when there are software updates.", ++"shell-version": [ "@shell_current@" ], ++"url": "http://rtcm.fedorapeople.org/updates-dialog" ++} +diff --git a/extensions/updates-dialog/org.gnome.shell.extensions.updates-dialog.gschema.xml b/extensions/updates-dialog/org.gnome.shell.extensions.updates-dialog.gschema.xml +new file mode 100644 +index 0000000..c08d33c +--- /dev/null ++++ b/extensions/updates-dialog/org.gnome.shell.extensions.updates-dialog.gschema.xml +@@ -0,0 +1,30 @@ ++ ++ ++ ++ ++ 300 ++ Grace period in minutes ++ ++ When the grace period is over, the computer will automatically ++ reboot and install security updates. ++ ++ ++ ++ 10 ++ Last warning dialog period ++ ++ A last warning dialog is displayed this many minutes before ++ the automatic reboot. ++ ++ ++ ++ 1 ++ Number of last warning dialogs ++ ++ How many warning dialogs are displayed. Each is displayed at ++ 'last-warning-period' minute intervals. ++ ++ ++ ++ +diff --git a/extensions/updates-dialog/stylesheet.css b/extensions/updates-dialog/stylesheet.css +new file mode 100644 +index 0000000..25134b6 +--- /dev/null ++++ b/extensions/updates-dialog/stylesheet.css +@@ -0,0 +1 @@ ++/* This extensions requires no special styling */ +diff --git a/meson.build b/meson.build +index 3451585..08a243e 100644 +--- a/meson.build ++++ b/meson.build +@@ -57,6 +57,7 @@ all_extensions += [ + 'native-window-placement', + 'panel-favorites', + 'top-icons', ++ 'updates-dialog', + 'user-theme' + ] + +diff --git a/po/POTFILES.in b/po/POTFILES.in +index d98ca1b..43a817f 100644 +--- a/po/POTFILES.in ++++ b/po/POTFILES.in +@@ -15,6 +15,8 @@ extensions/native-window-placement/org.gnome.shell.extensions.native-window-plac + extensions/places-menu/extension.js + extensions/places-menu/placeDisplay.js + extensions/screenshot-window-sizer/org.gnome.shell.extensions.screenshot-window-sizer.gschema.xml ++extensions/updates-dialog/extension.js ++extensions/updates-dialog/org.gnome.shell.extensions.updates-dialog.gschema.xml + extensions/user-theme/extension.js + extensions/user-theme/org.gnome.shell.extensions.user-theme.gschema.xml + extensions/window-list/extension.js +-- +2.21.0 + + +From c86f37e39adb0d2e1fce01cb68be47c675a9ace9 Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Florian=20M=C3=BCllner?= +Date: Thu, 1 Jun 2017 23:57:14 +0200 +Subject: [PATCH 5/7] Add no-hot-corner extension + +--- + extensions/no-hot-corner/extension.js | 31 +++++++++++++++++++++++ + extensions/no-hot-corner/meson.build | 5 ++++ + extensions/no-hot-corner/metadata.json.in | 9 +++++++ + extensions/no-hot-corner/stylesheet.css | 1 + + meson.build | 1 + + 5 files changed, 47 insertions(+) + create mode 100644 extensions/no-hot-corner/extension.js + create mode 100644 extensions/no-hot-corner/meson.build + create mode 100644 extensions/no-hot-corner/metadata.json.in + create mode 100644 extensions/no-hot-corner/stylesheet.css + +diff --git a/extensions/no-hot-corner/extension.js b/extensions/no-hot-corner/extension.js +new file mode 100644 +index 0000000..e7a0d63 +--- /dev/null ++++ b/extensions/no-hot-corner/extension.js +@@ -0,0 +1,31 @@ ++const Main = imports.ui.main; ++ ++let _id; ++ ++function _disableHotCorners() { ++ // Disables all hot corners ++ Main.layoutManager.hotCorners.forEach(function(hotCorner) { ++ if (!hotCorner) { ++ return; ++ } ++ ++ hotCorner._toggleOverview = function() {}; ++ hotCorner._pressureBarrier._trigger = function() {}; ++ }); ++} ++ ++function init() { ++} ++ ++function enable() { ++ _disableHotCorners(); ++ // Hot corners may be re-created afterwards (for example, If there's a monitor change). ++ // So we catch all changes. ++ _id = Main.layoutManager.connect('hot-corners-changed', _disableHotCorners); ++} ++ ++function disable() { ++ // Disconnects the callback and re-creates the hot corners ++ Main.layoutManager.disconnect(_id); ++ Main.layoutManager._updateHotCorners(); ++} +diff --git a/extensions/no-hot-corner/meson.build b/extensions/no-hot-corner/meson.build +new file mode 100644 +index 0000000..48504f6 +--- /dev/null ++++ b/extensions/no-hot-corner/meson.build +@@ -0,0 +1,5 @@ ++extension_data += configure_file( ++ input: metadata_name + '.in', ++ output: metadata_name, ++ configuration: metadata_conf ++) +diff --git a/extensions/no-hot-corner/metadata.json.in b/extensions/no-hot-corner/metadata.json.in +new file mode 100644 +index 0000000..406d83b +--- /dev/null ++++ b/extensions/no-hot-corner/metadata.json.in +@@ -0,0 +1,9 @@ ++{ ++"extension-id": "@extension_id@", ++"uuid": "@uuid@", ++"name": "No Topleft Hot Corner", ++"description": "Disable the hot corner in the top left; you can still reach the overview by clicking the Activities button or pressing the dedicated key.", ++"shell-version": [ "@shell_current@" ], ++"url": "https://github.com/HROMANO/nohotcorner/", ++"version": 15 ++} +diff --git a/extensions/no-hot-corner/stylesheet.css b/extensions/no-hot-corner/stylesheet.css +new file mode 100644 +index 0000000..25134b6 +--- /dev/null ++++ b/extensions/no-hot-corner/stylesheet.css +@@ -0,0 +1 @@ ++/* This extensions requires no special styling */ +diff --git a/meson.build b/meson.build +index 08a243e..201c484 100644 +--- a/meson.build ++++ b/meson.build +@@ -55,6 +55,7 @@ all_extensions += [ + 'dash-to-dock', + 'example', + 'native-window-placement', ++ 'no-hot-corner', + 'panel-favorites', + 'top-icons', + 'updates-dialog', +-- +2.21.0 + + +From 9749175fac05702dbed39942b4e5ffe889a84f8c Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Florian=20M=C3=BCllner?= +Date: Tue, 26 Mar 2019 19:44:43 +0100 +Subject: [PATCH 6/7] Add window-grouper extension + +--- + extensions/window-grouper/extension.js | 109 ++++++++++ + extensions/window-grouper/meson.build | 8 + + extensions/window-grouper/metadata.json.in | 11 + + ...hell.extensions.window-grouper.gschema.xml | 9 + + extensions/window-grouper/prefs.js | 194 ++++++++++++++++++ + extensions/window-grouper/stylesheet.css | 1 + + meson.build | 3 +- + 7 files changed, 334 insertions(+), 1 deletion(-) + create mode 100644 extensions/window-grouper/extension.js + create mode 100644 extensions/window-grouper/meson.build + create mode 100644 extensions/window-grouper/metadata.json.in + create mode 100644 extensions/window-grouper/org.gnome.shell.extensions.window-grouper.gschema.xml + create mode 100644 extensions/window-grouper/prefs.js + create mode 100644 extensions/window-grouper/stylesheet.css + +diff --git a/extensions/window-grouper/extension.js b/extensions/window-grouper/extension.js +new file mode 100644 +index 0000000..e67e634 +--- /dev/null ++++ b/extensions/window-grouper/extension.js +@@ -0,0 +1,109 @@ ++// -*- mode: js2; indent-tabs-mode: nil; js2-basic-offset: 4 -*- ++// Start apps on custom workspaces ++ ++const Shell = imports.gi.Shell; ++ ++const Main = imports.ui.main; ++ ++const ExtensionUtils = imports.misc.extensionUtils; ++const Me = ExtensionUtils.getCurrentExtension(); ++const Convenience = Me.imports.convenience; ++ ++class WindowMover { ++ constructor() { ++ this._settings = Convenience.getSettings(); ++ this._appSystem = Shell.AppSystem.get_default(); ++ this._appConfigs = new Set(); ++ this._appData = new Map(); ++ ++ this._appsChangedId = ++ this._appSystem.connect('installed-changed', ++ this._updateAppData.bind(this)); ++ ++ this._settings.connect('changed', this._updateAppConfigs.bind(this)); ++ this._updateAppConfigs(); ++ } ++ ++ _updateAppConfigs() { ++ this._appConfigs.clear(); ++ ++ this._settings.get_strv('application-list').forEach(appId => { ++ this._appConfigs.add(appId); ++ }); ++ ++ this._updateAppData(); ++ } ++ ++ _updateAppData() { ++ let ids = [...this._appConfigs.values()]; ++ let removedApps = [...this._appData.keys()].filter( ++ a => !ids.includes(a.id) ++ ); ++ removedApps.forEach(app => { ++ app.disconnect(this._appData.get(app).windowsChangedId); ++ this._appData.delete(app); ++ }); ++ ++ let addedApps = ids.map(id => this._appSystem.lookup_app(id)).filter( ++ app => app != null && !this._appData.has(app) ++ ); ++ addedApps.forEach(app => { ++ let data = { ++ windows: app.get_windows(), ++ windowsChangedId: app.connect('windows-changed', ++ this._appWindowsChanged.bind(this)) ++ } ++ this._appData.set(app, data); ++ }); ++ } ++ ++ destroy() { ++ if (this._appsChangedId) { ++ this._appSystem.disconnect(this._appsChangedId); ++ this._appsChangedId = 0; ++ } ++ ++ if (this._settings) { ++ this._settings.run_dispose(); ++ this._settings = null; ++ } ++ ++ this._appConfigs.clear(); ++ this._updateAppData(); ++ } ++ ++ _appWindowsChanged(app) { ++ let data = this._appData.get(app); ++ let windows = app.get_windows(); ++ ++ // If get_compositor_private() returns non-NULL on a removed windows, ++ // the window still exists and is just moved to a different workspace ++ // or something; assume it'll be added back immediately, so keep it ++ // to avoid moving it again ++ windows.push(...data.windows.filter( ++ w => !windows.includes(w) && w.get_compositor_private() != null ++ )); ++ ++ windows.filter(w => !data.windows.includes(w)).forEach(window => { ++ let leader = data.windows.find(w => w.get_pid() == window.get_pid()); ++ if (leader) ++ window.change_workspace(leader.get_workspace()); ++ }); ++ data.windows = windows; ++ } ++}; ++ ++let prevCheckWorkspaces; ++let winMover; ++ ++function init() { ++ Convenience.initTranslations(); ++} ++ ++function enable() { ++ winMover = new WindowMover(); ++} ++ ++function disable() { ++ winMover.destroy(); ++} +diff --git a/extensions/window-grouper/meson.build b/extensions/window-grouper/meson.build +new file mode 100644 +index 0000000..c55a783 +--- /dev/null ++++ b/extensions/window-grouper/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/window-grouper/metadata.json.in b/extensions/window-grouper/metadata.json.in +new file mode 100644 +index 0000000..aa202c8 +--- /dev/null ++++ b/extensions/window-grouper/metadata.json.in +@@ -0,0 +1,11 @@ ++{ ++ "extension-id": "@extension_id@", ++ "uuid": "@uuid@", ++ "settings-schema": "@gschemaname@", ++ "gettext-domain": "@gettext_domain@", ++ "name": "Window grouper", ++ "description": "Keep windows that belong to the same process on the same workspace.", ++ "shell-version": [ "@shell_current@" ], ++ "original-authors": [ "fmuellner@redhat.com" ], ++ "url": "@url@" ++} +diff --git a/extensions/window-grouper/org.gnome.shell.extensions.window-grouper.gschema.xml b/extensions/window-grouper/org.gnome.shell.extensions.window-grouper.gschema.xml +new file mode 100644 +index 0000000..ee052a6 +--- /dev/null ++++ b/extensions/window-grouper/org.gnome.shell.extensions.window-grouper.gschema.xml +@@ -0,0 +1,9 @@ ++ ++ ++ ++ [ ] ++ Application that should be grouped ++ A list of application ids ++ ++ ++ +diff --git a/extensions/window-grouper/prefs.js b/extensions/window-grouper/prefs.js +new file mode 100644 +index 0000000..27fbcca +--- /dev/null ++++ b/extensions/window-grouper/prefs.js +@@ -0,0 +1,194 @@ ++// -*- mode: js2; indent-tabs-mode: nil; js2-basic-offset: 4 -*- ++// Start apps on custom workspaces ++ ++const { Gio, GObject, Gtk } = imports.gi; ++ ++const Gettext = imports.gettext.domain('gnome-shell-extensions'); ++const _ = Gettext.gettext; ++const N_ = e => e; ++ ++const ExtensionUtils = imports.misc.extensionUtils; ++const Me = ExtensionUtils.getCurrentExtension(); ++const Convenience = Me.imports.convenience; ++ ++const SETTINGS_KEY = 'application-list'; ++ ++const Columns = { ++ APPINFO: 0, ++ DISPLAY_NAME: 1, ++ ICON: 2 ++}; ++ ++const Widget = GObject.registerClass({ ++ GTypeName: 'WindowGrouperPrefsWidget', ++}, class Widget extends Gtk.Grid { ++ _init(params) { ++ super._init(params); ++ this.set_orientation(Gtk.Orientation.VERTICAL); ++ ++ this._settings = Convenience.getSettings(); ++ this._settings.connect('changed', this._refresh.bind(this)); ++ this._changedPermitted = false; ++ ++ this._store = new Gtk.ListStore(); ++ this._store.set_column_types([Gio.AppInfo, GObject.TYPE_STRING, Gio.Icon]); ++ ++ let scrolled = new Gtk.ScrolledWindow({ shadow_type: Gtk.ShadowType.IN}); ++ scrolled.set_policy(Gtk.PolicyType.NEVER, Gtk.PolicyType.AUTOMATIC); ++ this.add(scrolled); ++ ++ ++ this._treeView = new Gtk.TreeView({ ++ model: this._store, ++ headers_visible: false, ++ hexpand: true, ++ vexpand: true ++ }); ++ this._treeView.get_selection().set_mode(Gtk.SelectionMode.SINGLE); ++ ++ let appColumn = new Gtk.TreeViewColumn({ ++ sort_column_id: Columns.DISPLAY_NAME, ++ spacing: 12 ++ }); ++ let iconRenderer = new Gtk.CellRendererPixbuf({ ++ stock_size: Gtk.IconSize.DIALOG, ++ xpad: 12, ++ ypad: 12 ++ }); ++ appColumn.pack_start(iconRenderer, false); ++ appColumn.add_attribute(iconRenderer, "gicon", Columns.ICON); ++ let nameRenderer = new Gtk.CellRendererText(); ++ appColumn.pack_start(nameRenderer, true); ++ appColumn.add_attribute(nameRenderer, "text", Columns.DISPLAY_NAME); ++ this._treeView.append_column(appColumn); ++ ++ 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); ++ this.add(toolbar); ++ ++ let newButton = new Gtk.ToolButton({ ++ icon_name: 'list-add-symbolic' ++ }); ++ newButton.connect('clicked', this._createNew.bind(this)); ++ toolbar.add(newButton); ++ ++ let delButton = new Gtk.ToolButton({ ++ icon_name: 'list-remove-symbolic' ++ }); ++ delButton.connect('clicked', this._deleteSelected.bind(this)); ++ toolbar.add(delButton); ++ ++ let selection = this._treeView.get_selection(); ++ selection.connect('changed', () => { ++ delButton.sensitive = selection.count_selected_rows() > 0; ++ }); ++ delButton.sensitive = selection.count_selected_rows() > 0; ++ ++ this._changedPermitted = true; ++ this._refresh(); ++ } ++ ++ _createNew() { ++ let dialog = new Gtk.AppChooserDialog({ ++ heading: _("Select an application for which grouping should apply"), ++ transient_for: this.get_toplevel(), ++ modal: true ++ }); ++ ++ dialog.get_widget().show_all = true; ++ ++ dialog.connect('response', (dialog, id) => { ++ if (id != Gtk.ResponseType.OK) { ++ dialog.destroy(); ++ return; ++ } ++ ++ let appInfo = dialog.get_app_info(); ++ if (!appInfo) { ++ dialog.destroy(); ++ return; ++ } ++ ++ this._changedPermitted = false; ++ this._appendItem(appInfo.get_id()); ++ this._changedPermitted = true; ++ ++ let iter = this._store.append(); ++ this._store.set(iter, ++ [Columns.APPINFO, Columns.ICON, Columns.DISPLAY_NAME], ++ [appInfo, appInfo.get_icon(), appInfo.get_display_name()]); ++ ++ dialog.destroy(); ++ }); ++ dialog.show_all(); ++ } ++ ++ _deleteSelected() { ++ let [any, model, iter] = this._treeView.get_selection().get_selected(); ++ ++ if (any) { ++ let appInfo = this._store.get_value(iter, Columns.APPINFO); ++ ++ this._changedPermitted = false; ++ this._removeItem(appInfo.get_id()); ++ this._changedPermitted = true; ++ this._store.remove(iter); ++ } ++ } ++ ++ _refresh() { ++ if (!this._changedPermitted) ++ // Ignore this notification, model is being modified outside ++ return; ++ ++ this._store.clear(); ++ ++ let currentItems = this._settings.get_strv(SETTINGS_KEY); ++ let validItems = [ ]; ++ for (let i = 0; i < currentItems.length; i++) { ++ let id = currentItems[i]; ++ let appInfo = Gio.DesktopAppInfo.new(id); ++ if (!appInfo) ++ continue; ++ validItems.push(currentItems[i]); ++ ++ let iter = this._store.append(); ++ this._store.set(iter, ++ [Columns.APPINFO, Columns.ICON, Columns.DISPLAY_NAME], ++ [appInfo, appInfo.get_icon(), appInfo.get_display_name()]); ++ } ++ ++ if (validItems.length != currentItems.length) // some items were filtered out ++ this._settings.set_strv(SETTINGS_KEY, validItems); ++ } ++ ++ _appendItem(id) { ++ let currentItems = this._settings.get_strv(SETTINGS_KEY); ++ currentItems.push(id); ++ this._settings.set_strv(SETTINGS_KEY, currentItems); ++ } ++ ++ _removeItem(id) { ++ let currentItems = this._settings.get_strv(SETTINGS_KEY); ++ let index = currentItems.indexOf(id); ++ ++ if (index < 0) ++ return; ++ currentItems.splice(index, 1); ++ this._settings.set_strv(SETTINGS_KEY, currentItems); ++ } ++}); ++ ++ ++function init() { ++ Convenience.initTranslations(); ++} ++ ++function buildPrefsWidget() { ++ let widget = new Widget({ margin: 12 }); ++ widget.show_all(); ++ ++ return widget; ++} +diff --git a/extensions/window-grouper/stylesheet.css b/extensions/window-grouper/stylesheet.css +new file mode 100644 +index 0000000..25134b6 +--- /dev/null ++++ b/extensions/window-grouper/stylesheet.css +@@ -0,0 +1 @@ ++/* This extensions requires no special styling */ +diff --git a/meson.build b/meson.build +index 201c484..6a4d1bf 100644 +--- a/meson.build ++++ b/meson.build +@@ -59,7 +59,8 @@ all_extensions += [ + 'panel-favorites', + 'top-icons', + 'updates-dialog', +- 'user-theme' ++ 'user-theme', ++ 'window-grouper' + ] + + enabled_extensions = get_option('enable_extensions') +-- +2.21.0 + + +From a2e46a1c2ee8308133703a9ba7951fdc4621100a Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Florian=20M=C3=BCllner?= +Date: Tue, 26 Mar 2019 21:32:09 +0100 +Subject: [PATCH 7/7] Add disable-screenshield extension + +--- + extensions/disable-screenshield/extension.js | 32 +++++++++++++++++++ + extensions/disable-screenshield/meson.build | 5 +++ + .../disable-screenshield/metadata.json.in | 9 ++++++ + .../disable-screenshield/stylesheet.css | 1 + + meson.build | 1 + + 5 files changed, 48 insertions(+) + create mode 100644 extensions/disable-screenshield/extension.js + create mode 100644 extensions/disable-screenshield/meson.build + create mode 100644 extensions/disable-screenshield/metadata.json.in + create mode 100644 extensions/disable-screenshield/stylesheet.css + +diff --git a/extensions/disable-screenshield/extension.js b/extensions/disable-screenshield/extension.js +new file mode 100644 +index 0000000..4f80f3c +--- /dev/null ++++ b/extensions/disable-screenshield/extension.js +@@ -0,0 +1,32 @@ ++const ScreenShield = imports.ui.screenShield; ++ ++let _onUserBecameActiveOrig; ++ ++function _onUserBecameActiveInjected() ++{ ++ this.idleMonitor.remove_watch(this._becameActiveId); ++ this._becameActiveId = 0; ++ ++ this._longLightbox.hide(); ++ this._shortLightbox.hide(); ++ ++ this.deactivate(false); ++} ++ ++function init() ++{ ++} ++ ++function enable() ++{ ++ _onUserBecameActiveOrig = ++ ScreenShield.ScreenShield.prototype._onUserBecameActive; ++ ScreenShield.ScreenShield.prototype._onUserBecameActive = ++ _onUserBecameActiveInjected; ++} ++ ++function disable() ++{ ++ ScreenShield.ScreenShield.prototype._onUserBecameActive = ++ _onUserBecameActiveOrig; ++} +diff --git a/extensions/disable-screenshield/meson.build b/extensions/disable-screenshield/meson.build +new file mode 100644 +index 0000000..48504f6 +--- /dev/null ++++ b/extensions/disable-screenshield/meson.build +@@ -0,0 +1,5 @@ ++extension_data += configure_file( ++ input: metadata_name + '.in', ++ output: metadata_name, ++ configuration: metadata_conf ++) +diff --git a/extensions/disable-screenshield/metadata.json.in b/extensions/disable-screenshield/metadata.json.in +new file mode 100644 +index 0000000..074429f +--- /dev/null ++++ b/extensions/disable-screenshield/metadata.json.in +@@ -0,0 +1,9 @@ ++{ ++ "extension-id": "@extension_id@", ++ "uuid": "@uuid@", ++ "name": "Disable Screen Shield", ++ "description": "Disable screen shield when screen lock is disabled", ++ "shell-version": [ "@shell_current@" ], ++ "original-authors": [ "lgpasquale@gmail.com" ], ++ "url": "@url@" ++} +diff --git a/extensions/disable-screenshield/stylesheet.css b/extensions/disable-screenshield/stylesheet.css +new file mode 100644 +index 0000000..25134b6 +--- /dev/null ++++ b/extensions/disable-screenshield/stylesheet.css +@@ -0,0 +1 @@ ++/* This extensions requires no special styling */ +diff --git a/meson.build b/meson.build +index 6a4d1bf..7562eb1 100644 +--- a/meson.build ++++ b/meson.build +@@ -53,6 +53,7 @@ all_extensions = default_extensions + all_extensions += [ + 'auto-move-windows', + 'dash-to-dock', ++ 'disable-screenshield', + 'example', + 'native-window-placement', + 'no-hot-corner', +-- +2.21.0 + diff --git a/SOURCES/more-classic-classic-mode.patch b/SOURCES/more-classic-classic-mode.patch new file mode 100644 index 0000000..2e60b40 --- /dev/null +++ b/SOURCES/more-classic-classic-mode.patch @@ -0,0 +1,3313 @@ +From 05833ca853e5e661cf43f59734ca0a29219159fd Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Florian=20M=C3=BCllner?= +Date: Thu, 18 Jul 2019 00:39:49 +0200 +Subject: [PATCH 01/33] apps-menu: Add drop-shadow to application icons + +... to make sure they are readable on light backgrounds. + +https://gitlab.gnome.org/GNOME/gnome-shell-extensions/issues/168 +--- + extensions/apps-menu/extension.js | 4 +++- + 1 file changed, 3 insertions(+), 1 deletion(-) + +diff --git a/extensions/apps-menu/extension.js b/extensions/apps-menu/extension.js +index 49a05c7..860cb77 100644 +--- a/extensions/apps-menu/extension.js ++++ b/extensions/apps-menu/extension.js +@@ -112,7 +112,9 @@ class ApplicationMenuItem extends PopupMenu.PopupBaseMenuItem { + } + + _updateIcon() { +- this._iconBin.set_child(this.getDragActor()); ++ let icon = this.getDragActor(); ++ icon.style_class = 'icon-dropshadow'; ++ this._iconBin.set_child(icon); + } + }; + +-- +2.21.0 + + +From 4ace6d5da8e0edc590706af8afb8cfc843a06af4 Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Florian=20M=C3=BCllner?= +Date: Wed, 29 May 2019 10:17:20 +0000 +Subject: [PATCH 02/33] places-menu: Don't hardcode position + +The extension currently assumes that we have the "Activities" button +at the left of the top bar. This is currently true, not only in the +regular session, but also in GNOME classic where the button is hidden +(but still present). + +However this is about to change: We will stop taking over the button +from the apps-menu extension, and instead disable "Activities" from +the session mode definition. + +Prepare for this by adding the places menu before the application menu +instead of assuming a hardcoded position. + +https://gitlab.gnome.org/GNOME/gnome-shell-extensions/merge_requests/69 +--- + extensions/places-menu/extension.js | 4 ++-- + 1 file changed, 2 insertions(+), 2 deletions(-) + +diff --git a/extensions/places-menu/extension.js b/extensions/places-menu/extension.js +index 429e81d..cdecb7b 100644 +--- a/extensions/places-menu/extension.js ++++ b/extensions/places-menu/extension.js +@@ -133,9 +133,9 @@ let _indicator; + function enable() { + _indicator = new PlacesMenu; + +- let pos = 1; ++ let pos = Main.sessionMode.panel.left.indexOf('appMenu'); + if ('apps-menu' in Main.panel.statusArea) +- pos = 2; ++ pos++; + Main.panel.addToStatusArea('places-menu', _indicator, pos, 'left'); + } + +-- +2.21.0 + + +From 829929350bda3a03795c43d303273a7c9ff225b3 Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Florian=20M=C3=BCllner?= +Date: Thu, 27 Jun 2019 03:57:53 +0200 +Subject: [PATCH 03/33] 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 860cb77..08eda10 100644 +--- a/extensions/apps-menu/extension.js ++++ b/extensions/apps-menu/extension.js +@@ -491,6 +491,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 + + +From 83e93a115f7375991893877ea82f2409569fee0f Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Florian=20M=C3=BCllner?= +Date: Wed, 29 May 2019 08:32:03 +0000 +Subject: [PATCH 04/33] apps-menu: Stop taking over Activities button + +We don't want the "Activities" button in GNOME Classic, but the current +way of handling it is confusing: + + - the button is hidden, but the corresponding hot corner + sometimes works (when the application menu isn't open) + + - the button is effectively moved inside the menu, although + it's clearly not an app or category + + - the apps-menu can be used independent from classic mode, in + which case removing the "Activities" button may not be wanted + +Address those points by removing any handling of the activities button +from the apps-menu extension. We will remove it again from the classic +session via a session mode tweak. + +https://gitlab.gnome.org/GNOME/gnome-shell-extensions/merge_requests/69 +--- + extensions/apps-menu/extension.js | 63 +++---------------------------- + 1 file changed, 6 insertions(+), 57 deletions(-) + +diff --git a/extensions/apps-menu/extension.js b/extensions/apps-menu/extension.js +index 08eda10..9e02946 100644 +--- a/extensions/apps-menu/extension.js ++++ b/extensions/apps-menu/extension.js +@@ -30,22 +30,6 @@ const HORIZ_FACTOR = 5; + const MENU_HEIGHT_OFFSET = 132; + const NAVIGATION_REGION_OVERSHOOT = 50; + +-class ActivitiesMenuItem extends PopupMenu.PopupBaseMenuItem { +- constructor(button) { +- super(); +- this._button = button; +- let label = new St.Label({ text: _("Activities Overview") }); +- this.actor.add_child(label); +- this.actor.label_actor = label; +- } +- +- activate(event) { +- this._button.menu.toggle(); +- Main.overview.toggle(); +- super.activate(event); +- } +-}; +- + class ApplicationMenuItem extends PopupMenu.PopupBaseMenuItem { + constructor(button, app) { + super(); +@@ -244,21 +228,6 @@ class ApplicationsMenu extends PopupMenu.PopupMenu { + return false; + } + +- open(animate) { +- this._button.hotCorner.setBarrierSize(0); +- if (this._button.hotCorner.actor) // fallback corner +- this._button.hotCorner.actor.hide(); +- super.open(animate); +- } +- +- close(animate) { +- let size = Main.layoutManager.panelBox.height; +- this._button.hotCorner.setBarrierSize(size); +- if (this._button.hotCorner.actor) // fallback corner +- this._button.hotCorner.actor.show(); +- super.close(animate); +- } +- + toggle() { + if (this.isOpen) { + this._button.selectCategory(null, null); +@@ -407,7 +376,7 @@ class DesktopTarget { + Signals.addSignalMethods(DesktopTarget.prototype); + + class ApplicationsButton extends PanelMenu.Button { +- constructor() { ++ constructor(includeIcon) { + super(1.0, null, false); + + this.setMenu(new ApplicationsMenu(this.actor, 1.0, St.Side.TOP, this)); +@@ -422,7 +391,8 @@ class ApplicationsButton extends PanelMenu.Button { + + 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' }); ++ style_class: 'panel-logo-icon', ++ visible: includeIcon }); + hbox.add_actor(this._icon); + + this._label = new St.Label({ text: _("Applications"), +@@ -435,7 +405,6 @@ class ApplicationsButton extends PanelMenu.Button { + this.actor.name = 'panelApplications'; + this.actor.label_actor = this._label; + +- this.actor.connect('captured-event', this._onCapturedEvent.bind(this)); + this.actor.connect('destroy', this._onDestroy.bind(this)); + + this._showingId = Main.overview.connect('showing', () => { +@@ -479,10 +448,6 @@ class ApplicationsButton extends PanelMenu.Button { + } + } + +- get hotCorner() { +- return Main.layoutManager.hotCorners[Main.layoutManager.primaryIndex]; +- } +- + _createVertSeparator() { + let separator = new St.DrawingArea({ style_class: 'calendar-vertical-separator', + pseudo_class: 'highlighted' }); +@@ -509,14 +474,6 @@ class ApplicationsButton extends PanelMenu.Button { + this._desktopTarget.destroy(); + } + +- _onCapturedEvent(actor, event) { +- if (event.type() == Clutter.EventType.BUTTON_PRESS) { +- if (!Main.overview.shouldToggleByCornerOrButton()) +- return true; +- } +- return false; +- } +- + _onMenuKeyPress(actor, event) { + let symbol = event.get_key_symbol(); + if (symbol == Clutter.KEY_Left || symbol == Clutter.KEY_Right) { +@@ -651,11 +608,6 @@ class ApplicationsButton extends PanelMenu.Button { + x_fill: true, y_fill: true, + y_align: St.Align.START }); + +- let activities = new ActivitiesMenuItem(this); +- this.leftBox.add(activities.actor, { expand: false, +- x_fill: true, y_fill: false, +- y_align: St.Align.START }); +- + this.applicationsBox = new St.BoxLayout({ vertical: true }); + this.applicationsScrollBox.add_actor(this.applicationsBox); + this.categoriesBox = new St.BoxLayout({ vertical: true }); +@@ -760,19 +712,16 @@ class ApplicationsButton extends PanelMenu.Button { + }; + + let appsMenuButton; +-let activitiesButton; + + function enable() { +- activitiesButton = Main.panel.statusArea['activities']; +- activitiesButton.container.hide(); +- appsMenuButton = new ApplicationsButton(); +- Main.panel.addToStatusArea('apps-menu', appsMenuButton, 1, 'left'); ++ let index = Main.sessionMode.panel.left.indexOf('activities') + 1; ++ appsMenuButton = new ApplicationsButton(index == 0); ++ Main.panel.addToStatusArea('apps-menu', appsMenuButton, index, 'left'); + } + + function disable() { + Main.panel.menuManager.removeMenu(appsMenuButton.menu); + appsMenuButton.destroy(); +- activitiesButton.container.show(); + } + + function init(metadata) { +-- +2.21.0 + + +From ea66ad17fe7b5d1a8071a6aa693603be5e62ae27 Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Florian=20M=C3=BCllner?= +Date: Fri, 7 Jun 2019 14:30:16 +0000 +Subject: [PATCH 05/33] apps-menu: Stop hiding the overview when toggled + +Now that the extension no longer doubles as the "Activities" button, +that behavior is confusing. + +https://gitlab.gnome.org/GNOME/gnome-shell-extensions/merge_requests/69 +--- + extensions/apps-menu/extension.js | 6 +----- + 1 file changed, 1 insertion(+), 5 deletions(-) + +diff --git a/extensions/apps-menu/extension.js b/extensions/apps-menu/extension.js +index 9e02946..50fe55a 100644 +--- a/extensions/apps-menu/extension.js ++++ b/extensions/apps-menu/extension.js +@@ -229,12 +229,8 @@ class ApplicationsMenu extends PopupMenu.PopupMenu { + } + + toggle() { +- if (this.isOpen) { ++ if (this.isOpen) + this._button.selectCategory(null, null); +- } else { +- if (Main.overview.visible) +- Main.overview.hide(); +- } + super.toggle(); + } + }; +-- +2.21.0 + + +From 8b850a7f9b17cafbd417f597650a2a13cf8cfefa Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Florian=20M=C3=BCllner?= +Date: Fri, 7 Jun 2019 20:07:19 +0000 +Subject: [PATCH 06/33] apps-menu: Hide overview when launching app + +Now that we no longer hide the overview when the menu is opened, +it is possible to activate menu entries from the overview. Start +hiding the overview in that case, which is consistent with app +launching elsewhere. + +https://gitlab.gnome.org/GNOME/gnome-shell-extensions/merge_requests/69 +--- + 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 50fe55a..8d59e6d 100644 +--- a/extensions/apps-menu/extension.js ++++ b/extensions/apps-menu/extension.js +@@ -75,6 +75,8 @@ class ApplicationMenuItem extends PopupMenu.PopupBaseMenuItem { + this._button.selectCategory(null, null); + this._button.menu.toggle(); + super.activate(event); ++ ++ Main.overview.hide(); + } + + setActive(active, params) { +-- +2.21.0 + + +From 9762c3507177fe3a420bb228549aca03757d73c5 Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Florian=20M=C3=BCllner?= +Date: Wed, 29 May 2019 09:44:30 +0000 +Subject: [PATCH 07/33] classic: Disable overview + +The overview is one of the defining features of GNOME 3, and thus +almost by definition at odds with the classic session, which +emulates a traditional GNOME 2 desktop. + +Even with the less prominent placement inside the application menu +it never quite fit in - it doesn't help that besides the different +UI paradigma, the overview keeps its "normal" styling which differs +greatly with classic's normal mode. + +So besides removing the "Activities" button via the session mode +definition, now that the apps-menu extension doesn't replace it anymore, +disable the overview completely in the classic session. + +https://gitlab.gnome.org/GNOME/gnome-shell-extensions/merge_requests/69 +--- + data/classic.json.in | 3 ++- + 1 file changed, 2 insertions(+), 1 deletion(-) + +diff --git a/data/classic.json.in b/data/classic.json.in +index fdb3762..c1c0544 100644 +--- a/data/classic.json.in ++++ b/data/classic.json.in +@@ -1,8 +1,9 @@ + { + "parentMode": "user", + "stylesheetName": "gnome-classic.css", ++ "hasOverview": false, + "enabledExtensions": [@CLASSIC_EXTENSIONS@], +- "panel": { "left": ["activities", "appMenu"], ++ "panel": { "left": ["appMenu"], + "center": [], + "right": ["a11y", "keyboard", "dateMenu", "aggregateMenu"] + } +-- +2.21.0 + + +From 1fee42083afafef861fc57e5d886e4e00945a19c Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Florian=20M=C3=BCllner?= +Date: Tue, 14 May 2019 19:51:22 +0200 +Subject: [PATCH 08/33] window-list: Add window picker button +MIME-Version: 1.0 +Content-Type: text/plain; charset=UTF-8 +Content-Transfer-Encoding: 8bit + +With the latest changes, GNOME Classic has become so classic that it +is bordering dull. Salvage at least a tiny piece of GNOME 3 in form +of a window-pick button which toggles an exposé-like reduced overview. + +https://gitlab.gnome.org/GNOME/gnome-shell-extensions/merge_requests/73 +--- + extensions/window-list/classic.css | 20 +- + extensions/window-list/extension.js | 36 +++- + extensions/window-list/meson.build | 2 +- + extensions/window-list/stylesheet.css | 27 ++- + extensions/window-list/windowPicker.js | 260 +++++++++++++++++++++++++ + 5 files changed, 332 insertions(+), 13 deletions(-) + create mode 100644 extensions/window-list/windowPicker.js + +diff --git a/extensions/window-list/classic.css b/extensions/window-list/classic.css +index f3c44a3..c506bea 100644 +--- a/extensions/window-list/classic.css ++++ b/extensions/window-list/classic.css +@@ -6,14 +6,13 @@ + height: 2.25em ; + } + +- .bottom-panel .window-button > StWidget { ++ .bottom-panel .window-button > StWidget, ++ .bottom-panel .window-picker-toggle > StWidget { + background-gradient-drection: vertical; + background-color: #fff; + background-gradient-start: #fff; + background-gradient-end: #eee; + color: #000; +- -st-natural-width: 18.7em; +- max-width: 18.75em; + color: #2e3436; + background-color: #eee; + border-radius: 2px; +@@ -22,7 +21,17 @@ + text-shadow: 0 0 transparent; + } + +- .bottom-panel .window-button:hover > StWidget { ++ .bottom-panel .window-button > StWidget { ++ -st-natural-width: 18.7em; ++ max-width: 18.75em; ++ } ++ ++ .bottom-panel .window-picker-toggle > StWidet { ++ border: 1px solid rgba(0,0,0,0.3); ++ } ++ ++ .bottom-panel .window-button:hover > StWidget, ++ .bottom-panel .window-picker-toggle:hover > StWidget { + background-color: #f9f9f9; + } + +@@ -31,7 +40,8 @@ + box-shadow: inset 1px 1px 2px rgba(0,0,0,0.5); + } + +- .bottom-panel .window-button.focused > StWidget { ++ .bottom-panel .window-button.focused > StWidget, ++ .bottom-panel .window-picker-toggle:checked > StWidget { + background-color: #ddd; + box-shadow: inset 1px 1px 1px rgba(0,0,0,0.5); + } +diff --git a/extensions/window-list/extension.js b/extensions/window-list/extension.js +index 716a324..f4610f9 100644 +--- a/extensions/window-list/extension.js ++++ b/extensions/window-list/extension.js +@@ -8,12 +8,15 @@ const St = imports.gi.St; + + const DND = imports.ui.dnd; + const Main = imports.ui.main; ++const Overview = imports.ui.overview; + const PanelMenu = imports.ui.panelMenu; + const PopupMenu = imports.ui.popupMenu; ++const Tweener = imports.ui.tweener; + + const ExtensionUtils = imports.misc.extensionUtils; + const Me = ExtensionUtils.getCurrentExtension(); + const Convenience = Me.imports.convenience; ++const { WindowPicker, WindowPickerToggle } = Me.imports.windowPicker; + + const Gettext = imports.gettext.domain('gnome-shell-extensions'); + const _ = Gettext.gettext; +@@ -769,6 +772,12 @@ class WindowList { + let box = new St.BoxLayout({ x_expand: true, y_expand: true }); + this.actor.add_actor(box); + ++ let toggle = new WindowPickerToggle(); ++ box.add_actor(toggle); ++ ++ toggle.connect('notify::checked', ++ this._updateWindowListVisibility.bind(this)); ++ + let layout = new Clutter.BoxLayout({ homogeneous: true }); + this._windowList = new St.Widget({ style_class: 'window-list', + reactive: true, +@@ -931,6 +940,19 @@ class WindowList { + this._workspaceIndicator.actor.visible = hasWorkspaces && workspacesOnMonitor; + } + ++ _updateWindowListVisibility() { ++ let visible = !Main.windowPicker.visible; ++ ++ Tweener.addTween(this._windowList, { ++ opacity: visible ? 255 : 0, ++ transition: 'ease-out-quad', ++ time: Overview.ANIMATION_TIME ++ }); ++ ++ this._windowList.reactive = visible; ++ this._windowList.get_children().forEach(c => c.reactive = visible); ++ } ++ + _getPreferredUngroupedWindowListWidth() { + if (this._windowList.get_n_children() == 0) + return this._windowList.get_preferred_width(-1)[1]; +@@ -1205,7 +1227,7 @@ class WindowList { + class Extension { + constructor() { + this._windowLists = null; +- this._injections = {}; ++ this._hideOverviewOrig = Main.overview.hide; + } + + enable() { +@@ -1220,6 +1242,13 @@ class Extension { + Main.layoutManager.connect('monitors-changed', + this._buildWindowLists.bind(this)); + ++ Main.windowPicker = new WindowPicker(); ++ ++ Main.overview.hide = () => { ++ Main.windowPicker.close(); ++ this._hideOverviewOrig.call(Main.overview); ++ }; ++ + this._buildWindowLists(); + } + +@@ -1250,6 +1279,11 @@ class Extension { + windowList.actor.destroy(); + }); + this._windowLists = null; ++ ++ Main.windowPicker.actor.destroy(); ++ delete Main.windowPicker; ++ ++ Main.overview.hide = this._hideOverviewOrig; + } + + someWindowListContains(actor) { +diff --git a/extensions/window-list/meson.build b/extensions/window-list/meson.build +index b4aa4db..5b1f5f5 100644 +--- a/extensions/window-list/meson.build ++++ b/extensions/window-list/meson.build +@@ -4,7 +4,7 @@ extension_data += configure_file( + configuration: metadata_conf + ) + +-extension_sources += files('prefs.js') ++extension_sources += files('prefs.js', 'windowPicker.js') + extension_schemas += files(metadata_conf.get('gschemaname') + '.gschema.xml') + + if classic_mode_enabled +diff --git a/extensions/window-list/stylesheet.css b/extensions/window-list/stylesheet.css +index f5285cb..91383ab 100644 +--- a/extensions/window-list/stylesheet.css ++++ b/extensions/window-list/stylesheet.css +@@ -26,9 +26,8 @@ + spacing: 4px; + } + +-.window-button > StWidget { +- -st-natural-width: 18.75em; +- max-width: 18.75em; ++.window-button > StWidget, ++.window-picker-toggle > StWidget { + color: #bbb; + background-color: black; + border-radius: 4px; +@@ -37,7 +36,21 @@ + text-shadow: 1px 1px 4px rgba(0,0,0,0.8); + } + +-.window-button:hover > StWidget { ++.window-picker-toggle { ++ padding: 3px; ++} ++ ++.window-picker-toggle > StWidet { ++ border: 1px solid rgba(255,255,255,0.3); ++} ++ ++.window-button > StWidget { ++ -st-natural-width: 18.75em; ++ max-width: 18.75em; ++} ++ ++.window-button:hover > StWidget, ++.window-picker-toggle:hover > StWidget { + color: white; + background-color: #1f1f1f; + } +@@ -47,12 +60,14 @@ + box-shadow: inset 2px 2px 4px rgba(255,255,255,0.5); + } + +-.window-button.focused > StWidget { ++.window-button.focused > StWidget, ++.window-picker-toggle:checked > StWidget { + color: white; + box-shadow: inset 1px 1px 4px rgba(255,255,255,0.7); + } + +-.window-button.focused:active > StWidget { ++.window-button.focused:active > StWidget, ++.window-picker-toggle:checked:active > StWidget { + box-shadow: inset 2px 2px 4px rgba(255,255,255,0.7); + } + +diff --git a/extensions/window-list/windowPicker.js b/extensions/window-list/windowPicker.js +new file mode 100644 +index 0000000..59875d5 +--- /dev/null ++++ b/extensions/window-list/windowPicker.js +@@ -0,0 +1,260 @@ ++/* exported WindowPicker, WindowPickerToggle */ ++const { Clutter, GLib, GObject, Meta, Shell, St } = imports.gi; ++const Signals = imports.signals; ++ ++const Layout = imports.ui.layout; ++const Main = imports.ui.main; ++const Overview = imports.ui.overview; ++const { WorkspacesDisplay } = imports.ui.workspacesView; ++ ++let MyWorkspacesDisplay = class extends WorkspacesDisplay { ++ constructor() { ++ super(); ++ ++ this.actor.add_constraint( ++ new Layout.MonitorConstraint({ ++ primary: true, ++ work_area: true ++ })); ++ ++ this.actor.connect('destroy', this._onDestroy.bind(this)); ++ ++ this._workareasChangedId = global.screen.connect('workareas-changed', ++ this._onWorkAreasChanged.bind(this)); ++ this._onWorkAreasChanged(); ++ } ++ ++ show(...args) { ++ if (this._scrollEventId == 0) ++ this._scrollEventId = Main.windowPicker.connect('scroll-event', ++ this._onScrollEvent.bind(this)); ++ ++ super.show(...args); ++ } ++ ++ hide(...args) { ++ if (this._scrollEventId > 0) ++ Main.windowPicker.disconnect(this._scrollEventId); ++ this._scrollEventId = 0; ++ ++ super.hide(...args); ++ } ++ ++ _onWorkAreasChanged() { ++ let { primaryIndex } = Main.layoutManager; ++ let workarea = Main.layoutManager.getWorkAreaForMonitor(primaryIndex); ++ this.setWorkspacesFullGeometry(workarea); ++ } ++ ++ _updateWorkspacesViews() { ++ super._updateWorkspacesViews(); ++ ++ this._workspacesViews.forEach(v => { ++ Main.layoutManager.overviewGroup.remove_actor(v.actor); ++ Main.windowPicker.actor.add_actor(v.actor); ++ }); ++ } ++ ++ _onDestroy() { ++ if (this._workareasChangedId) ++ global.screen.disconnect(this._workareasChangedId); ++ this._workareasChangedId = 0; ++ } ++}; ++ ++var WindowPicker = class { ++ constructor() { ++ this._visible = false; ++ this._modal = false; ++ ++ this.actor = new Clutter.Actor(); ++ ++ this.actor.connect('destroy', this._onDestroy.bind(this)); ++ ++ global.bind_property('screen-width', ++ this.actor, 'width', ++ GObject.BindingFlags.SYNC_CREATE); ++ global.bind_property('screen-height', ++ this.actor, 'height', ++ GObject.BindingFlags.SYNC_CREATE); ++ ++ this._backgroundGroup = new Meta.BackgroundGroup({ reactive: true }); ++ this.actor.add_child(this._backgroundGroup); ++ ++ this._backgroundGroup.connect('scroll-event', (a, ev) => { ++ this.emit('scroll-event', ev); ++ }); ++ ++ // Trick WorkspacesDisplay constructor into adding actions here ++ let addActionOrig = Main.overview.addAction; ++ Main.overview.addAction = a => this._backgroundGroup.add_action(a); ++ ++ this._workspacesDisplay = new MyWorkspacesDisplay(); ++ this.actor.add_child(this._workspacesDisplay.actor); ++ ++ Main.overview.addAction = addActionOrig; ++ ++ this._bgManagers = []; ++ ++ this._monitorsChangedId = Main.layoutManager.connect('monitors-changed', ++ this._updateBackgrounds.bind(this)); ++ this._updateBackgrounds(); ++ ++ Main.uiGroup.insert_child_below(this.actor, global.window_group); ++ } ++ ++ get visible() { ++ return this._visible; ++ } ++ ++ open() { ++ if (this._visible) ++ return; ++ ++ this._visible = true; ++ ++ if (!this._syncGrab()) ++ return; ++ ++ this._fakeOverviewVisible(true); ++ this._shadeBackgrounds(); ++ this._fakeOverviewAnimation(); ++ this._workspacesDisplay.show(false); ++ ++ this.emit('open-state-changed', this._visible); ++ } ++ ++ close() { ++ if (!this._visible) ++ return; ++ ++ this._visible = false; ++ ++ if (!this._syncGrab()) ++ return; ++ ++ this._workspacesDisplay.animateFromOverview(false); ++ this._unshadeBackgrounds(); ++ this._fakeOverviewAnimation(() => { ++ this._workspacesDisplay.hide(); ++ this._fakeOverviewVisible(false); ++ }); ++ ++ this.emit('open-state-changed', this._visible); ++ } ++ ++ _fakeOverviewAnimation(onComplete) { ++ Main.overview.animationInProgress = true; ++ GLib.timeout_add( ++ GLib.PRIORITY_DEFAULT, ++ Overview.ANIMATION_TIME * 1000, ++ () => { ++ Main.overview.animationInProgress = false; ++ if (onComplete) ++ onComplete(); ++ }); ++ } ++ ++ _fakeOverviewVisible(visible) { ++ // Fake overview state for WorkspacesDisplay ++ Main.overview.visible = visible; ++ ++ // Hide real windows ++ Main.layoutManager._inOverview = visible; ++ Main.layoutManager._updateVisibility(); ++ } ++ ++ _syncGrab() { ++ if (this._visible) { ++ if (this._modal) ++ return true; ++ ++ this._modal = Main.pushModal(this.actor, { ++ actionMode: Shell.ActionMode.OVERVIEW ++ }); ++ ++ if (!this._modal) { ++ this.hide(); ++ return false; ++ } ++ } else if (this._modal) { ++ Main.popModal(this.actor); ++ this._modal = false; ++ } ++ return true; ++ } ++ ++ _onDestroy() { ++ if (this._monitorsChangedId) ++ Main.layoutManager.disconnect(this._monitorsChangedId); ++ this._monitorsChangedId = 0; ++ } ++ ++ _updateBackgrounds() { ++ Main.overview._updateBackgrounds.call(this); ++ } ++ ++ _shadeBackgrounds() { ++ Main.overview._shadeBackgrounds.call(this); ++ } ++ ++ _unshadeBackgrounds() { ++ Main.overview._unshadeBackgrounds.call(this); ++ } ++}; ++Signals.addSignalMethods(WindowPicker.prototype); ++ ++var WindowPickerToggle = GObject.registerClass( ++class WindowPickerToggle extends St.Button { ++ _init() { ++ let iconBin = new St.Widget({ ++ layout_manager: new Clutter.BinLayout() ++ }); ++ iconBin.add_child(new St.Icon({ ++ icon_name: 'focus-windows-symbolic', ++ icon_size: 16, ++ x_expand: true, ++ y_expand: true, ++ x_align: Clutter.ActorAlign.CENTER, ++ y_align: Clutter.ActorAlign.CENTER ++ })); ++ super._init({ ++ style_class: 'window-picker-toggle', ++ child: iconBin, ++ visible: !Main.sessionMode.hasOverview, ++ x_fill: true, ++ y_fill: true, ++ toggle_mode: true ++ }); ++ ++ this._overlayKeyId = 0; ++ ++ this.connect('destroy', this._onDestroy.bind(this)); ++ ++ this.connect('notify::checked', () => { ++ if (this.checked) ++ Main.windowPicker.open(); ++ else ++ Main.windowPicker.close(); ++ }); ++ ++ if (!Main.sessionMode.hasOverview) { ++ this._overlayKeyId = global.display.connect('overlay-key', () => { ++ if (!Main.windowPicker.visible) ++ Main.windowPicker.open(); ++ else ++ Main.windowPicker.close(); ++ }); ++ } ++ ++ Main.windowPicker.connect('open-state-changed', () => { ++ this.checked = Main.windowPicker.visible; ++ }); ++ } ++ ++ _onDestroy() { ++ if (this._overlayKeyId) ++ global.display.disconnect(this._overlayKeyId); ++ this._overlayKeyId == 0; ++ } ++}); +-- +2.21.0 + + +From ea3f6be47066e1e4a2f86824576a17bc09bcd39d Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Florian=20M=C3=BCllner?= +Date: Sat, 29 Jun 2019 02:52:45 +0200 +Subject: [PATCH 09/33] window-list: Fix resetting handler ID + +This is embarrassing, although destroy() is expected to only run once, +so the bug shouldn't have an effect in practice. + +https://gitlab.gnome.org/GNOME/gnome-shell-extensions/merge_requests/78 +--- + extensions/window-list/windowPicker.js | 2 +- + 1 file changed, 1 insertion(+), 1 deletion(-) + +diff --git a/extensions/window-list/windowPicker.js b/extensions/window-list/windowPicker.js +index 59875d5..c8a3dd6 100644 +--- a/extensions/window-list/windowPicker.js ++++ b/extensions/window-list/windowPicker.js +@@ -255,6 +255,6 @@ class WindowPickerToggle extends St.Button { + _onDestroy() { + if (this._overlayKeyId) + global.display.disconnect(this._overlayKeyId); +- this._overlayKeyId == 0; ++ this._overlayKeyId = 0; + } + }); +-- +2.21.0 + + +From eaa96905569de6640bd59cb89aa47fcdb8f486c4 Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Florian=20M=C3=BCllner?= +Date: Tue, 2 Jul 2019 17:39:55 +0200 +Subject: [PATCH 10/33] window-list: Move super-key handling into WindowPicker + +We have an option to put a window list on each monitor, so we may have +more than one window picker toggle. We don't want each of those try to +toggle the window picker simultanuously, so move handling of the super +key directly into the picker. + +https://gitlab.gnome.org/GNOME/gnome-shell-extensions/merge_requests/80 +--- + extensions/window-list/windowPicker.js | 34 ++++++++++++-------------- + 1 file changed, 15 insertions(+), 19 deletions(-) + +diff --git a/extensions/window-list/windowPicker.js b/extensions/window-list/windowPicker.js +index c8a3dd6..088ebf2 100644 +--- a/extensions/window-list/windowPicker.js ++++ b/extensions/window-list/windowPicker.js +@@ -67,6 +67,8 @@ var WindowPicker = class { + this._visible = false; + this._modal = false; + ++ this._overlayKeyId = 0; ++ + this.actor = new Clutter.Actor(); + + this.actor.connect('destroy', this._onDestroy.bind(this)); +@@ -101,6 +103,15 @@ var WindowPicker = class { + this._updateBackgrounds(); + + Main.uiGroup.insert_child_below(this.actor, global.window_group); ++ ++ if (!Main.sessionMode.hasOverview) { ++ this._overlayKeyId = global.display.connect('overlay-key', () => { ++ if (!this._visible) ++ this.open(); ++ else ++ this.close(); ++ }); ++ } + } + + get visible() { +@@ -188,6 +199,10 @@ var WindowPicker = class { + if (this._monitorsChangedId) + Main.layoutManager.disconnect(this._monitorsChangedId); + this._monitorsChangedId = 0; ++ ++ if (this._overlayKeyId) ++ global.display.disconnect(this._overlayKeyId); ++ this._overlayKeyId = 0; + } + + _updateBackgrounds() { +@@ -227,10 +242,6 @@ class WindowPickerToggle extends St.Button { + toggle_mode: true + }); + +- this._overlayKeyId = 0; +- +- this.connect('destroy', this._onDestroy.bind(this)); +- + this.connect('notify::checked', () => { + if (this.checked) + Main.windowPicker.open(); +@@ -238,23 +249,8 @@ class WindowPickerToggle extends St.Button { + Main.windowPicker.close(); + }); + +- if (!Main.sessionMode.hasOverview) { +- this._overlayKeyId = global.display.connect('overlay-key', () => { +- if (!Main.windowPicker.visible) +- Main.windowPicker.open(); +- else +- Main.windowPicker.close(); +- }); +- } +- + Main.windowPicker.connect('open-state-changed', () => { + this.checked = Main.windowPicker.visible; + }); + } +- +- _onDestroy() { +- if (this._overlayKeyId) +- global.display.disconnect(this._overlayKeyId); +- this._overlayKeyId = 0; +- } + }); +-- +2.21.0 + + +From 381290b9938339768501e34aee6f21d7040e9394 Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Florian=20M=C3=BCllner?= +Date: Tue, 2 Jul 2019 18:12:31 +0200 +Subject: [PATCH 11/33] window-list: Handle closing window picker with Escape + +Just like the overview can be closed with Escape, it makes sense to +allow the same for the window picker (in addition to pressing super +repeatedly). + +https://gitlab.gnome.org/GNOME/gnome-shell-extensions/merge_requests/80 +--- + extensions/window-list/windowPicker.js | 18 ++++++++++++++++++ + 1 file changed, 18 insertions(+) + +diff --git a/extensions/window-list/windowPicker.js b/extensions/window-list/windowPicker.js +index 088ebf2..461a4d1 100644 +--- a/extensions/window-list/windowPicker.js ++++ b/extensions/window-list/windowPicker.js +@@ -68,6 +68,7 @@ var WindowPicker = class { + this._modal = false; + + this._overlayKeyId = 0; ++ this._stageKeyPressId = 0; + + this.actor = new Clutter.Actor(); + +@@ -132,6 +133,16 @@ var WindowPicker = class { + this._fakeOverviewAnimation(); + this._workspacesDisplay.show(false); + ++ this._stageKeyPressId = global.stage.connect('key-press-event', ++ (a, event) => { ++ let sym = event.get_key_symbol(); ++ if (sym == Clutter.KEY_Escape) { ++ this.close(); ++ return Clutter.EVENT_STOP; ++ } ++ return Clutter.EVENT_PROPAGATE; ++ }); ++ + this.emit('open-state-changed', this._visible); + } + +@@ -151,6 +162,9 @@ var WindowPicker = class { + this._fakeOverviewVisible(false); + }); + ++ global.stage.disconnect(this._stageKeyPressId); ++ this._stageKeyPressId = 0; ++ + this.emit('open-state-changed', this._visible); + } + +@@ -203,6 +217,10 @@ var WindowPicker = class { + if (this._overlayKeyId) + global.display.disconnect(this._overlayKeyId); + this._overlayKeyId = 0; ++ ++ if (this._stageKeyPressId) ++ global.stage.disconnect(this._stageKeyPressId); ++ this._stageKeyPressId = 0; + } + + _updateBackgrounds() { +-- +2.21.0 + + +From 22a3bb1051ebda5cba050cdc95114673dae7dab8 Mon Sep 17 00:00:00 2001 +From: Jakub Steiner +Date: Mon, 15 Jul 2019 23:40:09 +0200 +Subject: [PATCH 12/33] classic: hover state for panel buttons + +- prelight before active +- lighten up slightly, similar to what the default does (inverted) + +Fixes https://gitlab.gnome.org/GNOME/gnome-shell-extensions/issues/169 +--- + data/gnome-classic.scss | 14 ++++++++------ + 1 file changed, 8 insertions(+), 6 deletions(-) + +diff --git a/data/gnome-classic.scss b/data/gnome-classic.scss +index e8f4803..a231534 100644 +--- a/data/gnome-classic.scss ++++ b/data/gnome-classic.scss +@@ -30,18 +30,20 @@ $variant: 'light'; + font-weight: normal; + color: $fg_color; + text-shadow: none; ++ &:hover { ++ color: lighten($fg_color,10%); ++ text-shadow: none; ++ & .system-status-icon { icon-shadow: none; } ++ } + &:active, &:overview, &:focus, &:checked { + // Trick due to St limitations. It needs a background to draw + // a box-shadow +- background-color: $selected_bg_color !important; +- color: $selected_fg_color !important; ++ background-color: $selected_bg_color; ++ color: $selected_fg_color; + box-shadow: none; + & > .system-status-icon { icon-shadow: none; } + } +- &:hover { +- text-shadow: none; +- & .system-status-icon { icon-shadow: none; } +- } ++ + .app-menu-icon { width: 0; height: 0; margin: 0; } // shell's display:none; :D + + .system-status-icon { +-- +2.21.0 + + +From da9d0ffad1b75413027f95c112be9d6a5e92ba78 Mon Sep 17 00:00:00 2001 +From: Jakub Steiner +Date: Mon, 15 Jul 2019 23:03:41 +0200 +Subject: [PATCH 13/33] classic: Update window-list styling + +Make buttons flatter, rounder to match default styling. + +https://gitlab.gnome.org/GNOME/gnome-shell-extensions/merge_requests/82 +--- + extensions/window-list/classic.css | 25 +++++++++---------------- + 1 file changed, 9 insertions(+), 16 deletions(-) + +diff --git a/extensions/window-list/classic.css b/extensions/window-list/classic.css +index c506bea..cc967e0 100644 +--- a/extensions/window-list/classic.css ++++ b/extensions/window-list/classic.css +@@ -4,21 +4,18 @@ + border-top-width: 1px; + border-bottom-width: 0px; + height: 2.25em ; ++ padding: 2px; + } + + .bottom-panel .window-button > StWidget, + .bottom-panel .window-picker-toggle > StWidget { +- background-gradient-drection: vertical; +- background-color: #fff; +- background-gradient-start: #fff; +- background-gradient-end: #eee; +- color: #000; + color: #2e3436; + background-color: #eee; +- border-radius: 2px; ++ border-radius: 3px; + padding: 3px 6px 1px; +- box-shadow: inset -1px -1px 1px rgba(0,0,0,0.5); +- text-shadow: 0 0 transparent; ++ box-shadow: none; ++ text-shadow: none; ++ border: 1px solid rgba(0,0,0,0.2); + } + + .bottom-panel .window-button > StWidget { +@@ -26,10 +23,6 @@ + max-width: 18.75em; + } + +- .bottom-panel .window-picker-toggle > StWidet { +- border: 1px solid rgba(0,0,0,0.3); +- } +- + .bottom-panel .window-button:hover > StWidget, + .bottom-panel .window-picker-toggle:hover > StWidget { + background-color: #f9f9f9; +@@ -37,13 +30,13 @@ + + .bottom-panel .window-button:active > StWidget, + .bottom-panel .window-button:focus > StWidget { +- box-shadow: inset 1px 1px 2px rgba(0,0,0,0.5); ++ box-shadow: inset 0 1px 3px rgba(0,0,0,0.1); + } + + .bottom-panel .window-button.focused > StWidget, + .bottom-panel .window-picker-toggle:checked > StWidget { +- background-color: #ddd; +- box-shadow: inset 1px 1px 1px rgba(0,0,0,0.5); ++ background-color: #ccc; ++ box-shadow: inset 0 1px 3px rgba(0,0,0,0.1); + } + + .bottom-panel .window-button.focused:hover > StWidget { +@@ -52,5 +45,5 @@ + + .bottom-panel .window-button.minimized > StWidget { + color: #888; +- box-shadow: inset -1px -1px 1px rgba(0,0,0,0.5); ++ box-shadow: none; + } +-- +2.21.0 + + +From f9268e6d38e174d78a98f1e64734a00917724032 Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Florian=20M=C3=BCllner?= +Date: Wed, 5 Jun 2019 00:23:13 +0000 +Subject: [PATCH 14/33] window-list: Split out workspaceIndicator + +The extension has grown unwieldily big, so before starting to improve +on the workspace indicator, move it to its own source file. + +https://gitlab.gnome.org/GNOME/gnome-shell-extensions/merge_requests/70 +--- + extensions/window-list/extension.js | 115 +---------------- + extensions/window-list/meson.build | 2 +- + extensions/window-list/workspaceIndicator.js | 127 +++++++++++++++++++ + 3 files changed, 129 insertions(+), 115 deletions(-) + create mode 100644 extensions/window-list/workspaceIndicator.js + +diff --git a/extensions/window-list/extension.js b/extensions/window-list/extension.js +index f4610f9..061421a 100644 +--- a/extensions/window-list/extension.js ++++ b/extensions/window-list/extension.js +@@ -9,7 +9,6 @@ const St = imports.gi.St; + const DND = imports.ui.dnd; + const Main = imports.ui.main; + const Overview = imports.ui.overview; +-const PanelMenu = imports.ui.panelMenu; + const PopupMenu = imports.ui.popupMenu; + const Tweener = imports.ui.tweener; + +@@ -17,6 +16,7 @@ const ExtensionUtils = imports.misc.extensionUtils; + const Me = ExtensionUtils.getCurrentExtension(); + const Convenience = Me.imports.convenience; + const { WindowPicker, WindowPickerToggle } = Me.imports.windowPicker; ++const { WorkspaceIndicator } = Me.imports.workspaceIndicator; + + const Gettext = imports.gettext.domain('gnome-shell-extensions'); + const _ = Gettext.gettext; +@@ -644,119 +644,6 @@ class AppButton extends BaseButton { + }; + + +-class WorkspaceIndicator extends PanelMenu.Button { +- constructor() { +- super(0.0, _("Workspace Indicator"), true); +- this.setMenu(new PopupMenu.PopupMenu(this.actor, 0.0, St.Side.BOTTOM)); +- this.actor.add_style_class_name('window-list-workspace-indicator'); +- this.menu.actor.remove_style_class_name('panel-menu'); +- +- let container = new St.Widget({ layout_manager: new Clutter.BinLayout(), +- x_expand: true, y_expand: true }); +- this.actor.add_actor(container); +- +- this._currentWorkspace = global.screen.get_active_workspace().index(); +- this.statusLabel = new St.Label({ text: this._getStatusText(), +- x_align: Clutter.ActorAlign.CENTER, +- y_align: Clutter.ActorAlign.CENTER }); +- container.add_actor(this.statusLabel); +- +- this.workspacesItems = []; +- +- this._screenSignals = []; +- this._screenSignals.push(global.screen.connect('notify::n-workspaces', +- this._updateMenu.bind(this))); +- this._screenSignals.push(global.screen.connect_after('workspace-switched', +- this._updateIndicator.bind(this))); +- +- this.actor.connect('scroll-event', this._onScrollEvent.bind(this)); +- this._updateMenu(); +- +- this._settings = new Gio.Settings({ schema_id: 'org.gnome.desktop.wm.preferences' }); +- this._settingsChangedId = +- this._settings.connect('changed::workspace-names', +- this._updateMenu.bind(this)); +- } +- +- destroy() { +- for (let i = 0; i < this._screenSignals.length; i++) +- global.screen.disconnect(this._screenSignals[i]); +- +- if (this._settingsChangedId) { +- this._settings.disconnect(this._settingsChangedId); +- this._settingsChangedId = 0; +- } +- +- super.destroy(); +- } +- +- _updateIndicator() { +- this.workspacesItems[this._currentWorkspace].setOrnament(PopupMenu.Ornament.NONE); +- this._currentWorkspace = global.screen.get_active_workspace().index(); +- this.workspacesItems[this._currentWorkspace].setOrnament(PopupMenu.Ornament.DOT); +- +- this.statusLabel.set_text(this._getStatusText()); +- } +- +- _getStatusText() { +- let current = global.screen.get_active_workspace().index(); +- let total = global.screen.n_workspaces; +- +- return '%d / %d'.format(current + 1, total); +- } +- +- _updateMenu() { +- this.menu.removeAll(); +- this.workspacesItems = []; +- this._currentWorkspace = global.screen.get_active_workspace().index(); +- +- for(let i = 0; i < global.screen.n_workspaces; i++) { +- let name = Meta.prefs_get_workspace_name(i); +- let item = new PopupMenu.PopupMenuItem(name); +- item.workspaceId = i; +- +- item.connect('activate', (item, event) => { +- this._activate(item.workspaceId); +- }); +- +- if (i == this._currentWorkspace) +- item.setOrnament(PopupMenu.Ornament.DOT); +- +- this.menu.addMenuItem(item); +- this.workspacesItems[i] = item; +- } +- +- this.statusLabel.set_text(this._getStatusText()); +- } +- +- _activate(index) { +- if(index >= 0 && index < global.screen.n_workspaces) { +- let metaWorkspace = global.screen.get_workspace_by_index(index); +- metaWorkspace.activate(global.get_current_time()); +- } +- } +- +- _onScrollEvent(actor, event) { +- let direction = event.get_scroll_direction(); +- let diff = 0; +- if (direction == Clutter.ScrollDirection.DOWN) { +- diff = 1; +- } else if (direction == Clutter.ScrollDirection.UP) { +- diff = -1; +- } else { +- return; +- } +- +- let newIndex = this._currentWorkspace + diff; +- this._activate(newIndex); +- } +- +- _allocate(actor, box, flags) { +- if (actor.get_n_children() > 0) +- actor.get_first_child().allocate(box, flags); +- } +-}; +- + class WindowList { + constructor(perMonitor, monitor) { + this._perMonitor = perMonitor; +diff --git a/extensions/window-list/meson.build b/extensions/window-list/meson.build +index 5b1f5f5..34d7c3f 100644 +--- a/extensions/window-list/meson.build ++++ b/extensions/window-list/meson.build +@@ -4,7 +4,7 @@ extension_data += configure_file( + configuration: metadata_conf + ) + +-extension_sources += files('prefs.js', 'windowPicker.js') ++extension_sources += files('prefs.js', 'windowPicker.js', 'workspaceIndicator.js') + extension_schemas += files(metadata_conf.get('gschemaname') + '.gschema.xml') + + if classic_mode_enabled +diff --git a/extensions/window-list/workspaceIndicator.js b/extensions/window-list/workspaceIndicator.js +new file mode 100644 +index 0000000..ac4037b +--- /dev/null ++++ b/extensions/window-list/workspaceIndicator.js +@@ -0,0 +1,127 @@ ++/* exported WorkspaceIndicator */ ++const { Clutter, Gio, GObject, Meta, St } = imports.gi; ++ ++const PanelMenu = imports.ui.panelMenu; ++const PopupMenu = imports.ui.popupMenu; ++ ++const Gettext = imports.gettext.domain('gnome-shell-extensions'); ++const _ = Gettext.gettext; ++ ++var WorkspaceIndicator = class WorkspaceIndicator extends PanelMenu.Button { ++ constructor() { ++ super(0.0, _('Workspace Indicator'), true); ++ this.setMenu(new PopupMenu.PopupMenu(this, 0.0, St.Side.BOTTOM)); ++ this.actor.add_style_class_name('window-list-workspace-indicator'); ++ this.menu.actor.remove_style_class_name('panel-menu'); ++ ++ let container = new St.Widget({ ++ layout_manager: new Clutter.BinLayout(), ++ x_expand: true, ++ y_expand: true ++ }); ++ this.actor.add_actor(container); ++ ++ this._currentWorkspace = global.screen.get_active_workspace().index(); ++ this.statusLabel = new St.Label({ ++ text: this._getStatusText(), ++ x_align: Clutter.ActorAlign.CENTER, ++ y_align: Clutter.ActorAlign.CENTER ++ }); ++ container.add_actor(this.statusLabel); ++ ++ this.workspacesItems = []; ++ ++ this._screenSignals = [ ++ global.screen.connect('notify::n-workspaces', ++ this._updateMenu.bind(this)), ++ global.screen.connect_after('workspace-switched', ++ this._updateIndicator.bind(this)) ++ ]; ++ ++ this.actor.connect('scroll-event', this._onScrollEvent.bind(this)); ++ this._updateMenu(); ++ ++ this._settings = new Gio.Settings({ schema_id: 'org.gnome.desktop.wm.preferences' }); ++ this._settingsChangedId = this._settings.connect( ++ 'changed::workspace-names', this._updateMenu.bind(this)); ++ } ++ ++ destroy() { ++ for (let i = 0; i < this._screenSignals.length; i++) ++ global.screen.disconnect(this._screenSignals[i]); ++ ++ if (this._settingsChangedId) { ++ this._settings.disconnect(this._settingsChangedId); ++ this._settingsChangedId = 0; ++ } ++ ++ super.destroy(); ++ } ++ ++ _updateIndicator() { ++ this.workspacesItems[this._currentWorkspace].setOrnament(PopupMenu.Ornament.NONE); ++ this._currentWorkspace = global.screen.get_active_workspace().index(); ++ this.workspacesItems[this._currentWorkspace].setOrnament(PopupMenu.Ornament.DOT); ++ ++ this.statusLabel.set_text(this._getStatusText()); ++ } ++ ++ _getStatusText() { ++ let current = global.screen.get_active_workspace().index(); ++ let total = global.screen.n_workspaces; ++ ++ return '%d / %d'.format(current + 1, total); ++ } ++ ++ _updateMenu() { ++ this.menu.removeAll(); ++ this.workspacesItems = []; ++ this._currentWorkspace = global.screen.get_active_workspace().index(); ++ ++ for (let i = 0; i < global.screen.n_workspaces; i++) { ++ let name = Meta.prefs_get_workspace_name(i); ++ let item = new PopupMenu.PopupMenuItem(name); ++ item.workspaceId = i; ++ ++ item.connect('activate', (item, _event) => { ++ this._activate(item.workspaceId); ++ }); ++ ++ if (i == this._currentWorkspace) ++ item.setOrnament(PopupMenu.Ornament.DOT); ++ ++ this.menu.addMenuItem(item); ++ this.workspacesItems[i] = item; ++ } ++ ++ this.statusLabel.set_text(this._getStatusText()); ++ } ++ ++ _activate(index) { ++ if (index >= 0 && index < global.screen.n_workspaces) { ++ let metaWorkspace = global.screen.get_workspace_by_index(index); ++ metaWorkspace.activate(global.get_current_time()); ++ } ++ } ++ ++ _onScrollEvent(actor, event) { ++ let direction = event.get_scroll_direction(); ++ let diff = 0; ++ if (direction == Clutter.ScrollDirection.DOWN) { ++ diff = 1; ++ } else if (direction == Clutter.ScrollDirection.UP) { ++ diff = -1; ++ } else { ++ return; ++ } ++ ++ let newIndex = this._currentWorkspace + diff; ++ this._activate(newIndex); ++ } ++ ++ _allocate(actor, box, flags) { ++ if (actor.get_n_children() > 0) ++ actor.get_first_child().allocate(box, flags); ++ } ++}; ++ +-- +2.21.0 + + +From aa6079ff69f942420f806523463337fe81b23121 Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Florian=20M=C3=BCllner?= +Date: Wed, 5 Jun 2019 04:54:50 +0200 +Subject: [PATCH 15/33] window-list: Make some properties private + +There's no reason why they should be public. + +https://gitlab.gnome.org/GNOME/gnome-shell-extensions/merge_requests/70 +--- + extensions/window-list/workspaceIndicator.js | 18 +++++++++--------- + 1 file changed, 9 insertions(+), 9 deletions(-) + +diff --git a/extensions/window-list/workspaceIndicator.js b/extensions/window-list/workspaceIndicator.js +index ac4037b..cd3e394 100644 +--- a/extensions/window-list/workspaceIndicator.js ++++ b/extensions/window-list/workspaceIndicator.js +@@ -22,14 +22,14 @@ var WorkspaceIndicator = class WorkspaceIndicator extends PanelMenu.Button { + this.actor.add_actor(container); + + this._currentWorkspace = global.screen.get_active_workspace().index(); +- this.statusLabel = new St.Label({ ++ this._statusLabel = new St.Label({ + text: this._getStatusText(), + x_align: Clutter.ActorAlign.CENTER, + y_align: Clutter.ActorAlign.CENTER + }); +- container.add_actor(this.statusLabel); ++ container.add_actor(this._statusLabel); + +- this.workspacesItems = []; ++ this._workspacesItems = []; + + this._screenSignals = [ + global.screen.connect('notify::n-workspaces', +@@ -59,11 +59,11 @@ var WorkspaceIndicator = class WorkspaceIndicator extends PanelMenu.Button { + } + + _updateIndicator() { +- this.workspacesItems[this._currentWorkspace].setOrnament(PopupMenu.Ornament.NONE); ++ this._workspacesItems[this._currentWorkspace].setOrnament(PopupMenu.Ornament.NONE); + this._currentWorkspace = global.screen.get_active_workspace().index(); +- this.workspacesItems[this._currentWorkspace].setOrnament(PopupMenu.Ornament.DOT); ++ this._workspacesItems[this._currentWorkspace].setOrnament(PopupMenu.Ornament.DOT); + +- this.statusLabel.set_text(this._getStatusText()); ++ this._statusLabel.set_text(this._getStatusText()); + } + + _getStatusText() { +@@ -75,7 +75,7 @@ var WorkspaceIndicator = class WorkspaceIndicator extends PanelMenu.Button { + + _updateMenu() { + this.menu.removeAll(); +- this.workspacesItems = []; ++ this._workspacesItems = []; + this._currentWorkspace = global.screen.get_active_workspace().index(); + + for (let i = 0; i < global.screen.n_workspaces; i++) { +@@ -91,10 +91,10 @@ var WorkspaceIndicator = class WorkspaceIndicator extends PanelMenu.Button { + item.setOrnament(PopupMenu.Ornament.DOT); + + this.menu.addMenuItem(item); +- this.workspacesItems[i] = item; ++ this._workspacesItems[i] = item; + } + +- this.statusLabel.set_text(this._getStatusText()); ++ this._statusLabel.set_text(this._getStatusText()); + } + + _activate(index) { +-- +2.21.0 + + +From 54f5bf585056267f63e2a28153ae709325afe9ca Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Florian=20M=C3=BCllner?= +Date: Wed, 5 Jun 2019 04:57:39 +0200 +Subject: [PATCH 16/33] window-list: Update workspace names in-place + +There's no good reason to rebuild the entire menu on workspace names +changes, we can simply update the labels in-place. + +https://gitlab.gnome.org/GNOME/gnome-shell-extensions/merge_requests/70 +--- + extensions/window-list/workspaceIndicator.js | 10 +++++++++- + 1 file changed, 9 insertions(+), 1 deletion(-) + +diff --git a/extensions/window-list/workspaceIndicator.js b/extensions/window-list/workspaceIndicator.js +index cd3e394..5157e6e 100644 +--- a/extensions/window-list/workspaceIndicator.js ++++ b/extensions/window-list/workspaceIndicator.js +@@ -43,7 +43,7 @@ var WorkspaceIndicator = class WorkspaceIndicator extends PanelMenu.Button { + + this._settings = new Gio.Settings({ schema_id: 'org.gnome.desktop.wm.preferences' }); + this._settingsChangedId = this._settings.connect( +- 'changed::workspace-names', this._updateMenu.bind(this)); ++ 'changed::workspace-names', this._updateMenuLabels.bind(this)); + } + + destroy() { +@@ -73,6 +73,14 @@ var WorkspaceIndicator = class WorkspaceIndicator extends PanelMenu.Button { + return '%d / %d'.format(current + 1, total); + } + ++ _updateMenuLabels() { ++ for (let i = 0; i < this._workspacesItems.length; i++) { ++ let item = this._workspacesItems[i]; ++ let name = Meta.prefs_get_workspace_name(i); ++ item.label.text = name; ++ } ++ } ++ + _updateMenu() { + this.menu.removeAll(); + this._workspacesItems = []; +-- +2.21.0 + + +From a2fb1382b9ac7c50c556c1a41dc19ceca43bd320 Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Florian=20M=C3=BCllner?= +Date: Wed, 5 Jun 2019 04:59:19 +0200 +Subject: [PATCH 17/33] window-list: Minor cleanup + +Mutter has a dedicated method for getting the index of the active +workspace, use that instead of getting first the active workspace +and then its index. + +https://gitlab.gnome.org/GNOME/gnome-shell-extensions/merge_requests/70 +--- + extensions/window-list/workspaceIndicator.js | 8 ++++---- + 1 file changed, 4 insertions(+), 4 deletions(-) + +diff --git a/extensions/window-list/workspaceIndicator.js b/extensions/window-list/workspaceIndicator.js +index 5157e6e..f9778f7 100644 +--- a/extensions/window-list/workspaceIndicator.js ++++ b/extensions/window-list/workspaceIndicator.js +@@ -21,7 +21,7 @@ var WorkspaceIndicator = class WorkspaceIndicator extends PanelMenu.Button { + }); + this.actor.add_actor(container); + +- this._currentWorkspace = global.screen.get_active_workspace().index(); ++ this._currentWorkspace = global.screen.get_active_workspace_index(); + this._statusLabel = new St.Label({ + text: this._getStatusText(), + x_align: Clutter.ActorAlign.CENTER, +@@ -60,14 +60,14 @@ var WorkspaceIndicator = class WorkspaceIndicator extends PanelMenu.Button { + + _updateIndicator() { + this._workspacesItems[this._currentWorkspace].setOrnament(PopupMenu.Ornament.NONE); +- this._currentWorkspace = global.screen.get_active_workspace().index(); ++ this._currentWorkspace = global.screen.get_active_workspace_index(); + this._workspacesItems[this._currentWorkspace].setOrnament(PopupMenu.Ornament.DOT); + + this._statusLabel.set_text(this._getStatusText()); + } + + _getStatusText() { +- let current = global.screen.get_active_workspace().index(); ++ let current = global.screen.get_active_workspace_index(); + let total = global.screen.n_workspaces; + + return '%d / %d'.format(current + 1, total); +@@ -84,7 +84,7 @@ var WorkspaceIndicator = class WorkspaceIndicator extends PanelMenu.Button { + _updateMenu() { + this.menu.removeAll(); + this._workspacesItems = []; +- this._currentWorkspace = global.screen.get_active_workspace().index(); ++ this._currentWorkspace = global.screen.get_active_workspace_index(); + + for (let i = 0; i < global.screen.n_workspaces; i++) { + let name = Meta.prefs_get_workspace_name(i); +-- +2.21.0 + + +From fe93812275cc44f01a4c3877fc5efdb9121da78a Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Florian=20M=C3=BCllner?= +Date: Wed, 5 Jun 2019 05:11:34 +0200 +Subject: [PATCH 18/33] window-list: Improve workspace label styling + +The border currently looks off - it extends all the way vertically +and leaves zero spacing to the label horizontally. Fix both issues +by setting appropriate padding/margins. + +https://gitlab.gnome.org/GNOME/gnome-shell-extensions/merge_requests/70 +--- + extensions/window-list/stylesheet.css | 8 +++----- + extensions/window-list/workspaceIndicator.js | 13 ++++++++----- + 2 files changed, 11 insertions(+), 10 deletions(-) + +diff --git a/extensions/window-list/stylesheet.css b/extensions/window-list/stylesheet.css +index 91383ab..bab8f76 100644 +--- a/extensions/window-list/stylesheet.css ++++ b/extensions/window-list/stylesheet.css +@@ -85,13 +85,11 @@ + height: 24px; + } + +-.window-list-workspace-indicator { +- padding: 3px; +-} +- +-.window-list-workspace-indicator > StWidget { ++.window-list-workspace-indicator .status-label-bin { + background-color: rgba(200, 200, 200, .3); + border: 1px solid #cccccc; ++ padding: 0 3px; ++ margin: 3px 0; + } + + .notification { +diff --git a/extensions/window-list/workspaceIndicator.js b/extensions/window-list/workspaceIndicator.js +index f9778f7..132d621 100644 +--- a/extensions/window-list/workspaceIndicator.js ++++ b/extensions/window-list/workspaceIndicator.js +@@ -22,12 +22,15 @@ var WorkspaceIndicator = class WorkspaceIndicator extends PanelMenu.Button { + this.actor.add_actor(container); + + this._currentWorkspace = global.screen.get_active_workspace_index(); +- this._statusLabel = new St.Label({ +- text: this._getStatusText(), +- x_align: Clutter.ActorAlign.CENTER, +- y_align: Clutter.ActorAlign.CENTER ++ this._statusLabel = new St.Label({ text: this._getStatusText() }); ++ ++ this._statusBin = new St.Bin({ ++ style_class: 'status-label-bin', ++ x_expand: true, ++ y_expand: true, ++ child: this._statusLabel + }); +- container.add_actor(this._statusLabel); ++ container.add_actor(this._statusBin); + + this._workspacesItems = []; + +-- +2.21.0 + + +From 2edb23239c7d3c2120d59cfe74cba36674be515f Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Florian=20M=C3=BCllner?= +Date: Wed, 5 Jun 2019 05:08:31 +0200 +Subject: [PATCH 19/33] window-list: Refactor workspace signal handlers + +We are about to support a separate representation if horizontal +workspaces are used. To prepare for that, rename the handlers to +something more generic and split out menu-specific bits into a +dedicated helper function. + +https://gitlab.gnome.org/GNOME/gnome-shell-extensions/merge_requests/70 +--- + extensions/window-list/workspaceIndicator.js | 22 +++++++++++++++----- + 1 file changed, 17 insertions(+), 5 deletions(-) + +diff --git a/extensions/window-list/workspaceIndicator.js b/extensions/window-list/workspaceIndicator.js +index 132d621..6352763 100644 +--- a/extensions/window-list/workspaceIndicator.js ++++ b/extensions/window-list/workspaceIndicator.js +@@ -36,9 +36,9 @@ var WorkspaceIndicator = class WorkspaceIndicator extends PanelMenu.Button { + + this._screenSignals = [ + global.screen.connect('notify::n-workspaces', +- this._updateMenu.bind(this)), ++ this._nWorkspacesChanged.bind(this)), + global.screen.connect_after('workspace-switched', +- this._updateIndicator.bind(this)) ++ this._onWorkspaceSwitched.bind(this)) + ]; + + this.actor.connect('scroll-event', this._onScrollEvent.bind(this)); +@@ -61,14 +61,26 @@ var WorkspaceIndicator = class WorkspaceIndicator extends PanelMenu.Button { + super.destroy(); + } + +- _updateIndicator() { +- this._workspacesItems[this._currentWorkspace].setOrnament(PopupMenu.Ornament.NONE); ++ _onWorkspaceSwitched() { + this._currentWorkspace = global.screen.get_active_workspace_index(); +- this._workspacesItems[this._currentWorkspace].setOrnament(PopupMenu.Ornament.DOT); ++ ++ this._updateMenuOrnament(); + + this._statusLabel.set_text(this._getStatusText()); + } + ++ _nWorkspacesChanged() { ++ this._updateMenu(); ++ } ++ ++ _updateMenuOrnament() { ++ for (let i = 0; i < this._workspacesItems.length; i++) { ++ this._workspacesItems[i].setOrnament(i == this._currentWorkspace ++ ? PopupMenu.Ornament.DOT ++ : PopupMenu.Ornament.NONE); ++ } ++ } ++ + _getStatusText() { + let current = global.screen.get_active_workspace_index(); + let total = global.screen.n_workspaces; +-- +2.21.0 + + +From 1fa782c8ab1d3e69f99cbe0a68110a557d828c78 Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Florian=20M=C3=BCllner?= +Date: Wed, 5 Jun 2019 02:53:38 +0000 +Subject: [PATCH 20/33] window-list: Support horizontal workspace layout + +Unlike in GNOME 2, the workspace indicator we display in the window list +isn't a workspace switcher, but a menu button that allows switching +workspaces via its menu. The reason for that is that a horizontal +in-place switcher would be at odds with the vertical workspace layout +used in GNOME 3. + +However that reasoning doesn't apply when the layout is changed to a +horizontal one, so replace the button with a traditional workspace +switcher in that case. + +https://gitlab.gnome.org/GNOME/gnome-shell-extensions/merge_requests/70 +--- + extensions/window-list/classic.css | 9 +++ + extensions/window-list/stylesheet.css | 29 +++++++++ + extensions/window-list/workspaceIndicator.js | 63 +++++++++++++++++++- + 3 files changed, 100 insertions(+), 1 deletion(-) + +diff --git a/extensions/window-list/classic.css b/extensions/window-list/classic.css +index cc967e0..c533473 100644 +--- a/extensions/window-list/classic.css ++++ b/extensions/window-list/classic.css +@@ -47,3 +47,12 @@ + color: #888; + box-shadow: none; + } ++ ++/* workspace switcher */ ++.window-list-workspace-indicator .workspace { ++ background-color: #ddd; ++} ++ ++.window-list-workspace-indicator .workspace.active { ++ background-color: #ccc; ++} +diff --git a/extensions/window-list/stylesheet.css b/extensions/window-list/stylesheet.css +index bab8f76..ad5978a 100644 +--- a/extensions/window-list/stylesheet.css ++++ b/extensions/window-list/stylesheet.css +@@ -92,6 +92,35 @@ + margin: 3px 0; + } + ++.window-list-workspace-indicator .workspaces-box { ++ spacing: 3px; ++ padding: 3px; ++} ++ ++.window-list-workspace-indicator .workspace { ++ border: 1px solid #cccccc; ++ width: 52px; ++} ++ ++.window-list-workspace-indicator .workspace:first-child:last-child:ltr, ++.window-list-workspace-indicator .workspace:first-child:last-child:rtl { ++ border-radius: 4px; ++} ++ ++.window-list-workspace-indicator .workspace:first-child:ltr, ++.window-list-workspace-indicator .workspace:last-child:rtl { ++ border-radius: 4px 0 0 4px; ++} ++ ++.window-list-workspace-indicator .workspace:first-child:rtl, ++.window-list-workspace-indicator .workspace:last-child:ltr { ++ border-radius: 0 4px 4px 0; ++} ++ ++.window-list-workspace-indicator .workspace.active { ++ background-color: rgba(200, 200, 200, .3); ++} ++ + .notification { + font-weight: normal; + } +diff --git a/extensions/window-list/workspaceIndicator.js b/extensions/window-list/workspaceIndicator.js +index 6352763..77acf8a 100644 +--- a/extensions/window-list/workspaceIndicator.js ++++ b/extensions/window-list/workspaceIndicator.js +@@ -7,6 +7,24 @@ const PopupMenu = imports.ui.popupMenu; + const Gettext = imports.gettext.domain('gnome-shell-extensions'); + const _ = Gettext.gettext; + ++let WorkspaceThumbnail = GObject.registerClass({ ++ GTypeName: 'WindowListWorkspaceThumbnail' ++}, class WorkspaceThumbnail extends St.Button { ++ _init(index) { ++ super._init({ ++ style_class: 'workspace' ++ }); ++ ++ this._index = index; ++ } ++ ++ on_clicked() { ++ let ws = global.screen.get_workspace_by_index(this._index); ++ if (ws) ++ ws.activate(global.get_current_time()); ++ } ++}); ++ + var WorkspaceIndicator = class WorkspaceIndicator extends PanelMenu.Button { + constructor() { + super(0.0, _('Workspace Indicator'), true); +@@ -32,17 +50,30 @@ var WorkspaceIndicator = class WorkspaceIndicator extends PanelMenu.Button { + }); + container.add_actor(this._statusBin); + ++ this._thumbnailsBox = new St.BoxLayout({ ++ style_class: 'workspaces-box', ++ y_expand: true, ++ reactive: true ++ }); ++ this._thumbnailsBox.connect('scroll-event', ++ this._onScrollEvent.bind(this)); ++ container.add_actor(this._thumbnailsBox); ++ + this._workspacesItems = []; + + this._screenSignals = [ + global.screen.connect('notify::n-workspaces', + this._nWorkspacesChanged.bind(this)), + global.screen.connect_after('workspace-switched', +- this._onWorkspaceSwitched.bind(this)) ++ this._onWorkspaceSwitched.bind(this)), ++ global.screen.connect('notify::layout-rows', ++ this._onWorkspaceOrientationChanged.bind(this)) + ]; + + this.actor.connect('scroll-event', this._onScrollEvent.bind(this)); + this._updateMenu(); ++ this._updateThumbnails(); ++ this._onWorkspaceOrientationChanged(); + + this._settings = new Gio.Settings({ schema_id: 'org.gnome.desktop.wm.preferences' }); + this._settingsChangedId = this._settings.connect( +@@ -61,16 +92,26 @@ var WorkspaceIndicator = class WorkspaceIndicator extends PanelMenu.Button { + super.destroy(); + } + ++ _onWorkspaceOrientationChanged() { ++ let vertical = global.screen.layout_rows == -1; ++ this.reactive = vertical; ++ ++ this._statusBin.visible = vertical; ++ this._thumbnailsBox.visible = !vertical; ++ } ++ + _onWorkspaceSwitched() { + this._currentWorkspace = global.screen.get_active_workspace_index(); + + this._updateMenuOrnament(); ++ this._updateActiveThumbnail(); + + this._statusLabel.set_text(this._getStatusText()); + } + + _nWorkspacesChanged() { + this._updateMenu(); ++ this._updateThumbnails(); + } + + _updateMenuOrnament() { +@@ -81,6 +122,16 @@ var WorkspaceIndicator = class WorkspaceIndicator extends PanelMenu.Button { + } + } + ++ _updateActiveThumbnail() { ++ let thumbs = this._thumbnailsBox.get_children(); ++ for (let i = 0; i < thumbs.length; i++) { ++ if (i == this._currentWorkspace) ++ thumbs[i].add_style_class_name('active'); ++ else ++ thumbs[i].remove_style_class_name('active'); ++ } ++ } ++ + _getStatusText() { + let current = global.screen.get_active_workspace_index(); + let total = global.screen.n_workspaces; +@@ -120,6 +171,16 @@ var WorkspaceIndicator = class WorkspaceIndicator extends PanelMenu.Button { + this._statusLabel.set_text(this._getStatusText()); + } + ++ _updateThumbnails() { ++ this._thumbnailsBox.destroy_all_children(); ++ ++ for (let i = 0; i < global.screen.n_workspaces; i++) { ++ let thumb = new WorkspaceThumbnail(i); ++ this._thumbnailsBox.add_actor(thumb); ++ } ++ this._updateActiveThumbnail(); ++ } ++ + _activate(index) { + if (index >= 0 && index < global.screen.n_workspaces) { + let metaWorkspace = global.screen.get_workspace_by_index(index); +-- +2.21.0 + + +From 8ab04d7d1fe9807cdde949afa743de97539769cc Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Florian=20M=C3=BCllner?= +Date: Tue, 11 Jun 2019 23:01:20 +0000 +Subject: [PATCH 21/33] window-list: Turn workspace thumbs into drop targets + +It makes some sense to allow using the workspace indicator for moving +windows between workspaces as well as for workspace switching. This +applies particularly in GNOME classic after we disabled the overview +there, so that there is again a non-shortcut way of moving windows +between workspaces. + +https://gitlab.gnome.org/GNOME/gnome-shell-extensions/merge_requests/74 +--- + extensions/window-list/workspaceIndicator.js | 27 ++++++++++++++++++++ + 1 file changed, 27 insertions(+) + +diff --git a/extensions/window-list/workspaceIndicator.js b/extensions/window-list/workspaceIndicator.js +index 77acf8a..edf176e 100644 +--- a/extensions/window-list/workspaceIndicator.js ++++ b/extensions/window-list/workspaceIndicator.js +@@ -1,6 +1,8 @@ + /* exported WorkspaceIndicator */ + const { Clutter, Gio, GObject, Meta, St } = imports.gi; + ++const DND = imports.ui.dnd; ++const Main = imports.ui.main; + const PanelMenu = imports.ui.panelMenu; + const PopupMenu = imports.ui.popupMenu; + +@@ -16,6 +18,31 @@ let WorkspaceThumbnail = GObject.registerClass({ + }); + + this._index = index; ++ this._delegate = this; // needed for DND ++ } ++ ++ acceptDrop(source) { ++ if (!source.realWindow) ++ return false; ++ ++ let window = source.realWindow.get_meta_window(); ++ this._moveWindow(window); ++ return true; ++ } ++ ++ handleDragOver(source) { ++ if (source.realWindow) ++ return DND.DragMotionResult.MOVE_DROP; ++ else ++ return DND.DragMotionResult.CONTINUE; ++ } ++ ++ ++ _moveWindow(window) { ++ let monitorIndex = Main.layoutManager.findIndexForActor(this); ++ if (monitorIndex != window.get_monitor()) ++ window.move_to_monitor(monitorIndex); ++ window.change_workspace_by_index(this._index, false); + } + + on_clicked() { +-- +2.21.0 + + +From a4d0ccc3122891f9e164b2b45c9540dbdebba0de Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Florian=20M=C3=BCllner?= +Date: Wed, 26 Jun 2019 23:55:58 +0000 +Subject: [PATCH 22/33] window-list: Show previews in workspace switcher + +Currently the new horizontal workspace switcher only shows a series of +buttons, with no indication of the workspaces' contents. Go full GNOME 2 +and add tiny draggable preview rectangles that represent the windows +on a particular workspace. + +https://gitlab.gnome.org/GNOME/gnome-shell-extensions/merge_requests/74 +--- + extensions/window-list/classic.css | 10 ++ + extensions/window-list/stylesheet.css | 10 ++ + extensions/window-list/workspaceIndicator.js | 153 ++++++++++++++++++- + 3 files changed, 172 insertions(+), 1 deletion(-) + +diff --git a/extensions/window-list/classic.css b/extensions/window-list/classic.css +index c533473..7079d3e 100644 +--- a/extensions/window-list/classic.css ++++ b/extensions/window-list/classic.css +@@ -56,3 +56,13 @@ + .window-list-workspace-indicator .workspace.active { + background-color: #ccc; + } ++ ++.window-list-window-preview { ++ background-color: #ededed; ++ border: 1px solid #ccc; ++} ++ ++.window-list-window-preview.active { ++ background-color: #f6f5f4; ++ border: 2px solid #888; ++} +diff --git a/extensions/window-list/stylesheet.css b/extensions/window-list/stylesheet.css +index ad5978a..79d56ba 100644 +--- a/extensions/window-list/stylesheet.css ++++ b/extensions/window-list/stylesheet.css +@@ -121,6 +121,16 @@ + background-color: rgba(200, 200, 200, .3); + } + ++.window-list-window-preview { ++ background-color: #252525; ++ border: 1px solid #ccc; ++} ++ ++.window-list-window-preview.active { ++ background-color: #353535; ++ border: 2px solid #ccc; ++} ++ + .notification { + font-weight: normal; + } +diff --git a/extensions/window-list/workspaceIndicator.js b/extensions/window-list/workspaceIndicator.js +index edf176e..0bcee80 100644 +--- a/extensions/window-list/workspaceIndicator.js ++++ b/extensions/window-list/workspaceIndicator.js +@@ -9,16 +9,130 @@ const PopupMenu = imports.ui.popupMenu; + const Gettext = imports.gettext.domain('gnome-shell-extensions'); + const _ = Gettext.gettext; + ++let WindowPreview = GObject.registerClass({ ++ GTypeName: 'WindowListWindowPreview' ++}, class WindowPreview extends St.Button { ++ _init(window) { ++ super._init({ ++ style_class: 'window-list-window-preview' ++ }); ++ ++ this._delegate = this; ++ DND.makeDraggable(this, { restoreOnSuccess: true }); ++ ++ this._window = window; ++ ++ this.connect('destroy', this._onDestroy.bind(this)); ++ ++ this._sizeChangedId = this._window.connect('size-changed', ++ this._relayout.bind(this)); ++ this._positionChangedId = this._window.connect('position-changed', ++ this._relayout.bind(this)); ++ this._minimizedChangedId = this._window.connect('notify::minimized', ++ this._relayout.bind(this)); ++ this._monitorEnteredId = global.screen.connect('window-entered-monitor', ++ this._relayout.bind(this)); ++ this._monitorLeftId = global.screen.connect('window-left-monitor', ++ this._relayout.bind(this)); ++ ++ // Do initial layout when we get a parent ++ let id = this.connect('parent-set', () => { ++ this.disconnect(id); ++ if (!this.get_parent()) ++ return; ++ this._laterId = Meta.later_add(Meta.LaterType.BEFORE_REDRAW, () => { ++ this._laterId = 0; ++ this._relayout(); ++ return false; ++ }); ++ }); ++ ++ this._focusChangedId = global.display.connect('notify::focus-window', ++ this._onFocusChanged.bind(this)); ++ this._onFocusChanged(); ++ } ++ ++ // needed for DND ++ get realWindow() { ++ return this._window.get_compositor_private(); ++ } ++ ++ _onDestroy() { ++ this._window.disconnect(this._sizeChangedId); ++ this._window.disconnect(this._positionChangedId); ++ this._window.disconnect(this._minimizedChangedId); ++ global.screen.disconnect(this._monitorEnteredId); ++ global.screen.disconnect(this._monitorLeftId); ++ global.display.disconnect(this._focusChangedId); ++ if (this._laterId) ++ Meta.later_remove(this._laterId); ++ } ++ ++ _onFocusChanged() { ++ if (global.display.focus_window == this._window) ++ this.add_style_class_name('active'); ++ else ++ this.remove_style_class_name('active'); ++ } ++ ++ _relayout() { ++ let monitor = Main.layoutManager.findIndexForActor(this); ++ this.visible = monitor == this._window.get_monitor() && ++ this._window.showing_on_its_workspace(); ++ ++ if (!this.visible) ++ return; ++ ++ let workArea = Main.layoutManager.getWorkAreaForMonitor(monitor); ++ let hscale = this.get_parent().allocation.get_width() / workArea.width; ++ let vscale = this.get_parent().allocation.get_height() / workArea.height; ++ ++ let frameRect = this._window.get_frame_rect(); ++ this.set_size( ++ Math.round(Math.min(frameRect.width, workArea.width) * hscale), ++ Math.round(Math.min(frameRect.height, workArea.height) * vscale)); ++ this.set_position( ++ Math.round(frameRect.x * hscale), ++ Math.round(frameRect.y * vscale)); ++ } ++}); ++ + let WorkspaceThumbnail = GObject.registerClass({ + GTypeName: 'WindowListWorkspaceThumbnail' + }, class WorkspaceThumbnail extends St.Button { + _init(index) { + super._init({ +- style_class: 'workspace' ++ style_class: 'workspace', ++ child: new Clutter.Actor({ ++ layout_manager: new Clutter.BinLayout(), ++ clip_to_allocation: true ++ }), ++ x_fill: true, ++ y_fill: true + }); + ++ this.connect('destroy', this._onDestroy.bind(this)); ++ + this._index = index; + this._delegate = this; // needed for DND ++ ++ this._windowPreviews = new Map(); ++ ++ this._workspace = global.screen.get_workspace_by_index(index); ++ ++ this._windowAddedId = this._workspace.connect('window-added', ++ (ws, window) => { ++ this._addWindow(window); ++ }); ++ this._windowRemovedId = this._workspace.connect('window-removed', ++ (ws, window) => { ++ this._removeWindow(window); ++ }); ++ this._restackedId = global.screen.connect('restacked', ++ this._onRestacked.bind(this)); ++ ++ this._workspace.list_windows().forEach(w => this._addWindow(w)); ++ this._onRestacked(); + } + + acceptDrop(source) { +@@ -37,6 +151,37 @@ let WorkspaceThumbnail = GObject.registerClass({ + return DND.DragMotionResult.CONTINUE; + } + ++ _addWindow(window) { ++ if (this._windowPreviews.has(window)) ++ return; ++ ++ let preview = new WindowPreview(window); ++ preview.connect('clicked', (a, btn) => this.emit('clicked', btn)); ++ this._windowPreviews.set(window, preview); ++ this.child.add_child(preview); ++ } ++ ++ _removeWindow(window) { ++ let preview = this._windowPreviews.get(window); ++ if (!preview) ++ return; ++ ++ this._windowPreviews.delete(window); ++ preview.destroy(); ++ } ++ ++ _onRestacked() { ++ let lastPreview = null; ++ let windows = global.get_window_actors().map(a => a.meta_window); ++ for (let i = 0; i < windows.length; i++) { ++ let preview = this._windowPreviews.get(windows[i]); ++ if (!preview) ++ continue; ++ ++ this.child.set_child_above_sibling(preview, lastPreview); ++ lastPreview = preview; ++ } ++ } + + _moveWindow(window) { + let monitorIndex = Main.layoutManager.findIndexForActor(this); +@@ -50,6 +195,12 @@ let WorkspaceThumbnail = GObject.registerClass({ + if (ws) + ws.activate(global.get_current_time()); + } ++ ++ _onDestroy() { ++ this._workspace.disconnect(this._windowAddedId); ++ this._workspace.disconnect(this._windowRemovedId); ++ global.screen.disconnect(this._restackedId); ++ } + }); + + var WorkspaceIndicator = class WorkspaceIndicator extends PanelMenu.Button { +-- +2.21.0 + + +From 73e31ec6a601b2f692cf58b5377a1d92fce8d5ca Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Florian=20M=C3=BCllner?= +Date: Wed, 5 Jun 2019 03:31:16 +0000 +Subject: [PATCH 23/33] classic: Add 'horizontal-workspaces' extension + +Vertical workspaces are another defining characteristics of GNOME 3, +and thus rather un-classic. That switch was driven by the overall +layout of the overview, and now that we disable the overview in +GNOME Classic, we can just return to the traditional workspace +layout as well. + +Add a small extension that does just that. + +https://gitlab.gnome.org/GNOME/gnome-shell-extensions/merge_requests/72 +--- + extensions/horizontal-workspaces/extension.js | 38 +++++++++++++++++++ + extensions/horizontal-workspaces/meson.build | 5 +++ + .../horizontal-workspaces/metadata.json.in | 10 +++++ + .../horizontal-workspaces/stylesheet.css | 1 + + meson.build | 1 + + 5 files changed, 55 insertions(+) + create mode 100644 extensions/horizontal-workspaces/extension.js + create mode 100644 extensions/horizontal-workspaces/meson.build + create mode 100644 extensions/horizontal-workspaces/metadata.json.in + create mode 100644 extensions/horizontal-workspaces/stylesheet.css + +diff --git a/extensions/horizontal-workspaces/extension.js b/extensions/horizontal-workspaces/extension.js +new file mode 100644 +index 0000000..bade48b +--- /dev/null ++++ b/extensions/horizontal-workspaces/extension.js +@@ -0,0 +1,38 @@ ++/* exported init */ ++const { Meta } = imports.gi; ++ ++const { ThumbnailsBox } = imports.ui.workspaceThumbnail; ++ ++class Extension { ++ constructor() { ++ this._origUpdateSwitcherVisibility = ++ ThumbnailsBox.prototype._updateSwitcherVisibility; ++ } ++ ++ enable() { ++ global.screen.override_workspace_layout( ++ Meta.ScreenCorner.TOPLEFT, ++ false, ++ 1, ++ -1); ++ ++ ThumbnailsBox.prototype._updateSwitcherVisibility = function() { ++ this.hide(); ++ }; ++ } ++ ++ disable() { ++ global.screen.override_workspace_layout( ++ Meta.ScreenCorner.TOPLEFT, ++ false, ++ -1, ++ 1); ++ ++ ThumbnailsBox.prototype._updateSwitcherVisibility = ++ this._origUpdateSwitcherVisibility; ++ } ++} ++ ++function init() { ++ return new Extension(); ++} +diff --git a/extensions/horizontal-workspaces/meson.build b/extensions/horizontal-workspaces/meson.build +new file mode 100644 +index 0000000..48504f6 +--- /dev/null ++++ b/extensions/horizontal-workspaces/meson.build +@@ -0,0 +1,5 @@ ++extension_data += configure_file( ++ input: metadata_name + '.in', ++ output: metadata_name, ++ configuration: metadata_conf ++) +diff --git a/extensions/horizontal-workspaces/metadata.json.in b/extensions/horizontal-workspaces/metadata.json.in +new file mode 100644 +index 0000000..f109e06 +--- /dev/null ++++ b/extensions/horizontal-workspaces/metadata.json.in +@@ -0,0 +1,10 @@ ++{ ++"extension-id": "@extension_id@", ++"uuid": "@uuid@", ++"settings-schema": "@gschemaname@", ++"gettext-domain": "@gettext_domain@", ++"name": "Horizontal workspaces", ++"description": "Use a horizontal workspace layout", ++"shell-version": [ "@shell_current@" ], ++"url": "@url@" ++} +diff --git a/extensions/horizontal-workspaces/stylesheet.css b/extensions/horizontal-workspaces/stylesheet.css +new file mode 100644 +index 0000000..25134b6 +--- /dev/null ++++ b/extensions/horizontal-workspaces/stylesheet.css +@@ -0,0 +1 @@ ++/* This extensions requires no special styling */ +diff --git a/meson.build b/meson.build +index fa4aa9c..c1581a5 100644 +--- a/meson.build ++++ b/meson.build +@@ -36,6 +36,7 @@ uuid_suffix = '@gnome-shell-extensions.gcampax.github.com' + classic_extensions = [ + 'alternate-tab', + 'apps-menu', ++ 'horizontal-workspaces', + 'places-menu', + 'launch-new-instance', + 'top-icons', +-- +2.21.0 + + +From 1269e9226cf93903957acc58029f4db3e095d5ca Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Florian=20M=C3=BCllner?= +Date: Sat, 29 Jun 2019 01:24:54 +0200 +Subject: [PATCH 24/33] workspace-indicator: Fix whitespace error + +We only want a single space before and after operators, not at least +one. Unfortunately eslint only enforces the latter ... + +https://gitlab.gnome.org/GNOME/gnome-shell-extensions/merge_requests/71 +--- + extensions/workspace-indicator/extension.js | 2 +- + 1 file changed, 1 insertion(+), 1 deletion(-) + +diff --git a/extensions/workspace-indicator/extension.js b/extensions/workspace-indicator/extension.js +index ace1703..f6d9912 100644 +--- a/extensions/workspace-indicator/extension.js ++++ b/extensions/workspace-indicator/extension.js +@@ -106,7 +106,7 @@ class WorkspaceIndicator extends PanelMenu.Button { + } + + _activate(index) { +- if(index >= 0 && index < global.screen.n_workspaces) { ++ if(index >= 0 && index < global.screen.n_workspaces) { + let metaWorkspace = global.screen.get_workspace_by_index(index); + metaWorkspace.activate(global.get_current_time()); + } +-- +2.21.0 + + +From fee1399f6862ca99ed063b795fc3014b0d18414f Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Florian=20M=C3=BCllner?= +Date: Sun, 9 Jun 2019 22:58:29 +0000 +Subject: [PATCH 25/33] workspace-indicator: Make some properties private + +There's no reason why they should be public. + +https://gitlab.gnome.org/GNOME/gnome-shell-extensions/merge_requests/71 +--- + extensions/workspace-indicator/extension.js | 35 +++++++++++---------- + 1 file changed, 18 insertions(+), 17 deletions(-) + +diff --git a/extensions/workspace-indicator/extension.js b/extensions/workspace-indicator/extension.js +index f6d9912..67dec14 100644 +--- a/extensions/workspace-indicator/extension.js ++++ b/extensions/workspace-indicator/extension.js +@@ -26,12 +26,14 @@ class WorkspaceIndicator extends PanelMenu.Button { + super(0.0, _("Workspace Indicator")); + + this._currentWorkspace = global.screen.get_active_workspace().index(); +- this.statusLabel = new St.Label({ y_align: Clutter.ActorAlign.CENTER, +- text: this._labelText() }); ++ this._statusLabel = new St.Label({ ++ y_align: Clutter.ActorAlign.CENTER, ++ text: this._labelText() ++ }); + +- this.actor.add_actor(this.statusLabel); ++ this.add_actor(this._statusLabel); + +- this.workspacesItems = []; ++ this._workspacesItems = []; + this._workspaceSection = new PopupMenu.PopupMenuSection(); + this.menu.addMenuItem(this._workspaceSection); + +@@ -46,7 +48,7 @@ class WorkspaceIndicator extends PanelMenu.Button { + this._createWorkspacesSection(); + + //styling +- this.statusLabel.add_style_class_name('panel-workspace-indicator'); ++ this._statusLabel.add_style_class_name('panel-workspace-indicator'); + + this._settings = new Gio.Settings({ schema_id: WORKSPACE_SCHEMA }); + this._settingsChangedId = +@@ -67,11 +69,11 @@ class WorkspaceIndicator extends PanelMenu.Button { + } + + _updateIndicator() { +- this.workspacesItems[this._currentWorkspace].setOrnament(PopupMenu.Ornament.NONE); ++ this._workspacesItems[this._currentWorkspace].setOrnament(PopupMenu.Ornament.NONE); + this._currentWorkspace = global.screen.get_active_workspace().index(); +- this.workspacesItems[this._currentWorkspace].setOrnament(PopupMenu.Ornament.DOT); ++ this._workspacesItems[this._currentWorkspace].setOrnament(PopupMenu.Ornament.DOT); + +- this.statusLabel.set_text(this._labelText()); ++ this._statusLabel.set_text(this._labelText()); + } + + _labelText(workspaceIndex) { +@@ -84,25 +86,24 @@ class WorkspaceIndicator extends PanelMenu.Button { + + _createWorkspacesSection() { + this._workspaceSection.removeAll(); +- this.workspacesItems = []; ++ this._workspacesItems = []; + this._currentWorkspace = global.screen.get_active_workspace().index(); + + let i = 0; + for(; i < global.screen.n_workspaces; i++) { +- this.workspacesItems[i] = new PopupMenu.PopupMenuItem(this._labelText(i)); +- this._workspaceSection.addMenuItem(this.workspacesItems[i]); +- this.workspacesItems[i].workspaceId = i; +- this.workspacesItems[i].label_actor = this.statusLabel; +- let self = this; +- this.workspacesItems[i].connect('activate', (actor, event) => { ++ this._workspacesItems[i] = new PopupMenu.PopupMenuItem(this._labelText(i)); ++ this._workspaceSection.addMenuItem(this._workspacesItems[i]); ++ this._workspacesItems[i].workspaceId = i; ++ this._workspacesItems[i].label_actor = this._statusLabel; ++ this._workspacesItems[i].connect('activate', (actor, _event) => { + this._activate(actor.workspaceId); + }); + + if (i == this._currentWorkspace) +- this.workspacesItems[i].setOrnament(PopupMenu.Ornament.DOT); ++ this._workspacesItems[i].setOrnament(PopupMenu.Ornament.DOT); + } + +- this.statusLabel.set_text(this._labelText()); ++ this._statusLabel.set_text(this._labelText()); + } + + _activate(index) { +-- +2.21.0 + + +From 9f8beea12da86dfa0540fc022ef36de0fa67a932 Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Florian=20M=C3=BCllner?= +Date: Sun, 9 Jun 2019 23:03:55 +0000 +Subject: [PATCH 26/33] workspace-indicator: Update workspace names in-place + +There's no good reason to rebuild the entire menu on workspace names +changes, we can simply update the labels in-place. + +https://gitlab.gnome.org/GNOME/gnome-shell-extensions/merge_requests/71 +--- + extensions/workspace-indicator/extension.js | 11 ++++++++--- + 1 file changed, 8 insertions(+), 3 deletions(-) + +diff --git a/extensions/workspace-indicator/extension.js b/extensions/workspace-indicator/extension.js +index 67dec14..3befe14 100644 +--- a/extensions/workspace-indicator/extension.js ++++ b/extensions/workspace-indicator/extension.js +@@ -51,9 +51,9 @@ class WorkspaceIndicator extends PanelMenu.Button { + this._statusLabel.add_style_class_name('panel-workspace-indicator'); + + this._settings = new Gio.Settings({ schema_id: WORKSPACE_SCHEMA }); +- this._settingsChangedId = +- this._settings.connect('changed::' + WORKSPACE_KEY, +- this._createWorkspacesSection.bind(this)); ++ this._settingsChangedId = this._settings.connect( ++ `changed::${WORKSPACE_KEY}`, ++ this._updateMenuLabels.bind(this)); + } + + destroy() { +@@ -84,6 +84,11 @@ class WorkspaceIndicator extends PanelMenu.Button { + return Meta.prefs_get_workspace_name(workspaceIndex); + } + ++ _updateMenuLabels() { ++ for (let i = 0; i < this._workspacesItems.length; i++) ++ this._workspacesItems[i].label.text = this._labelText(i); ++ } ++ + _createWorkspacesSection() { + this._workspaceSection.removeAll(); + this._workspacesItems = []; +-- +2.21.0 + + +From 2d2baa3981a3d061c7b32d57ad07da9a3553dfa3 Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Florian=20M=C3=BCllner?= +Date: Sun, 9 Jun 2019 23:05:00 +0000 +Subject: [PATCH 27/33] workspace-indicator: Minor cleanup + +Mutter has a dedicated method for getting the index of the active +workspace, use that instead of getting first the active workspace +and then its index. + +https://gitlab.gnome.org/GNOME/gnome-shell-extensions/merge_requests/71 +--- + extensions/workspace-indicator/extension.js | 8 ++++---- + 1 file changed, 4 insertions(+), 4 deletions(-) + +diff --git a/extensions/workspace-indicator/extension.js b/extensions/workspace-indicator/extension.js +index 3befe14..d341b93 100644 +--- a/extensions/workspace-indicator/extension.js ++++ b/extensions/workspace-indicator/extension.js +@@ -25,7 +25,7 @@ class WorkspaceIndicator extends PanelMenu.Button { + constructor() { + super(0.0, _("Workspace Indicator")); + +- this._currentWorkspace = global.screen.get_active_workspace().index(); ++ this._currentWorkspace = global.screen.get_active_workspace_index(); + this._statusLabel = new St.Label({ + y_align: Clutter.ActorAlign.CENTER, + text: this._labelText() +@@ -70,7 +70,7 @@ class WorkspaceIndicator extends PanelMenu.Button { + + _updateIndicator() { + this._workspacesItems[this._currentWorkspace].setOrnament(PopupMenu.Ornament.NONE); +- this._currentWorkspace = global.screen.get_active_workspace().index(); ++ this._currentWorkspace = global.screen.get_active_workspace_index(); + this._workspacesItems[this._currentWorkspace].setOrnament(PopupMenu.Ornament.DOT); + + this._statusLabel.set_text(this._labelText()); +@@ -92,7 +92,7 @@ class WorkspaceIndicator extends PanelMenu.Button { + _createWorkspacesSection() { + this._workspaceSection.removeAll(); + this._workspacesItems = []; +- this._currentWorkspace = global.screen.get_active_workspace().index(); ++ this._currentWorkspace = global.screen.get_active_workspace_index(); + + let i = 0; + for(; i < global.screen.n_workspaces; i++) { +@@ -129,7 +129,7 @@ class WorkspaceIndicator extends PanelMenu.Button { + return; + } + +- let newIndex = global.screen.get_active_workspace().index() + diff; ++ let newIndex = global.screen.get_active_workspace_index() + diff; + this._activate(newIndex); + } + }; +-- +2.21.0 + + +From 7c3dfb2c2d5713f84a11d59dba9e34e50a51e204 Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Florian=20M=C3=BCllner?= +Date: Sun, 9 Jun 2019 23:09:12 +0000 +Subject: [PATCH 28/33] workspace-indicator: Refactor workspace signal handlers + +We are about to support a separate representation if horizontal +workspaces are used. To prepare for that, rename the handlers to +something more generic and split out menu-specific bits into a +dedicated helper function. + +https://gitlab.gnome.org/GNOME/gnome-shell-extensions/merge_requests/71 +--- + extensions/workspace-indicator/extension.js | 30 ++++++++++++++------- + 1 file changed, 21 insertions(+), 9 deletions(-) + +diff --git a/extensions/workspace-indicator/extension.js b/extensions/workspace-indicator/extension.js +index d341b93..58ac865 100644 +--- a/extensions/workspace-indicator/extension.js ++++ b/extensions/workspace-indicator/extension.js +@@ -37,12 +37,12 @@ class WorkspaceIndicator extends PanelMenu.Button { + this._workspaceSection = new PopupMenu.PopupMenuSection(); + this.menu.addMenuItem(this._workspaceSection); + +- this._screenSignals = []; +- this._screenSignals.push(global.screen.connect_after('workspace-added', this._createWorkspacesSection.bind(this))); +- this._screenSignals.push(global.screen.connect_after('workspace-removed', +- this._createWorkspacesSection.bind(this))); +- this._screenSignals.push(global.screen.connect_after('workspace-switched', +- this._updateIndicator.bind(this))); ++ this._screenSignals = [ ++ global.screen.connect_after('notify::n-workspaces', ++ this._nWorkspacesChanged.bind(this)), ++ global.screen.connect_after('workspace-switched', ++ this._onWorkspaceSwitched.bind(this)) ++ ]; + + this.actor.connect('scroll-event', this._onScrollEvent.bind(this)); + this._createWorkspacesSection(); +@@ -68,14 +68,26 @@ class WorkspaceIndicator extends PanelMenu.Button { + super.destroy(); + } + +- _updateIndicator() { +- this._workspacesItems[this._currentWorkspace].setOrnament(PopupMenu.Ornament.NONE); ++ _onWorkspaceSwitched() { + this._currentWorkspace = global.screen.get_active_workspace_index(); +- this._workspacesItems[this._currentWorkspace].setOrnament(PopupMenu.Ornament.DOT); ++ ++ this._updateMenuOrnament(); + + this._statusLabel.set_text(this._labelText()); + } + ++ _nWorkspacesChanged() { ++ this._createWorkspacesSection(); ++ } ++ ++ _updateMenuOrnament() { ++ for (let i = 0; i < this._workspacesItems.length; i++) { ++ this._workspacesItems[i].setOrnament(i == this._currentWorkspace ++ ? PopupMenu.Ornament.DOT ++ : PopupMenu.Ornament.NONE); ++ } ++ } ++ + _labelText(workspaceIndex) { + if(workspaceIndex == undefined) { + workspaceIndex = this._currentWorkspace; +-- +2.21.0 + + +From e5b8c870ff750973d615eb801e15b56ad8f614db Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Florian=20M=C3=BCllner?= +Date: Sun, 9 Jun 2019 23:17:35 +0000 +Subject: [PATCH 29/33] workspace-indicator: Minor cleanup + +Pass the style class at construction time instead of setting it later. + +https://gitlab.gnome.org/GNOME/gnome-shell-extensions/merge_requests/71 +--- + extensions/workspace-indicator/extension.js | 4 +--- + 1 file changed, 1 insertion(+), 3 deletions(-) + +diff --git a/extensions/workspace-indicator/extension.js b/extensions/workspace-indicator/extension.js +index 58ac865..e6fffe4 100644 +--- a/extensions/workspace-indicator/extension.js ++++ b/extensions/workspace-indicator/extension.js +@@ -27,6 +27,7 @@ class WorkspaceIndicator extends PanelMenu.Button { + + this._currentWorkspace = global.screen.get_active_workspace_index(); + this._statusLabel = new St.Label({ ++ style_class: 'panel-workspace-indicator', + y_align: Clutter.ActorAlign.CENTER, + text: this._labelText() + }); +@@ -47,9 +48,6 @@ class WorkspaceIndicator extends PanelMenu.Button { + this.actor.connect('scroll-event', this._onScrollEvent.bind(this)); + this._createWorkspacesSection(); + +- //styling +- this._statusLabel.add_style_class_name('panel-workspace-indicator'); +- + this._settings = new Gio.Settings({ schema_id: WORKSPACE_SCHEMA }); + this._settingsChangedId = this._settings.connect( + `changed::${WORKSPACE_KEY}`, +-- +2.21.0 + + +From cce0e2b739cab74a166cfa75631836a7bfd06429 Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Florian=20M=C3=BCllner?= +Date: Sun, 9 Jun 2019 23:45:24 +0000 +Subject: [PATCH 30/33] workspace-indicator: Support horizontal workspace + layout + +Just like we did for the workspace indicator in the window-list, improve +the handling of horizontal workspace layouts by showing the switcher +in-place instead of delegating the functionality to a menu. + +https://gitlab.gnome.org/GNOME/gnome-shell-extensions/merge_requests/71 +--- + extensions/workspace-indicator/extension.js | 74 ++++++++++++++++++- + extensions/workspace-indicator/stylesheet.css | 27 ++++++- + 2 files changed, 98 insertions(+), 3 deletions(-) + +diff --git a/extensions/workspace-indicator/extension.js b/extensions/workspace-indicator/extension.js +index e6fffe4..32bb10f 100644 +--- a/extensions/workspace-indicator/extension.js ++++ b/extensions/workspace-indicator/extension.js +@@ -1,6 +1,7 @@ + // -*- mode: js2; indent-tabs-mode: nil; js2-basic-offset: 4 -*- + + const Gio = imports.gi.Gio; ++const GObject = imports.gi.GObject; + const Meta = imports.gi.Meta; + const Clutter = imports.gi.Clutter; + const St = imports.gi.St; +@@ -21,10 +22,36 @@ const Convenience = Me.imports.convenience; + const WORKSPACE_SCHEMA = 'org.gnome.desktop.wm.preferences'; + const WORKSPACE_KEY = 'workspace-names'; + ++let WorkspaceThumbnail = GObject.registerClass({ ++ GTypeName: 'WorkspaceIndicatorWorkspaceThumbnail' ++}, class WorkspaceThumbnail extends St.Button { ++ _init(index) { ++ super._init({ ++ style_class: 'workspace', ++ }); ++ ++ this._index = index; ++ } ++ ++ on_clicked() { ++ let ws = global.screen.get_workspace_by_index(this._index); ++ if (ws) ++ ws.activate(global.get_current_time()); ++ } ++}); ++ ++ + class WorkspaceIndicator extends PanelMenu.Button { + constructor() { + super(0.0, _("Workspace Indicator")); + ++ let container = new St.Widget({ ++ layout_manager: new Clutter.BinLayout(), ++ x_expand: true, ++ y_expand: true ++ }); ++ this.actor.add_actor(container); ++ + this._currentWorkspace = global.screen.get_active_workspace_index(); + this._statusLabel = new St.Label({ + style_class: 'panel-workspace-indicator', +@@ -32,7 +59,15 @@ class WorkspaceIndicator extends PanelMenu.Button { + text: this._labelText() + }); + +- this.add_actor(this._statusLabel); ++ container.add_actor(this._statusLabel); ++ ++ this._thumbnailsBox = new St.BoxLayout({ ++ style_class: 'panel-workspace-indicator-box', ++ y_expand: true, ++ reactive: true ++ }); ++ ++ container.add_actor(this._thumbnailsBox); + + this._workspacesItems = []; + this._workspaceSection = new PopupMenu.PopupMenuSection(); +@@ -42,11 +77,16 @@ class WorkspaceIndicator extends PanelMenu.Button { + global.screen.connect_after('notify::n-workspaces', + this._nWorkspacesChanged.bind(this)), + global.screen.connect_after('workspace-switched', +- this._onWorkspaceSwitched.bind(this)) ++ this._onWorkspaceSwitched.bind(this)), ++ global.screen.connect('notify::layout-rows', ++ this._onWorkspaceOrientationChanged.bind(this)) + ]; + + this.actor.connect('scroll-event', this._onScrollEvent.bind(this)); ++ this._thumbnailsBox.connect('scroll-event', this._onScrollEvent.bind(this)); + this._createWorkspacesSection(); ++ this._updateThumbnails(); ++ this._onWorkspaceOrientationChanged(); + + this._settings = new Gio.Settings({ schema_id: WORKSPACE_SCHEMA }); + this._settingsChangedId = this._settings.connect( +@@ -66,16 +106,26 @@ class WorkspaceIndicator extends PanelMenu.Button { + super.destroy(); + } + ++ _onWorkspaceOrientationChanged() { ++ let vertical = global.screen.layout_rows == -1; ++ this.actor.reactive = vertical; ++ ++ this._statusLabel.visible = vertical; ++ this._thumbnailsBox.visible = !vertical; ++ } ++ + _onWorkspaceSwitched() { + this._currentWorkspace = global.screen.get_active_workspace_index(); + + this._updateMenuOrnament(); ++ this._updateActiveThumbnail(); + + this._statusLabel.set_text(this._labelText()); + } + + _nWorkspacesChanged() { + this._createWorkspacesSection(); ++ this._updateThumbnails(); + } + + _updateMenuOrnament() { +@@ -86,6 +136,16 @@ class WorkspaceIndicator extends PanelMenu.Button { + } + } + ++ _updateActiveThumbnail() { ++ let thumbs = this._thumbnailsBox.get_children(); ++ for (let i = 0; i < thumbs.length; i++) { ++ if (i == this._currentWorkspace) ++ thumbs[i].add_style_class_name('active'); ++ else ++ thumbs[i].remove_style_class_name('active'); ++ } ++ } ++ + _labelText(workspaceIndex) { + if(workspaceIndex == undefined) { + workspaceIndex = this._currentWorkspace; +@@ -121,6 +181,16 @@ class WorkspaceIndicator extends PanelMenu.Button { + this._statusLabel.set_text(this._labelText()); + } + ++ _updateThumbnails() { ++ this._thumbnailsBox.destroy_all_children(); ++ ++ for (let i = 0; i < global.screen.n_workspaces; i++) { ++ let thumb = new WorkspaceThumbnail(i); ++ this._thumbnailsBox.add_actor(thumb); ++ } ++ this._updateActiveThumbnail(); ++ } ++ + _activate(index) { + if(index >= 0 && index < global.screen.n_workspaces) { + let metaWorkspace = global.screen.get_workspace_by_index(index); +diff --git a/extensions/workspace-indicator/stylesheet.css b/extensions/workspace-indicator/stylesheet.css +index 1271f1c..5118194 100644 +--- a/extensions/workspace-indicator/stylesheet.css ++++ b/extensions/workspace-indicator/stylesheet.css +@@ -1,5 +1,30 @@ + .panel-workspace-indicator { + padding: 0 8px; +- background-color: rgba(200, 200, 200, .5); ++} ++ ++.panel-workspace-indicator-box { ++ padding: 2px 0; ++} ++ ++.panel-workspace-indicator-box .workspace { ++ width: 40px; ++} ++ ++.panel-workspace-indicator, ++.panel-workspace-indicator-box .workspace { + border: 1px solid #cccccc; + } ++ ++.panel-workspace-indicator, ++.panel-workspace-indicator-box .workspace.active { ++ background-color: rgba(200, 200, 200, .5); ++} ++ ++.panel-workspace-indicator-box .workspace { ++ background-color: rgba(200, 200, 200, .3); ++ border-left-width: 0; ++} ++ ++.panel-workspace-indicator-box .workspace:first-child { ++ border-left-width: 1px; ++} +-- +2.21.0 + + +From 98df229b8a62e575529ebc28e085df53154ebc42 Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Florian=20M=C3=BCllner?= +Date: Fri, 28 Jun 2019 11:33:16 +0200 +Subject: [PATCH 31/33] workspace-indicator: Show previews in workspace + switcher + +Currently the new horizontal workspace switcher only shows a series of +buttons, with no indication of the workspaces' contents. Go full GNOME 2 +and add tiny draggable preview rectangles that represent the windows +on a particular workspace. + +https://gitlab.gnome.org/GNOME/gnome-shell-extensions/merge_requests/77 +--- + extensions/workspace-indicator/extension.js | 179 +++++++++++++++++- + extensions/workspace-indicator/stylesheet.css | 10 + + 2 files changed, 188 insertions(+), 1 deletion(-) + +diff --git a/extensions/workspace-indicator/extension.js b/extensions/workspace-indicator/extension.js +index 32bb10f..6b656c8 100644 +--- a/extensions/workspace-indicator/extension.js ++++ b/extensions/workspace-indicator/extension.js +@@ -6,6 +6,8 @@ const Meta = imports.gi.Meta; + const Clutter = imports.gi.Clutter; + const St = imports.gi.St; + const Mainloop = imports.mainloop; ++ ++const DND = imports.ui.dnd; + const PanelMenu = imports.ui.panelMenu; + const PopupMenu = imports.ui.popupMenu; + const Panel = imports.ui.panel; +@@ -22,15 +24,185 @@ const Convenience = Me.imports.convenience; + const WORKSPACE_SCHEMA = 'org.gnome.desktop.wm.preferences'; + const WORKSPACE_KEY = 'workspace-names'; + ++let WindowPreview = GObject.registerClass({ ++ GTypeName: 'WorkspaceIndicatorWindowPreview' ++}, class WindowPreview extends St.Button { ++ _init(window) { ++ super._init({ ++ style_class: 'workspace-indicator-window-preview' ++ }); ++ ++ this._delegate = this; ++ DND.makeDraggable(this, { restoreOnSuccess: true }); ++ ++ this._window = window; ++ ++ this.connect('destroy', this._onDestroy.bind(this)); ++ ++ this._sizeChangedId = this._window.connect('size-changed', ++ this._relayout.bind(this)); ++ this._positionChangedId = this._window.connect('position-changed', ++ this._relayout.bind(this)); ++ this._minimizedChangedId = this._window.connect('notify::minimized', ++ this._relayout.bind(this)); ++ this._monitorEnteredId = global.screen.connect('window-entered-monitor', ++ this._relayout.bind(this)); ++ this._monitorLeftId = global.screen.connect('window-left-monitor', ++ this._relayout.bind(this)); ++ ++ // Do initial layout when we get a parent ++ let id = this.connect('parent-set', () => { ++ this.disconnect(id); ++ if (!this.get_parent()) ++ return; ++ this._laterId = Meta.later_add(Meta.LaterType.BEFORE_REDRAW, () => { ++ this._laterId = 0; ++ this._relayout(); ++ return false; ++ }); ++ }); ++ ++ this._focusChangedId = global.display.connect('notify::focus-window', ++ this._onFocusChanged.bind(this)); ++ this._onFocusChanged(); ++ } ++ ++ // needed for DND ++ get realWindow() { ++ return this._window.get_compositor_private(); ++ } ++ ++ _onDestroy() { ++ this._window.disconnect(this._sizeChangedId); ++ this._window.disconnect(this._positionChangedId); ++ this._window.disconnect(this._minimizedChangedId); ++ global.screen.disconnect(this._monitorEnteredId); ++ global.screen.disconnect(this._monitorLeftId); ++ global.display.disconnect(this._focusChangedId); ++ if (this._laterId) ++ Meta.later_remove(this._laterId); ++ } ++ ++ _onFocusChanged() { ++ if (global.display.focus_window == this._window) ++ this.add_style_class_name('active'); ++ else ++ this.remove_style_class_name('active'); ++ } ++ ++ _relayout() { ++ let monitor = Main.layoutManager.findIndexForActor(this); ++ this.visible = monitor == this._window.get_monitor() && ++ this._window.showing_on_its_workspace(); ++ ++ if (!this.visible) ++ return; ++ ++ let workArea = Main.layoutManager.getWorkAreaForMonitor(monitor); ++ let hscale = this.get_parent().allocation.get_width() / workArea.width; ++ let vscale = this.get_parent().allocation.get_height() / workArea.height; ++ ++ let frameRect = this._window.get_frame_rect(); ++ this.set_size( ++ Math.round(Math.min(frameRect.width, workArea.width) * hscale), ++ Math.round(Math.min(frameRect.height, workArea.height) * vscale)); ++ this.set_position( ++ Math.round(frameRect.x * hscale), ++ Math.round(frameRect.y * vscale)); ++ } ++}); ++ + let WorkspaceThumbnail = GObject.registerClass({ + GTypeName: 'WorkspaceIndicatorWorkspaceThumbnail' + }, class WorkspaceThumbnail extends St.Button { + _init(index) { + super._init({ + style_class: 'workspace', ++ child: new Clutter.Actor({ ++ layout_manager: new Clutter.BinLayout(), ++ clip_to_allocation: true ++ }), ++ x_fill: true, ++ y_fill: true + }); + ++ this.connect('destroy', this._onDestroy.bind(this)); ++ + this._index = index; ++ this._delegate = this; // needed for DND ++ ++ this._windowPreviews = new Map(); ++ ++ this._workspace = global.screen.get_workspace_by_index(index); ++ ++ this._windowAddedId = this._workspace.connect('window-added', ++ (ws, window) => { ++ this._addWindow(window); ++ }); ++ this._windowRemovedId = this._workspace.connect('window-removed', ++ (ws, window) => { ++ this._removeWindow(window); ++ }); ++ this._restackedId = global.screen.connect('restacked', ++ this._onRestacked.bind(this)); ++ ++ this._workspace.list_windows().forEach(w => this._addWindow(w)); ++ this._onRestacked(); ++ } ++ ++ acceptDrop(source) { ++ if (!source.realWindow) ++ return false; ++ ++ let window = source.realWindow.get_meta_window(); ++ this._moveWindow(window); ++ return true; ++ } ++ ++ handleDragOver(source) { ++ if (source.realWindow) ++ return DND.DragMotionResult.MOVE_DROP; ++ else ++ return DND.DragMotionResult.CONTINUE; ++ } ++ ++ _addWindow(window) { ++ if (this._windowPreviews.has(window)) ++ return; ++ ++ let preview = new WindowPreview(window); ++ preview.connect('clicked', (a, btn) => this.emit('clicked', btn)); ++ this._windowPreviews.set(window, preview); ++ this.child.add_child(preview); ++ } ++ ++ _removeWindow(window) { ++ let preview = this._windowPreviews.get(window); ++ if (!preview) ++ return; ++ ++ this._windowPreviews.delete(window); ++ preview.destroy(); ++ } ++ ++ _onRestacked() { ++ let lastPreview = null; ++ let windows = global.get_window_actors().map(a => a.meta_window); ++ for (let i = 0; i < windows.length; i++) { ++ let preview = this._windowPreviews.get(windows[i]); ++ if (!preview) ++ continue; ++ ++ this.child.set_child_above_sibling(preview, lastPreview); ++ lastPreview = preview; ++ } ++ } ++ ++ _moveWindow(window) { ++ let monitorIndex = Main.layoutManager.findIndexForActor(this); ++ if (monitorIndex != window.get_monitor()) ++ window.move_to_monitor(monitorIndex); ++ window.change_workspace_by_index(this._index, false); + } + + on_clicked() { +@@ -38,8 +210,13 @@ let WorkspaceThumbnail = GObject.registerClass({ + if (ws) + ws.activate(global.get_current_time()); + } +-}); + ++ _onDestroy() { ++ this._workspace.disconnect(this._windowAddedId); ++ this._workspace.disconnect(this._windowRemovedId); ++ global.screen.disconnect(this._restackedId); ++ } ++}); + + class WorkspaceIndicator extends PanelMenu.Button { + constructor() { +diff --git a/extensions/workspace-indicator/stylesheet.css b/extensions/workspace-indicator/stylesheet.css +index 5118194..8601c3e 100644 +--- a/extensions/workspace-indicator/stylesheet.css ++++ b/extensions/workspace-indicator/stylesheet.css +@@ -28,3 +28,13 @@ + .panel-workspace-indicator-box .workspace:first-child { + border-left-width: 1px; + } ++ ++.workspace-indicator-window-preview { ++ background-color: #252525; ++ border: 1px solid #ccc; ++} ++ ++.workspace-indicator-window-preview.active { ++ background-color: #353535; ++ border: 2px solid #ccc; ++} +-- +2.21.0 + + +From 9b1fb384f2f46a953c4838991ec58cd3c2872b31 Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Florian=20M=C3=BCllner?= +Date: Fri, 6 Sep 2019 20:41:23 +0200 +Subject: [PATCH 32/33] window-list: Exclude DESKTOP windows from window + previews + +While nautilus removed its desktop support a while ago in favor of an +extension, it's still possible that some external X11 desktop icon app +is used. As DESKTOP windows cannot be moved between workspaces or stacked, +and aren't perceived as regular windows, it doesn't make sense to show +them as previews in the workspace switcher. + +https://gitlab.gnome.org/GNOME/gnome-shell-extensions/merge_requests/93 +--- + extensions/window-list/workspaceIndicator.js | 1 + + 1 file changed, 1 insertion(+) + +diff --git a/extensions/window-list/workspaceIndicator.js b/extensions/window-list/workspaceIndicator.js +index 0bcee80..c0dd65d 100644 +--- a/extensions/window-list/workspaceIndicator.js ++++ b/extensions/window-list/workspaceIndicator.js +@@ -78,6 +78,7 @@ let WindowPreview = GObject.registerClass({ + _relayout() { + let monitor = Main.layoutManager.findIndexForActor(this); + this.visible = monitor == this._window.get_monitor() && ++ this._window.window_type !== Meta.WindowType.DESKTOP && + this._window.showing_on_its_workspace(); + + if (!this.visible) +-- +2.21.0 + + +From 59d4e3377072507f95562c8516913b1d257651c0 Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Florian=20M=C3=BCllner?= +Date: Fri, 6 Sep 2019 20:47:56 +0200 +Subject: [PATCH 33/33] workspace-indicator: Exclude DESKTOP windows from + window previews + +While nautilus removed its desktop support a while ago in favor of an +extension, it's still possible that some external X11 desktop icon app +is used. As DESKTOP windows cannot be moved between workspaces or stacked, +and aren't perceived as regular windows, it doesn't make sense to show +them as previews in the workspace switcher. + +https://gitlab.gnome.org/GNOME/gnome-shell-extensions/merge_requests/93 +--- + extensions/workspace-indicator/extension.js | 1 + + 1 file changed, 1 insertion(+) + +diff --git a/extensions/workspace-indicator/extension.js b/extensions/workspace-indicator/extension.js +index 6b656c8..6b7947d 100644 +--- a/extensions/workspace-indicator/extension.js ++++ b/extensions/workspace-indicator/extension.js +@@ -93,6 +93,7 @@ let WindowPreview = GObject.registerClass({ + _relayout() { + let monitor = Main.layoutManager.findIndexForActor(this); + this.visible = monitor == this._window.get_monitor() && ++ this._window.window_type !== Meta.WindowType.DESKTOP && + this._window.showing_on_its_workspace(); + + if (!this.visible) +-- +2.21.0 + diff --git a/SOURCES/resurrect-system-monitor.patch b/SOURCES/resurrect-system-monitor.patch new file mode 100644 index 0000000..d862550 --- /dev/null +++ b/SOURCES/resurrect-system-monitor.patch @@ -0,0 +1,1038 @@ +From 57bb099db30703a474a023122f1106e199ff79ed Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Florian=20M=C3=BCllner?= +Date: Wed, 17 May 2017 19:13:50 +0200 +Subject: [PATCH 1/4] extensions: Resurrect systemMonitor extension + +The extension was removed upstream because: + - it hooks into the message tray that was removed + - it was known to have performance issues + - there are plenty of alternatives + +Those aren't good enough reasons for dropping it downstream +as well though, so we need to bring it back ... + +This reverts commit c9a6421f362cd156cf731289eadc11f44f6970ac. +--- + extensions/systemMonitor/extension.js | 376 ++++++++++++++++++++++ + extensions/systemMonitor/meson.build | 5 + + extensions/systemMonitor/metadata.json.in | 11 + + extensions/systemMonitor/stylesheet.css | 35 ++ + meson.build | 1 + + 5 files changed, 428 insertions(+) + create mode 100644 extensions/systemMonitor/extension.js + create mode 100644 extensions/systemMonitor/meson.build + create mode 100644 extensions/systemMonitor/metadata.json.in + create mode 100644 extensions/systemMonitor/stylesheet.css + +diff --git a/extensions/systemMonitor/extension.js b/extensions/systemMonitor/extension.js +new file mode 100644 +index 0000000..7b09df0 +--- /dev/null ++++ b/extensions/systemMonitor/extension.js +@@ -0,0 +1,376 @@ ++/* -*- mode: js2; js2-basic-offset: 4; indent-tabs-mode: nil -*- */ ++ ++const Clutter = imports.gi.Clutter; ++const GTop = imports.gi.GTop; ++const Lang = imports.lang; ++const Mainloop = imports.mainloop; ++const St = imports.gi.St; ++const Shell = imports.gi.Shell; ++ ++const Main = imports.ui.main; ++const Tweener = imports.ui.tweener; ++ ++const Gettext = imports.gettext.domain('gnome-shell-extensions'); ++const _ = Gettext.gettext; ++ ++const ExtensionUtils = imports.misc.extensionUtils; ++const Me = ExtensionUtils.getCurrentExtension(); ++const Convenience = Me.imports.convenience; ++ ++const INDICATOR_UPDATE_INTERVAL = 500; ++const INDICATOR_NUM_GRID_LINES = 3; ++ ++const ITEM_LABEL_SHOW_TIME = 0.15; ++const ITEM_LABEL_HIDE_TIME = 0.1; ++const ITEM_HOVER_TIMEOUT = 300; ++ ++const Indicator = new Lang.Class({ ++ Name: 'SystemMonitor.Indicator', ++ ++ _init: function() { ++ this._initValues(); ++ this.drawing_area = new St.DrawingArea({ reactive: true }); ++ this.drawing_area.connect('repaint', Lang.bind(this, this._draw)); ++ this.drawing_area.connect('button-press-event', function() { ++ let app = Shell.AppSystem.get_default().lookup_app('gnome-system-monitor.desktop'); ++ app.open_new_window(-1); ++ return true; ++ }); ++ ++ this.actor = new St.Bin({ style_class: "extension-systemMonitor-indicator-area", ++ reactive: true, track_hover: true, ++ x_fill: true, y_fill: true }); ++ this.actor.add_actor(this.drawing_area); ++ ++ this._timeout = Mainloop.timeout_add(INDICATOR_UPDATE_INTERVAL, Lang.bind(this, function () { ++ this._updateValues(); ++ this.drawing_area.queue_repaint(); ++ return true; ++ })); ++ }, ++ ++ showLabel: function() { ++ if (this.label == null) ++ return; ++ ++ this.label.opacity = 0; ++ this.label.show(); ++ ++ let [stageX, stageY] = this.actor.get_transformed_position(); ++ ++ let itemWidth = this.actor.allocation.x2 - this.actor.allocation.x1; ++ let itemHeight = this.actor.allocation.y2 - this.actor.allocation.y1; ++ ++ let labelWidth = this.label.width; ++ let labelHeight = this.label.height; ++ let xOffset = Math.floor((itemWidth - labelWidth) / 2) ++ ++ let x = stageX + xOffset; ++ ++ let node = this.label.get_theme_node(); ++ let yOffset = node.get_length('-y-offset'); ++ ++ let y = stageY - this.label.get_height() - yOffset; ++ ++ this.label.set_position(x, y); ++ Tweener.addTween(this.label, ++ { opacity: 255, ++ time: ITEM_LABEL_SHOW_TIME, ++ transition: 'easeOutQuad', ++ }); ++ }, ++ ++ setLabelText: function(text) { ++ if (this.label == null) ++ this.label = new St.Label({ style_class: 'extension-systemMonitor-indicator-label'}); ++ ++ this.label.set_text(text); ++ Main.layoutManager.addChrome(this.label); ++ this.label.hide(); ++ }, ++ ++ hideLabel: function () { ++ Tweener.addTween(this.label, ++ { opacity: 0, ++ time: ITEM_LABEL_HIDE_TIME, ++ transition: 'easeOutQuad', ++ onComplete: Lang.bind(this, function() { ++ this.label.hide(); ++ }) ++ }); ++ }, ++ ++ destroy: function() { ++ Mainloop.source_remove(this._timeout); ++ ++ this.actor.destroy(); ++ if (this.label) ++ this.label.destroy(); ++ }, ++ ++ _initValues: function() { ++ }, ++ ++ _updateValues: function() { ++ }, ++ ++ _draw: function(area) { ++ let [width, height] = area.get_surface_size(); ++ let themeNode = this.actor.get_theme_node(); ++ let cr = area.get_context(); ++ ++ //draw the background grid ++ let color = themeNode.get_color(this.gridColor); ++ let gridOffset = Math.floor(height / (INDICATOR_NUM_GRID_LINES + 1)); ++ for (let i = 1; i <= INDICATOR_NUM_GRID_LINES; ++i) { ++ cr.moveTo(0, i * gridOffset + .5); ++ cr.lineTo(width, i * gridOffset + .5); ++ } ++ Clutter.cairo_set_source_color(cr, color); ++ cr.setLineWidth(1); ++ cr.setDash([4,1], 0); ++ cr.stroke(); ++ ++ //draw the foreground ++ ++ function makePath(values, reverse, nudge) { ++ if (nudge == null) { ++ nudge = 0; ++ } ++ //if we are going in reverse, we are completing the bottom of a chart, so use lineTo ++ if (reverse) { ++ cr.lineTo(values.length - 1, (1 - values[values.length - 1]) * height + nudge); ++ for (let k = values.length - 2; k >= 0; --k) { ++ cr.lineTo(k, (1 - values[k]) * height + nudge); ++ } ++ } else { ++ cr.moveTo(0, (1 - values[0]) * height + nudge); ++ for (let k = 1; k < values.length; ++k) { ++ cr.lineTo(k, (1 - values[k]) * height + nudge); ++ } ++ ++ } ++ } ++ ++ let renderStats = this.renderStats; ++ ++ // Make sure we don't have more sample points than pixels ++ renderStats.map(Lang.bind(this, function(k){ ++ let stat = this.stats[k]; ++ if (stat.values.length > width) { ++ stat.values = stat.values.slice(stat.values.length - width, stat.values.length); ++ } ++ })); ++ ++ for (let i = 0; i < renderStats.length; ++i) { ++ let stat = this.stats[renderStats[i]]; ++ // We outline at full opacity and fill with 40% opacity ++ let outlineColor = themeNode.get_color(stat.color); ++ let color = new Clutter.Color(outlineColor); ++ color.alpha = color.alpha * .4; ++ ++ // Render the background between us and the next level ++ makePath(stat.values, false); ++ // If there is a process below us, render the cpu between us and it, otherwise, ++ // render to the bottom of the chart ++ if (i == renderStats.length - 1) { ++ cr.lineTo(stat.values.length - 1, height); ++ cr.lineTo(0, height); ++ cr.closePath(); ++ } else { ++ let nextStat = this.stats[renderStats[i+1]]; ++ makePath(nextStat.values, true); ++ } ++ cr.closePath() ++ Clutter.cairo_set_source_color(cr, color); ++ cr.fill(); ++ ++ // Render the outline of this level ++ makePath(stat.values, false, .5); ++ Clutter.cairo_set_source_color(cr, outlineColor); ++ cr.setLineWidth(1.0); ++ cr.setDash([], 0); ++ cr.stroke(); ++ } ++ } ++}); ++ ++const CpuIndicator = new Lang.Class({ ++ Name: 'SystemMonitor.CpuIndicator', ++ Extends: Indicator, ++ ++ _init: function() { ++ this.parent(); ++ ++ this.gridColor = '-grid-color'; ++ this.renderStats = [ 'cpu-user', 'cpu-sys', 'cpu-iowait' ]; ++ ++ // Make sure renderStats is sorted as necessary for rendering ++ let renderStatOrder = {'cpu-total': 0, 'cpu-user': 1, 'cpu-sys': 2, 'cpu-iowait': 3}; ++ this.renderStats = this.renderStats.sort(function(a,b) { ++ return renderStatOrder[a] - renderStatOrder[b]; ++ }); ++ ++ this.setLabelText(_("CPU")); ++ }, ++ ++ _initValues: function() { ++ this._prev = new GTop.glibtop_cpu; ++ GTop.glibtop_get_cpu(this._prev); ++ ++ this.stats = { ++ 'cpu-user': {color: '-cpu-user-color', values: []}, ++ 'cpu-sys': {color: '-cpu-sys-color', values: []}, ++ 'cpu-iowait': {color: '-cpu-iowait-color', values: []}, ++ 'cpu-total': {color: '-cpu-total-color', values: []} ++ }; ++ }, ++ ++ _updateValues: function() { ++ let cpu = new GTop.glibtop_cpu; ++ let t = 0.0; ++ GTop.glibtop_get_cpu(cpu); ++ let total = cpu.total - this._prev.total; ++ let user = cpu.user - this._prev.user; ++ let sys = cpu.sys - this._prev.sys; ++ let iowait = cpu.iowait - this._prev.iowait; ++ let idle = cpu.idle - this._prev.idle; ++ ++ t += iowait / total; ++ this.stats['cpu-iowait'].values.push(t); ++ t += sys / total; ++ this.stats['cpu-sys'].values.push(t); ++ t += user / total; ++ this.stats['cpu-user'].values.push(t); ++ this.stats['cpu-total'].values.push(1 - idle / total); ++ ++ this._prev = cpu; ++ } ++}); ++ ++const MemoryIndicator = new Lang.Class({ ++ Name: 'SystemMonitor.MemoryIndicator', ++ Extends: Indicator, ++ ++ _init: function() { ++ this.parent(); ++ ++ this.gridColor = '-grid-color'; ++ this.renderStats = [ 'mem-user', 'mem-other', 'mem-cached' ]; ++ ++ // Make sure renderStats is sorted as necessary for rendering ++ let renderStatOrder = { 'mem-cached': 0, 'mem-other': 1, 'mem-user': 2 }; ++ this.renderStats = this.renderStats.sort(function(a,b) { ++ return renderStatOrder[a] - renderStatOrder[b]; ++ }); ++ ++ this.setLabelText(_("Memory")); ++ }, ++ ++ _initValues: function() { ++ this.mem = new GTop.glibtop_mem; ++ this.stats = { ++ 'mem-user': { color: "-mem-user-color", values: [] }, ++ 'mem-other': { color: "-mem-other-color", values: [] }, ++ 'mem-cached': { color: "-mem-cached-color", values: [] } ++ }; ++ }, ++ ++ _updateValues: function() { ++ GTop.glibtop_get_mem(this.mem); ++ ++ let t = this.mem.user / this.mem.total; ++ this.stats['mem-user'].values.push(t); ++ t += (this.mem.used - this.mem.user - this.mem.cached) / this.mem.total; ++ this.stats['mem-other'].values.push(t); ++ t += this.mem.cached / this.mem.total; ++ this.stats['mem-cached'].values.push(t); ++ } ++}); ++ ++const INDICATORS = [CpuIndicator, MemoryIndicator]; ++ ++const Extension = new Lang.Class({ ++ Name: 'SystemMonitor.Extension', ++ ++ _init: function() { ++ Convenience.initTranslations(); ++ ++ this._showLabelTimeoutId = 0; ++ this._resetHoverTimeoutId = 0; ++ this._labelShowing = false; ++ }, ++ ++ enable: function() { ++ this._box = new St.BoxLayout({ style_class: 'extension-systemMonitor-container', ++ x_align: Clutter.ActorAlign.START, ++ x_expand: true }); ++ this._indicators = [ ]; ++ ++ for (let i = 0; i < INDICATORS.length; i++) { ++ let indicator = new (INDICATORS[i])(); ++ ++ indicator.actor.connect('notify::hover', Lang.bind(this, function() { ++ this._onHover(indicator); ++ })); ++ this._box.add_actor(indicator.actor); ++ this._indicators.push(indicator); ++ } ++ ++ this._boxHolder = new St.BoxLayout({ x_expand: true, ++ y_expand: true, ++ x_align: Clutter.ActorAlign.START, ++ }); ++ let menuButton = Main.messageTray._messageTrayMenuButton.actor; ++ Main.messageTray.actor.remove_child(menuButton); ++ Main.messageTray.actor.add_child(this._boxHolder); ++ ++ this._boxHolder.add_child(this._box); ++ this._boxHolder.add_child(menuButton); ++ }, ++ ++ disable: function() { ++ this._indicators.forEach(function(i) { i.destroy(); }); ++ ++ let menuButton = Main.messageTray._messageTrayMenuButton.actor; ++ this._boxHolder.remove_child(menuButton); ++ Main.messageTray.actor.add_child(menuButton); ++ ++ this._box.destroy(); ++ this._boxHolder.destroy(); ++ }, ++ ++ _onHover: function (item) { ++ if (item.actor.get_hover()) { ++ if (this._showLabelTimeoutId == 0) { ++ let timeout = this._labelShowing ? 0 : ITEM_HOVER_TIMEOUT; ++ this._showLabelTimeoutId = Mainloop.timeout_add(timeout, ++ Lang.bind(this, function() { ++ this._labelShowing = true; ++ item.showLabel(); ++ return false; ++ })); ++ if (this._resetHoverTimeoutId > 0) { ++ Mainloop.source_remove(this._resetHoverTimeoutId); ++ this._resetHoverTimeoutId = 0; ++ } ++ } ++ } else { ++ if (this._showLabelTimeoutId > 0) ++ Mainloop.source_remove(this._showLabelTimeoutId); ++ this._showLabelTimeoutId = 0; ++ item.hideLabel(); ++ if (this._labelShowing) { ++ this._resetHoverTimeoutId = Mainloop.timeout_add(ITEM_HOVER_TIMEOUT, ++ Lang.bind(this, function() { ++ this._labelShowing = false; ++ return false; ++ })); ++ } ++ } ++ }, ++}); ++ ++function init() { ++ return new Extension(); ++} +diff --git a/extensions/systemMonitor/meson.build b/extensions/systemMonitor/meson.build +new file mode 100644 +index 0000000..48504f6 +--- /dev/null ++++ b/extensions/systemMonitor/meson.build +@@ -0,0 +1,5 @@ ++extension_data += configure_file( ++ input: metadata_name + '.in', ++ output: metadata_name, ++ configuration: metadata_conf ++) +diff --git a/extensions/systemMonitor/metadata.json.in b/extensions/systemMonitor/metadata.json.in +new file mode 100644 +index 0000000..fa75007 +--- /dev/null ++++ b/extensions/systemMonitor/metadata.json.in +@@ -0,0 +1,11 @@ ++{ ++ "shell-version": ["@shell_current@" ], ++ "uuid": "@uuid@", ++ "extension-id": "@extension_id@", ++ "settings-schema": "@gschemaname@", ++ "gettext-domain": "@gettext_domain@", ++ "original-author": "zaspire@rambler.ru", ++ "name": "SystemMonitor", ++ "description": "System monitor showing CPU and memory usage in the message tray.", ++ "url": "@url@" ++} +diff --git a/extensions/systemMonitor/stylesheet.css b/extensions/systemMonitor/stylesheet.css +new file mode 100644 +index 0000000..13f95ec +--- /dev/null ++++ b/extensions/systemMonitor/stylesheet.css +@@ -0,0 +1,35 @@ ++.extension-systemMonitor-container { ++ spacing: 5px; ++ padding-left: 5px; ++ padding-right: 5px; ++ padding-bottom: 10px; ++ padding-top: 10px; ++} ++ ++.extension-systemMonitor-indicator-area { ++ border: 1px solid #8d8d8d; ++ border-radius: 3px; ++ width: 100px; ++ /* message tray is 72px, so 20px padding of the container, ++ 2px of border, makes it 50px */ ++ height: 50px; ++ -grid-color: #575757; ++ -cpu-total-color: rgb(0,154,62); ++ -cpu-user-color: rgb(69,154,0); ++ -cpu-sys-color: rgb(255,253,81); ++ -cpu-iowait-color: rgb(210,148,0); ++ -mem-user-color: rgb(210,148,0); ++ -mem-cached-color: rgb(90,90,90); ++ -mem-other-color: rgb(205,203,41); ++ background-color: #1e1e1e; ++} ++ ++.extension-systemMonitor-indicator-label { ++ border-radius: 7px; ++ padding: 4px 12px; ++ background-color: rgba(0,0,0,0.9); ++ text-align: center; ++ -y-offset: 8px; ++ font-size: 9pt; ++ font-weight: bold; ++} +diff --git a/meson.build b/meson.build +index 7562eb1..955b5ee 100644 +--- a/meson.build ++++ b/meson.build +@@ -58,6 +58,7 @@ all_extensions += [ + 'native-window-placement', + 'no-hot-corner', + 'panel-favorites', ++ 'systemMonitor', + 'top-icons', + 'updates-dialog', + 'user-theme', +-- +2.21.0 + + +From 2f3092238bf17bf41749674dd94bf4609a955624 Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Florian=20M=C3=BCllner?= +Date: Wed, 17 May 2017 19:31:58 +0200 +Subject: [PATCH 2/4] systemMonitor: Move indicators to calendar + +The message tray joined the invisible choir, so we have to find +a new home for the extension UI. The message list in the calendar +drop-down looks like the best option, given that it replaced the +old tray (and also took over the old keyboard shortcut to bring +it up quickly). +--- + extensions/systemMonitor/extension.js | 56 ++++++++++++------------- + extensions/systemMonitor/stylesheet.css | 14 ------- + 2 files changed, 28 insertions(+), 42 deletions(-) + +diff --git a/extensions/systemMonitor/extension.js b/extensions/systemMonitor/extension.js +index 7b09df0..1388a1f 100644 +--- a/extensions/systemMonitor/extension.js ++++ b/extensions/systemMonitor/extension.js +@@ -1,132 +1,146 @@ + /* -*- mode: js2; js2-basic-offset: 4; indent-tabs-mode: nil -*- */ + + const Clutter = imports.gi.Clutter; + const GTop = imports.gi.GTop; + const Lang = imports.lang; + const Mainloop = imports.mainloop; ++const Signals = imports.signals; + const St = imports.gi.St; + const Shell = imports.gi.Shell; + + const Main = imports.ui.main; ++const MessageList = imports.ui.messageList; + const Tweener = imports.ui.tweener; + + const Gettext = imports.gettext.domain('gnome-shell-extensions'); + const _ = Gettext.gettext; + + const ExtensionUtils = imports.misc.extensionUtils; + const Me = ExtensionUtils.getCurrentExtension(); + const Convenience = Me.imports.convenience; + + const INDICATOR_UPDATE_INTERVAL = 500; + const INDICATOR_NUM_GRID_LINES = 3; + + const ITEM_LABEL_SHOW_TIME = 0.15; + const ITEM_LABEL_HIDE_TIME = 0.1; + const ITEM_HOVER_TIMEOUT = 300; + + const Indicator = new Lang.Class({ + Name: 'SystemMonitor.Indicator', + + _init: function() { + this._initValues(); +- this.drawing_area = new St.DrawingArea({ reactive: true }); ++ this.drawing_area = new St.DrawingArea(); + this.drawing_area.connect('repaint', Lang.bind(this, this._draw)); +- this.drawing_area.connect('button-press-event', function() { ++ ++ this.actor = new St.Button({ style_class: "message message-content extension-systemMonitor-indicator-area", ++ x_expand: true, x_fill: true, ++ y_fill: true, can_focus: true }); ++ this.actor.add_actor(this.drawing_area); ++ ++ this.actor.connect('clicked', function() { + let app = Shell.AppSystem.get_default().lookup_app('gnome-system-monitor.desktop'); + app.open_new_window(-1); +- return true; +- }); + +- this.actor = new St.Bin({ style_class: "extension-systemMonitor-indicator-area", +- reactive: true, track_hover: true, +- x_fill: true, y_fill: true }); +- this.actor.add_actor(this.drawing_area); ++ Main.overview.hide(); ++ Main.panel.closeCalendar(); ++ }); + + this._timeout = Mainloop.timeout_add(INDICATOR_UPDATE_INTERVAL, Lang.bind(this, function () { + this._updateValues(); + this.drawing_area.queue_repaint(); + return true; + })); + }, + + showLabel: function() { + if (this.label == null) + return; + + this.label.opacity = 0; + this.label.show(); + + let [stageX, stageY] = this.actor.get_transformed_position(); + + let itemWidth = this.actor.allocation.x2 - this.actor.allocation.x1; + let itemHeight = this.actor.allocation.y2 - this.actor.allocation.y1; + + let labelWidth = this.label.width; + let labelHeight = this.label.height; + let xOffset = Math.floor((itemWidth - labelWidth) / 2) + + let x = stageX + xOffset; + + let node = this.label.get_theme_node(); + let yOffset = node.get_length('-y-offset'); + + let y = stageY - this.label.get_height() - yOffset; + + this.label.set_position(x, y); ++ this.label.get_parent().set_child_above_sibling(this.label, null); + Tweener.addTween(this.label, + { opacity: 255, + time: ITEM_LABEL_SHOW_TIME, + transition: 'easeOutQuad', + }); + }, + + setLabelText: function(text) { + if (this.label == null) + this.label = new St.Label({ style_class: 'extension-systemMonitor-indicator-label'}); + + this.label.set_text(text); + Main.layoutManager.addChrome(this.label); + this.label.hide(); + }, + + hideLabel: function () { + Tweener.addTween(this.label, + { opacity: 0, + time: ITEM_LABEL_HIDE_TIME, + transition: 'easeOutQuad', + onComplete: Lang.bind(this, function() { + this.label.hide(); + }) + }); + }, + ++ /* MessageList.Message boilerplate */ ++ canClose: function() { ++ return false; ++ }, ++ ++ clear: function() { ++ }, ++ + destroy: function() { + Mainloop.source_remove(this._timeout); + + this.actor.destroy(); + if (this.label) + this.label.destroy(); + }, + + _initValues: function() { + }, + + _updateValues: function() { + }, + + _draw: function(area) { + let [width, height] = area.get_surface_size(); + let themeNode = this.actor.get_theme_node(); + let cr = area.get_context(); + + //draw the background grid + let color = themeNode.get_color(this.gridColor); + let gridOffset = Math.floor(height / (INDICATOR_NUM_GRID_LINES + 1)); + for (let i = 1; i <= INDICATOR_NUM_GRID_LINES; ++i) { + cr.moveTo(0, i * gridOffset + .5); + cr.lineTo(width, i * gridOffset + .5); + } + Clutter.cairo_set_source_color(cr, color); + cr.setLineWidth(1); + cr.setDash([4,1], 0); + cr.stroke(); +@@ -167,60 +181,61 @@ const Indicator = new Lang.Class({ + // We outline at full opacity and fill with 40% opacity + let outlineColor = themeNode.get_color(stat.color); + let color = new Clutter.Color(outlineColor); + color.alpha = color.alpha * .4; + + // Render the background between us and the next level + makePath(stat.values, false); + // If there is a process below us, render the cpu between us and it, otherwise, + // render to the bottom of the chart + if (i == renderStats.length - 1) { + cr.lineTo(stat.values.length - 1, height); + cr.lineTo(0, height); + cr.closePath(); + } else { + let nextStat = this.stats[renderStats[i+1]]; + makePath(nextStat.values, true); + } + cr.closePath() + Clutter.cairo_set_source_color(cr, color); + cr.fill(); + + // Render the outline of this level + makePath(stat.values, false, .5); + Clutter.cairo_set_source_color(cr, outlineColor); + cr.setLineWidth(1.0); + cr.setDash([], 0); + cr.stroke(); + } + } + }); ++Signals.addSignalMethods(Indicator.prototype); // For MessageList.Message compat + + const CpuIndicator = new Lang.Class({ + Name: 'SystemMonitor.CpuIndicator', + Extends: Indicator, + + _init: function() { + this.parent(); + + this.gridColor = '-grid-color'; + this.renderStats = [ 'cpu-user', 'cpu-sys', 'cpu-iowait' ]; + + // Make sure renderStats is sorted as necessary for rendering + let renderStatOrder = {'cpu-total': 0, 'cpu-user': 1, 'cpu-sys': 2, 'cpu-iowait': 3}; + this.renderStats = this.renderStats.sort(function(a,b) { + return renderStatOrder[a] - renderStatOrder[b]; + }); + + this.setLabelText(_("CPU")); + }, + + _initValues: function() { + this._prev = new GTop.glibtop_cpu; + GTop.glibtop_get_cpu(this._prev); + + this.stats = { + 'cpu-user': {color: '-cpu-user-color', values: []}, + 'cpu-sys': {color: '-cpu-sys-color', values: []}, + 'cpu-iowait': {color: '-cpu-iowait-color', values: []}, + 'cpu-total': {color: '-cpu-total-color', values: []} + }; +@@ -275,96 +290,81 @@ const MemoryIndicator = new Lang.Class({ + 'mem-cached': { color: "-mem-cached-color", values: [] } + }; + }, + + _updateValues: function() { + GTop.glibtop_get_mem(this.mem); + + let t = this.mem.user / this.mem.total; + this.stats['mem-user'].values.push(t); + t += (this.mem.used - this.mem.user - this.mem.cached) / this.mem.total; + this.stats['mem-other'].values.push(t); + t += this.mem.cached / this.mem.total; + this.stats['mem-cached'].values.push(t); + } + }); + + const INDICATORS = [CpuIndicator, MemoryIndicator]; + + const Extension = new Lang.Class({ + Name: 'SystemMonitor.Extension', + + _init: function() { + Convenience.initTranslations(); + + this._showLabelTimeoutId = 0; + this._resetHoverTimeoutId = 0; + this._labelShowing = false; + }, + + enable: function() { +- this._box = new St.BoxLayout({ style_class: 'extension-systemMonitor-container', +- x_align: Clutter.ActorAlign.START, +- x_expand: true }); ++ this._section = new MessageList.MessageListSection(_("System Monitor")); + this._indicators = [ ]; + + for (let i = 0; i < INDICATORS.length; i++) { + let indicator = new (INDICATORS[i])(); + + indicator.actor.connect('notify::hover', Lang.bind(this, function() { + this._onHover(indicator); + })); +- this._box.add_actor(indicator.actor); ++ this._section.addMessage(indicator, false); + this._indicators.push(indicator); + } + +- this._boxHolder = new St.BoxLayout({ x_expand: true, +- y_expand: true, +- x_align: Clutter.ActorAlign.START, +- }); +- let menuButton = Main.messageTray._messageTrayMenuButton.actor; +- Main.messageTray.actor.remove_child(menuButton); +- Main.messageTray.actor.add_child(this._boxHolder); +- +- this._boxHolder.add_child(this._box); +- this._boxHolder.add_child(menuButton); ++ Main.panel.statusArea.dateMenu._messageList._addSection(this._section); ++ this._section.actor.get_parent().set_child_at_index(this._section.actor, 0); + }, + + disable: function() { + this._indicators.forEach(function(i) { i.destroy(); }); + +- let menuButton = Main.messageTray._messageTrayMenuButton.actor; +- this._boxHolder.remove_child(menuButton); +- Main.messageTray.actor.add_child(menuButton); +- +- this._box.destroy(); +- this._boxHolder.destroy(); ++ Main.panel.statusArea.dateMenu._messageList._removeSection(this._section); + }, + + _onHover: function (item) { + if (item.actor.get_hover()) { + if (this._showLabelTimeoutId == 0) { + let timeout = this._labelShowing ? 0 : ITEM_HOVER_TIMEOUT; + this._showLabelTimeoutId = Mainloop.timeout_add(timeout, + Lang.bind(this, function() { + this._labelShowing = true; + item.showLabel(); + return false; + })); + if (this._resetHoverTimeoutId > 0) { + Mainloop.source_remove(this._resetHoverTimeoutId); + this._resetHoverTimeoutId = 0; + } + } + } else { + if (this._showLabelTimeoutId > 0) + Mainloop.source_remove(this._showLabelTimeoutId); + this._showLabelTimeoutId = 0; + item.hideLabel(); + if (this._labelShowing) { + this._resetHoverTimeoutId = Mainloop.timeout_add(ITEM_HOVER_TIMEOUT, + Lang.bind(this, function() { + this._labelShowing = false; + return false; + })); + } + } +diff --git a/extensions/systemMonitor/stylesheet.css b/extensions/systemMonitor/stylesheet.css +index 13f95ec..978ac12 100644 +--- a/extensions/systemMonitor/stylesheet.css ++++ b/extensions/systemMonitor/stylesheet.css +@@ -1,35 +1,21 @@ +-.extension-systemMonitor-container { +- spacing: 5px; +- padding-left: 5px; +- padding-right: 5px; +- padding-bottom: 10px; +- padding-top: 10px; +-} +- + .extension-systemMonitor-indicator-area { +- border: 1px solid #8d8d8d; +- border-radius: 3px; +- width: 100px; +- /* message tray is 72px, so 20px padding of the container, +- 2px of border, makes it 50px */ + height: 50px; + -grid-color: #575757; + -cpu-total-color: rgb(0,154,62); + -cpu-user-color: rgb(69,154,0); + -cpu-sys-color: rgb(255,253,81); + -cpu-iowait-color: rgb(210,148,0); + -mem-user-color: rgb(210,148,0); + -mem-cached-color: rgb(90,90,90); + -mem-other-color: rgb(205,203,41); +- background-color: #1e1e1e; + } + + .extension-systemMonitor-indicator-label { + border-radius: 7px; + padding: 4px 12px; + background-color: rgba(0,0,0,0.9); + text-align: center; + -y-offset: 8px; + font-size: 9pt; + font-weight: bold; + } +-- +2.17.1 + + +From e1133a8a92c49a90e02f8d2f1e66c7aae9d19519 Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Florian=20M=C3=BCllner?= +Date: Thu, 18 May 2017 16:20:07 +0200 +Subject: [PATCH 3/4] systemMonitor: Handle clicks on section title + +While on 3.24.x only the event section still has a clickable title, +it's a generic message list feature in previous versions. It's easy +enough to support with a small subclass, so use that instead of +the generic baseclass. + +Fixes: #3 +--- + extensions/systemMonitor/extension.js | 20 +++++++++++++++++++- + 1 file changed, 19 insertions(+), 1 deletion(-) + +diff --git a/extensions/systemMonitor/extension.js b/extensions/systemMonitor/extension.js +index 1388a1f..9c010d8 100644 +--- a/extensions/systemMonitor/extension.js ++++ b/extensions/systemMonitor/extension.js +@@ -276,75 +276,93 @@ const MemoryIndicator = new Lang.Class({ + // Make sure renderStats is sorted as necessary for rendering + let renderStatOrder = { 'mem-cached': 0, 'mem-other': 1, 'mem-user': 2 }; + this.renderStats = this.renderStats.sort(function(a,b) { + return renderStatOrder[a] - renderStatOrder[b]; + }); + + this.setLabelText(_("Memory")); + }, + + _initValues: function() { + this.mem = new GTop.glibtop_mem; + this.stats = { + 'mem-user': { color: "-mem-user-color", values: [] }, + 'mem-other': { color: "-mem-other-color", values: [] }, + 'mem-cached': { color: "-mem-cached-color", values: [] } + }; + }, + + _updateValues: function() { + GTop.glibtop_get_mem(this.mem); + + let t = this.mem.user / this.mem.total; + this.stats['mem-user'].values.push(t); + t += (this.mem.used - this.mem.user - this.mem.cached) / this.mem.total; + this.stats['mem-other'].values.push(t); + t += this.mem.cached / this.mem.total; + this.stats['mem-cached'].values.push(t); + } + }); + ++const SystemMonitorSection = new Lang.Class({ ++ Name: 'SystemMonitorSection', ++ Extends: MessageList.MessageListSection, ++ ++ _init: function() { ++ this.parent(_("System Monitor")); ++ }, ++ ++ _onTitleClicked: function() { ++ this.parent(); ++ ++ let appSys = Shell.AppSystem.get_default(); ++ let app = appSys.lookup_app('gnome-system-monitor.desktop'); ++ if (app) ++ app.open_new_window(-1); ++ } ++}); ++ + const INDICATORS = [CpuIndicator, MemoryIndicator]; + + const Extension = new Lang.Class({ + Name: 'SystemMonitor.Extension', + + _init: function() { + Convenience.initTranslations(); + + this._showLabelTimeoutId = 0; + this._resetHoverTimeoutId = 0; + this._labelShowing = false; + }, + + enable: function() { +- this._section = new MessageList.MessageListSection(_("System Monitor")); ++ this._section = new SystemMonitorSection(); + this._indicators = [ ]; + + for (let i = 0; i < INDICATORS.length; i++) { + let indicator = new (INDICATORS[i])(); + + indicator.actor.connect('notify::hover', Lang.bind(this, function() { + this._onHover(indicator); + })); + this._section.addMessage(indicator, false); + this._indicators.push(indicator); + } + + Main.panel.statusArea.dateMenu._messageList._addSection(this._section); + this._section.actor.get_parent().set_child_at_index(this._section.actor, 0); + }, + + disable: function() { + this._indicators.forEach(function(i) { i.destroy(); }); + + Main.panel.statusArea.dateMenu._messageList._removeSection(this._section); + }, + + _onHover: function (item) { + if (item.actor.get_hover()) { + if (this._showLabelTimeoutId == 0) { + let timeout = this._labelShowing ? 0 : ITEM_HOVER_TIMEOUT; + this._showLabelTimeoutId = Mainloop.timeout_add(timeout, + Lang.bind(this, function() { + this._labelShowing = true; + item.showLabel(); +-- +2.17.1 + + +From d2a0c7bfdb3fedf56021b6fd64628e4cda1aa294 Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Florian=20M=C3=BCllner?= +Date: Thu, 18 May 2017 18:00:17 +0200 +Subject: [PATCH 4/4] systemMonitor: Provide classic styling + +The indicator tooltips currently don't work out in classic mode +(dark text on dark background), so provide some mode-specific +style. + +Fixes: #4 +--- + extensions/systemMonitor/classic.css | 6 ++++++ + extensions/systemMonitor/meson.build | 4 ++++ + 2 files changed, 10 insertions(+) + create mode 100644 extensions/systemMonitor/classic.css + +diff --git a/extensions/systemMonitor/classic.css b/extensions/systemMonitor/classic.css +new file mode 100644 +index 0000000..946863d +--- /dev/null ++++ b/extensions/systemMonitor/classic.css +@@ -0,0 +1,6 @@ ++@import url("stylesheet.css"); ++ ++.extension-systemMonitor-indicator-label { ++ background-color: rgba(237,237,237,0.9); ++ border: 1px solid #a1a1a1; ++} +diff --git a/extensions/systemMonitor/meson.build b/extensions/systemMonitor/meson.build +index 48504f6..b6548b1 100644 +--- a/extensions/systemMonitor/meson.build ++++ b/extensions/systemMonitor/meson.build +@@ -1,5 +1,9 @@ + extension_data += configure_file( + input: metadata_name + '.in', + output: metadata_name, + configuration: metadata_conf + ) ++ ++if classic_mode_enabled ++ extension_data += files('classic.css') ++endif +-- +2.17.1 + diff --git a/SPECS/gnome-shell-extensions.spec b/SPECS/gnome-shell-extensions.spec new file mode 100644 index 0000000..28084d4 --- /dev/null +++ b/SPECS/gnome-shell-extensions.spec @@ -0,0 +1,985 @@ +%global major_version %%(cut -d "." -f 1-2 <<<%{version}) +# Minimum GNOME Shell version supported +%global min_gs_version %%(cut -d "." -f 1-3 <<<%{version}) + +%global pkg_prefix gnome-shell-extension + +Name: gnome-shell-extensions +Version: 3.28.1 +Release: 10%{?dist} +Summary: Modify and extend GNOME Shell functionality and behavior + +Group: User Interface/Desktops +# The entire source code is GPLv2+ except lib/convenience.js which is BSD +License: GPLv2+ and BSD +URL: http://wiki.gnome.org/Projects/GnomeShell/Extensions +Source0: http://ftp.gnome.org/pub/GNOME/sources/%{name}/%{major_version}/%{name}-%{version}.tar.xz +Source2: https://github.com/sass/sassc/archive/3.4.1.tar.gz +Source3: https://github.com/sass/libsass/archive/3.4.5.tar.gz +# BuildRequires: gnome-common +BuildRequires: meson +BuildRequires: ruby +BuildRequires: gettext >= 0.19.6 +BuildRequires: git +BuildRequires: pkgconfig(gnome-desktop-3.0) +BuildRequires: pkgconfig(libgtop-2.0) +Requires: gnome-shell >= %{min_gs_version} +BuildArch: noarch + +Patch1: 0001-Update-style.patch +Patch2: 0001-apps-menu-add-logo-icon-to-Applications-menu.patch +Patch4: add-extra-extensions.patch +Patch5: 0001-apps-menu-Explicitly-set-label_actor.patch +Patch6: resurrect-system-monitor.patch +Patch7: 0001-data-drop-app-icon-styling.patch +Patch11: 0001-Include-top-icons-in-classic-session.patch +Patch12: 0001-window-list-drop-button-grab-when-leaving-button.patch +Patch13: 0001-Add-extra-osk-keys-extension.patch +Patch14: more-classic-classic-mode.patch +Patch15: 0001-window-list-workspace-indicator-Set-reactiveness-of-.patch + +Patch99: 0001-Revert-data-Remove-nautilus-classic.patch + +%description +GNOME Shell Extensions is a collection of extensions providing additional and +optional functionality to GNOME Shell. + +Enabled extensions: + * alternate-tab + * apps-menu + * auto-move-windows + * drive-menu + * horizontal-workspaces + * launch-new-instance + * native-window-placement + * places-menu + * screenshot-window-sizer + * systemMonitor + * updates-dialog + * user-theme + * window-list + * windowsNavigator + * workspace-indicator + + +%package -n %{pkg_prefix}-common +Summary: Files common to GNOME Shell Extensions +Group: User Interface/Desktops +License: GPLv2+ +Requires: gnome-shell >= %{min_gs_version} +# Dock extension no longer provided by GNOME Shell extensions >= 3.7.1 +Obsoletes: %{pkg_prefix}-dock < 3.7.1 +# Alternative-status-menu extension no longer provided by GNOME Shell extensions >= 3.9.5 +Obsoletes: %{pkg_prefix}-alternative-status-menu < 3.9.5 +# Xrandr-indicator extension no longer provided by GNOME Shell extensions >= 3.9.5 +Obsoletes: %{pkg_prefix}-xrandr-indicator < 3.9.90 +# Obsolete extensions dropped in favor of schema overrides by upstream +Obsoletes: %{pkg_prefix}-default-min-max < 3.9.3-1 +Obsoletes: %{pkg_prefix}-static-workspaces < 3.9.3-1 + +%description -n %{pkg_prefix}-common +GNOME Shell Extensions is a collection of extensions providing additional and +optional functionality to GNOME Shell. + +This package provides common data files shared by various extensions. + + +%package -n gnome-classic-session +Summary: GNOME "classic" mode session +Group: User Interface/Desktops +License: GPLv2+ +Requires: %{pkg_prefix}-alternate-tab = %{version}-%{release} +Requires: %{pkg_prefix}-apps-menu = %{version}-%{release} +Requires: %{pkg_prefix}-horizontal-workspaces = %{version}-%{release} +Requires: %{pkg_prefix}-launch-new-instance = %{version}-%{release} +Requires: %{pkg_prefix}-places-menu = %{version}-%{release} +Requires: %{pkg_prefix}-top-icons = %{version}-%{release} +Requires: %{pkg_prefix}-window-list = %{version}-%{release} +Requires: nautilus +# Obsolete fallback mode components +Obsoletes: gnome-applets < 1:3.5.92-5 +%global gnome_applet_sensors_obsolete_ver 3.0.0-6 +Obsoletes: gnome-applet-sensors < %{gnome_applet_sensors_obsolete_ver} +Obsoletes: gnome-applet-sensors-devel < %{gnome_applet_sensors_obsolete_ver} +%global gnome_panel_obsolete_ver 3.6.2-7 +Obsoletes: gnome-panel < %{gnome_panel_obsolete_ver} +Obsoletes: gnome-panel-devel < %{gnome_panel_obsolete_ver} +Obsoletes: gnome-panel-libs < %{gnome_panel_obsolete_ver} + +%description -n gnome-classic-session +This package contains the required components for the GNOME Shell "classic" +mode, which aims to provide a GNOME 2-like user interface. + + +%package -n %{pkg_prefix}-alternate-tab +Summary: Classic Alt+Tab behavior for GNOME Shell +Group: User Interface/Desktops +License: GPLv2+ +Requires: %{pkg_prefix}-common = %{version}-%{release} + +%description -n %{pkg_prefix}-alternate-tab +This GNOME Shell extension changes Alt+Tab to be window-based instead of +app-based. + + +%package -n %{pkg_prefix}-apps-menu +Summary: Application menu for GNOME Shell +Group: User Interface/Desktops +License: GPLv2+ +Requires: %{pkg_prefix}-common = %{version}-%{release} +Requires: gnome-menus + +%description -n %{pkg_prefix}-apps-menu +This GNOME Shell extension adds a GNOME 2.x style menu for applications. + + +%package -n %{pkg_prefix}-auto-move-windows +Summary: Assign specific workspaces to applications in GNOME Shell +Group: User Interface/Desktops +License: GPLv2+ +Requires: %{pkg_prefix}-common = %{version}-%{release} + +%description -n %{pkg_prefix}-auto-move-windows +This GNOME Shell extension enables easy workspace management. A specific +workspace can be assigned to each application as soon as it creates a window, in +a manner configurable with a GSettings key. + + +%package -n %{pkg_prefix}-dash-to-dock +Summary: Show the dash outside the activities overview +Group: User Interface/Desktops +License: GPLv2+ +Requires: %{pkg_prefix}-common = %{version}-%{release} + +%description -n %{pkg_prefix}-dash-to-dock +This GNOME Shell extension makes the dash available outside the activities overview. + + +%package -n %{pkg_prefix}-disable-screenshield +Summary: Disable GNOME Shell screen shield if lock is disabled +Group: User Interface/Desktops +License: GPLv2+ +Requires: %{pkg_prefix}-common = %{version}-%{release} + +%description -n %{pkg_prefix}-disable-screenshield +This GNOME Shell extension disabled the screen shield if screen locking is disabled. + + +%package -n %{pkg_prefix}-drive-menu +Summary: Drive status menu for GNOME Shell +Group: User Interface/Desktops +License: GPLv2+ +Requires: %{pkg_prefix}-common = %{version}-%{release} + +%description -n %{pkg_prefix}-drive-menu +This GNOME Shell extension provides a panel status menu for accessing and +unmounting removable devices. + + +%package -n %{pkg_prefix}-extra-osk-keys +Summary: Extra Onscreen Keyboard Keys +Group: User Interface/Desktops +License: GPLv2+ +Requires: %{pkg_prefix}-common = %{version}-%{release} + +%description -n %{pkg_prefix}-extra-osk-keys +Adds extra keys to gnome-shell onscreen keyboard. + + +%package -n %{pkg_prefix}-horizontal-workspaces +Summary: Arrange workspaces in GNOME Shell in a single row +Group: User Interface/Desktops +License: GPLv2+ +Requires: %{pkg_prefix}-common = %{version}-%{release} + +%description -n %{pkg_prefix}-horizontal-workspaces +This GNOME Shell extension changes the workspace layout from a single column +to a single row. + + +%package -n %{pkg_prefix}-launch-new-instance +Summary: Always launch a new application instance for GNOME Shell +Group: User Interface/Desktops +License: GPLv2+ +Requires: %{pkg_prefix}-common = %{version}-%{release} + +%description -n %{pkg_prefix}-launch-new-instance +This GNOME Shell extension modifies the behavior of clicking in the dash and app +launcher to always launch a new application instance. + + +%package -n %{pkg_prefix}-native-window-placement +Summary: Native window placement for GNOME Shell +Group: User Interface/Desktops +License: GPLv2+ +Requires: %{pkg_prefix}-common = %{version}-%{release} + +%description -n %{pkg_prefix}-native-window-placement +This GNOME Shell extension provides additional configurability for the window +layout in the overview, including a mechanism similar to KDE4. + + +%package -n %{pkg_prefix}-no-hot-corner +Summary: Disable the hot corner in GNOME Shell +Group: User Interface/Desktops +License: GPLv2+ +Requires: %{pkg_prefix}-common = %{version}-%{release} + +%description -n %{pkg_prefix}-no-hot-corner +This GNOME Shell extension disables the hot corner in the top bar. + + +%package -n %{pkg_prefix}-panel-favorites +Summary: Favorite launchers in GNOME Shell's top bar +Group: User Interface/Desktops +License: GPLv2+ +Requires: %{pkg_prefix}-common = %{version}-%{release} + +%description -n %{pkg_prefix}-panel-favorites +This GNOME Shell extension adds favorite launchers to the top bar. + + +%package -n %{pkg_prefix}-places-menu +Summary: Places status menu for GNOME Shell +Group: User Interface/Desktops +License: GPLv2+ +Requires: %{pkg_prefix}-common = %{version}-%{release} + +%description -n %{pkg_prefix}-places-menu +This GNOME Shell extension add a system status menu for quickly navigating +places in the system. + + +%package -n %{pkg_prefix}-screenshot-window-sizer +Summary: Screenshot window sizer for GNOME Shell +Group: User Interface/Desktops +License: GPLv2+ +Requires: %{pkg_prefix}-common = %{version}-%{release} + +%description -n %{pkg_prefix}-screenshot-window-sizer +This GNOME Shell extension allows to easily resize windows for GNOME Software +screenshots. + + +%package -n %{pkg_prefix}-systemMonitor +Summary: System Monitor for GNOME Shell +Group: User Interface/Desktops +License: GPLv2+ +Requires: %{pkg_prefix}-common = %{version}-%{release} +# Should be pulled in by control-center, but in case someone tries for a +# minimalist gnome-shell installation +Requires: libgtop2 + +%description -n %{pkg_prefix}-systemMonitor +This GNOME Shell extension is a message tray indicator for CPU and memory usage + + + +%package -n %{pkg_prefix}-top-icons +Summary: Show legacy icons on top +Group: User Interface/Desktops +License: GPLv2+ +Requires: %{pkg_prefix}-common = %{version}-%{release} + +%description -n %{pkg_prefix}-top-icons +This GNOME Shell extension moves legacy tray icons into the top bar. + +%package -n %{pkg_prefix}-updates-dialog +Summary: Show a modal dialog when there are software updates +Group: User Interface/Desktops +License: GPLv2+ +Requires: %{pkg_prefix}-common = %{version}-%{release} + +%description -n %{pkg_prefix}-updates-dialog +This GNOME Shell extension shows a modal dialog when there are software updates + +%package -n %{pkg_prefix}-user-theme +Summary: Support for custom themes in GNOME Shell +Group: User Interface/Desktops +License: GPLv2+ +Requires: %{pkg_prefix}-common = %{version}-%{release} + +%description -n %{pkg_prefix}-user-theme +This GNOME Shell extension enables loading a GNOME Shell theme from +~/.themes//gnome-shell/. + + +%package -n %{pkg_prefix}-window-grouper +Summary: Keep windows that belong to the same process on the same workspace +Group: User Interface/Desktops +License: GPLv2+ +Requires: %{pkg_prefix}-common = %{version}-%{release} + +%description -n %{pkg_prefix}-window-grouper +This GNOME Shell extension keeps windows that belong to the same process on the same workspace. + + +%package -n %{pkg_prefix}-window-list +Summary: Display a window list at the bottom of the screen in GNOME Shell +Group: User Interface/Desktops +License: GPLv2+ +Requires: %{pkg_prefix}-common = %{version}-%{release} + +%description -n %{pkg_prefix}-window-list +This GNOME Shell extension displays a window list at the bottom of the screen. + + +%package -n %{pkg_prefix}-windowsNavigator +Summary: Support for keyboard selection of windows and workspaces in GNOME Shell +Group: User Interface/Desktops +License: GPLv2+ +Requires: %{pkg_prefix}-common = %{version}-%{release} + +%description -n %{pkg_prefix}-windowsNavigator +This GNOME Shell extension enables keyboard selection of windows and workspaces +in overlay mode, by pressing the Alt and Ctrl key respectively. + + +%package -n %{pkg_prefix}-workspace-indicator +Summary: Workspace indicator for GNOME Shell +Group: User Interface/Desktops +License: GPLv2+ +Requires: %{pkg_prefix}-common = %{version}-%{release} + +%description -n %{pkg_prefix}-workspace-indicator +This GNOME Shell extension add a system status menu for quickly changing +workspaces. + + +%prep +%setup -q -n libsass-3.4.5 -b3 -T +%setup -q -n sassc-3.4.1 -b2 -T +%autosetup -S git + + +%build +(cd ../libsass-3.4.5; + export LIBSASS_VERSION=3.4.5 + make %{?_smp_mflags}) +(cd ../sassc-3.4.1; + %make_build LDFLAGS="$RPM_OPT_FLAGS $PWD/../libsass-3.4.5/lib/libsass.a" \ + CFLAGS="$RPM_OPT_FLAGS -I$PWD/../libsass-3.4.5/include" \ + CXXFLAGS="$RPM_OPT_FLAGS" \ + SASS_LIBSASS_PATH=$PWD/../libsass-3.4.5) +export PATH=$PWD/../sassc-3.4.1/bin:$PATH + +%meson -Dextension_set="all" -Dclassic_mode=true +%meson_build + +%install +%meson_install + +# Drop useless example extension +rm -r $RPM_BUILD_ROOT%{_datadir}/gnome-shell/extensions/example*/ +rm $RPM_BUILD_ROOT%{_datadir}/glib-2.0/schemas/org.gnome.shell.extensions.example.gschema.xml + +%find_lang %{name} + + +%files -n %{pkg_prefix}-common -f %{name}.lang +%doc COPYING NEWS README.md + + +%files -n gnome-classic-session +%{_datadir}/gnome-session/sessions/gnome-classic.session +%{_datadir}/gnome-shell/modes/classic.json +%{_datadir}/gnome-shell/theme/*.svg +%{_datadir}/gnome-shell/theme/gnome-classic-high-contrast.css +%{_datadir}/gnome-shell/theme/gnome-classic.css +%{_datadir}/xsessions/gnome-classic.desktop +%{_datadir}/glib-2.0/schemas/org.gnome.shell.extensions.classic-overrides.gschema.xml + +%files -n %{pkg_prefix}-alternate-tab +%{_datadir}/gnome-shell/extensions/alternate-tab*/ + + +%files -n %{pkg_prefix}-apps-menu +%{_datadir}/gnome-shell/extensions/apps-menu*/ + + +%files -n %{pkg_prefix}-auto-move-windows +%{_datadir}/glib-2.0/schemas/org.gnome.shell.extensions.auto-move-windows.gschema.xml +%{_datadir}/gnome-shell/extensions/auto-move-windows*/ + +%files -n %{pkg_prefix}-dash-to-dock +%{_datadir}/glib-2.0/schemas/org.gnome.shell.extensions.dash-to-dock.gschema.xml +%{_datadir}/gnome-shell/extensions/dash-to-dock*/ + +%files -n %{pkg_prefix}-disable-screenshield +%{_datadir}/gnome-shell/extensions/disable-screenshield*/ + +%files -n %{pkg_prefix}-drive-menu +%{_datadir}/gnome-shell/extensions/drive-menu*/ + + +%files -n %{pkg_prefix}-extra-osk-keys +%{_datadir}/gnome-shell/extensions/extra-osk-keys*/ + + +%files -n %{pkg_prefix}-horizontal-workspaces +%{_datadir}/gnome-shell/extensions/horizontal-workspaces*/ + + +%files -n %{pkg_prefix}-launch-new-instance +%{_datadir}/gnome-shell/extensions/launch-new-instance*/ + + +%files -n %{pkg_prefix}-native-window-placement +%{_datadir}/glib-2.0/schemas/org.gnome.shell.extensions.native-window-placement.gschema.xml +%{_datadir}/gnome-shell/extensions/native-window-placement*/ + +%files -n %{pkg_prefix}-no-hot-corner +%{_datadir}/gnome-shell/extensions/no-hot-corner*/ + +%files -n %{pkg_prefix}-panel-favorites +%{_datadir}/gnome-shell/extensions/panel-favorites*/ + +%files -n %{pkg_prefix}-places-menu +%{_datadir}/gnome-shell/extensions/places-menu*/ + + +%files -n %{pkg_prefix}-screenshot-window-sizer +%{_datadir}/glib-2.0/schemas/org.gnome.shell.extensions.screenshot-window-sizer.gschema.xml +%{_datadir}/gnome-shell/extensions/screenshot-window-sizer*/ + + +%files -n %{pkg_prefix}-systemMonitor +%{_datadir}/gnome-shell/extensions/systemMonitor*/ + + +%files -n %{pkg_prefix}-top-icons +%{_datadir}/gnome-shell/extensions/top-icons*/ + + +%files -n %{pkg_prefix}-updates-dialog +%{_datadir}/glib-2.0/schemas/org.gnome.shell.extensions.updates-dialog.gschema.xml +%{_datadir}/gnome-shell/extensions/updates-dialog*/ + + +%files -n %{pkg_prefix}-user-theme +%{_datadir}/glib-2.0/schemas/org.gnome.shell.extensions.user-theme.gschema.xml +%{_datadir}/gnome-shell/extensions/user-theme*/ + + +%files -n %{pkg_prefix}-window-grouper +%{_datadir}/gnome-shell/extensions/window-grouper*/ +%{_datadir}/glib-2.0/schemas/org.gnome.shell.extensions.window-grouper.gschema.xml + + +%files -n %{pkg_prefix}-window-list +%{_datadir}/gnome-shell/extensions/window-list*/ +%{_datadir}/glib-2.0/schemas/org.gnome.shell.extensions.window-list.gschema.xml + + +%files -n %{pkg_prefix}-windowsNavigator +%{_datadir}/gnome-shell/extensions/windowsNavigator*/ + + +%files -n %{pkg_prefix}-workspace-indicator +%{_datadir}/gnome-shell/extensions/workspace-indicator*/ + + +%postun -n gnome-classic-session +if [ $1 -eq 0 ]; then + /usr/bin/glib-compile-schemas %{_datadir}/glib-2.0/schemas/ &>/dev/null || : +fi + +%posttrans -n gnome-classic-session +/usr/bin/glib-compile-schemas %{_datadir}/glib-2.0/schemas/ &>/dev/null || : + + +%postun -n %{pkg_prefix}-auto-move-windows +if [ $1 -eq 0 ]; then + /usr/bin/glib-compile-schemas %{_datadir}/glib-2.0/schemas/ &>/dev/null || : +fi + +%posttrans -n %{pkg_prefix}-auto-move-windows +/usr/bin/glib-compile-schemas %{_datadir}/glib-2.0/schemas/ &>/dev/null || : + + +%postun -n %{pkg_prefix}-dash-to-dock +if [ $1 -eq 0 ]; then + /usr/bin/glib-compile-schemas %{_datadir}/glib-2.0/schemas/ &>/dev/null || : +fi + +%posttrans -n %{pkg_prefix}-dash-to-dock +/usr/bin/glib-compile-schemas %{_datadir}/glib-2.0/schemas/ &>/dev/null || : + + +%postun -n %{pkg_prefix}-native-window-placement +if [ $1 -eq 0 ]; then + /usr/bin/glib-compile-schemas %{_datadir}/glib-2.0/schemas/ &>/dev/null || : +fi + +%posttrans -n %{pkg_prefix}-native-window-placement +/usr/bin/glib-compile-schemas %{_datadir}/glib-2.0/schemas/ &>/dev/null || : + + +%postun -n %{pkg_prefix}-screenshot-window-sizer +if [ $1 -eq 0 ]; then + /usr/bin/glib-compile-schemas %{_datadir}/glib-2.0/schemas/ &>/dev/null || : +fi + +%posttrans -n %{pkg_prefix}-screenshot-window-sizer +/usr/bin/glib-compile-schemas %{_datadir}/glib-2.0/schemas/ &>/dev/null || : + + +%postun -n %{pkg_prefix}-updates-dialog +if [ $1 -eq 0 ]; then + /usr/bin/glib-compile-schemas %{_datadir}/glib-2.0/schemas/ &>/dev/null || : +fi + +%posttrans -n %{pkg_prefix}-updates-dialog +/usr/bin/glib-compile-schemas %{_datadir}/glib-2.0/schemas/ &>/dev/null || : + + +%postun -n %{pkg_prefix}-user-theme +if [ $1 -eq 0 ]; then + /usr/bin/glib-compile-schemas %{_datadir}/glib-2.0/schemas/ &>/dev/null || : +fi + +%posttrans -n %{pkg_prefix}-user-theme +/usr/bin/glib-compile-schemas %{_datadir}/glib-2.0/schemas/ &>/dev/null || : + + +%postun -n %{pkg_prefix}-window-grouper +if [ $1 -eq 0 ]; then + /usr/bin/glib-compile-schemas %{_datadir}/glib-2.0/schemas/ &>/dev/null || : +fi + +%posttrans -n %{pkg_prefix}-window-grouper +/usr/bin/glib-compile-schemas %{_datadir}/glib-2.0/schemas/ &>/dev/null || : + + +%postun -n %{pkg_prefix}-window-list +if [ $1 -eq 0 ]; then + /usr/bin/glib-compile-schemas %{_datadir}/glib-2.0/schemas/ &>/dev/null || : +fi + +%posttrans -n %{pkg_prefix}-window-list +/usr/bin/glib-compile-schemas %{_datadir}/glib-2.0/schemas/ &>/dev/null || : + + +%changelog +* Wed Sep 25 2019 Jonas Ådahl - 3.28.1-10 +- Fix unwanted appearance of workspace switcher menu + Resolves: #1752357 + +* Sun Aug 25 2019 Florian Müllner - 3.28.1-9 +- Make classic mode more classic + Resolves: #1720286 + +* Fri Aug 09 2019 Carlos Garnacho - 3.28.1-8 +- Add extra-osk-keys extension + Resolves: #1702417 + +* Tue Mar 26 2019 Florian Müllner - 3.28.1-7 +- Add window-grouper extension + Resolves: #1355845 + +- Add disable-screenshield extension + Resolves: #1643501 + +* Fri Mar 15 2019 Ray Strode - 3.28.1-6 +- Fix stuck grab bug + Resolves: #1659260 + +* Tue Sep 04 2018 Ray Strode - 3.28.1-5 +- Get rid of weird drop shadow next to app menu + Resolves: #1599841 + +* Wed Aug 01 2018 Ray Strode - 3.28.1-4 +- Make icons on desktop default in classic session again + Resolves: #1610477 + +* Fri Jun 22 2018 Florian Müllner - 3.28.1-3 +- Fix a couple of regressions from the rebase: + - add back classic overview style + - update dash-to-dock to a compatible version + Related: #1569717 + +* Mon Jun 11 2018 Ray Strode - 3.28.1-2 +- Import updated styles from gnome-shell + Related: #1569717 + +* Fri Jun 08 2018 Ray Strode - 3.28.1-1 +- Rebase to 3.28.1 + Resolves: #1569717 + +* Fri Feb 23 2018 Florian Müllner - 3.26.2-3 +- Enable top-icons extension in classic mode + Resolves: #1548446 + +* Wed Feb 07 2018 Florian Müllner - 3.26.2-2 +- Fix notification legibility in classic mode + Resolves: #1507457 +- Fix stray icon shadows in classic mode + Resolves: #1530654 + +* Fri Nov 03 2017 Kalev Lember - 3.26.2-1 +- Update to 3.26.2 +- Related: #1505743 + +* Tue Oct 17 2017 Florian Müllner - 3.26.1-1 +- apps-menu: Support separators + Resolves: #1435074 + +* Tue Oct 17 2017 Florian Müllner - 3.26.1-1 +- apps-menu: Follow sort order + Resolves: #1435073 + +* Fri Oct 06 2017 Florian Müllner - 3.26.1-1 +- Update to 3.26.1 + Related: #1481381 + +* Mon Jun 26 2017 Ray Strode - 3.22.2-10 +- Fix pam info messages at the unlock screen + Related: #1449359 + +* Sat Jun 24 2017 Florian Müllner - 3.22.3-9 +- Update dash-to-dock to latest upstream +- Resolves: #1464614 + +* Wed May 17 2017 Florian Müllner - 3.22.3-8 +- Resurrect system monitor extension +- Resolves: #1452319 + +* Fri Apr 28 2017 Florian Müllner - 3.22.3-7 +- Include DND improvements from upstream +- Resolves: #1236601 + +* Wed Apr 05 2017 Florian Müllner - 3.22.2-6 +- Update last patch to not rely on newer JS API +- Resolves: #1236601 + +* Tue Mar 21 2017 Florian Müllner - 3.22.2-5 +- Allow creating desktop launchers via DND from apps menu +- Resolves: #1236601 + +* Tue Mar 14 2017 Florian Müllner - 3.22.2-4 +- Fix downstream branding +- Resolves: #1386960 + +* Tue Mar 14 2017 Florian Müllner - 3.22.2-3 +- Hide workspace indicator in window list if there's just 1 workspace +- Resolves: #1414817 + +* Tue Mar 14 2017 Florian Müllner - 3.22.2-2 +- Re-add downstream patches +- Resolves: #1386960 + +* Wed Nov 16 2016 Kalev Lember - 3.22.2-1 +- Update to 3.22.2 +- Resolves: #1386960 + +* Wed Sep 07 2016 Florian Müllner - 3.14.4-21 +- Improve menu category accessibility further + Related: rhbz#1263128 + +* Fri Jul 08 2016 Florian Müllner - 3.14.4-20 +- Fix handling of menu entries from non-standard locations + Resolves: #1353249 + +* Tue Jun 28 2016 Florian Müllner - 3.14.4-19 +- Update translations + Resolves: #1304265 + +* Fri Jun 10 2016 Florian Müllner - 3.14.4-18 +- Adjust to changes from backported gnome-shell patches + Resolves: #1343953 + +* Thu May 12 2016 Florian Müllner - 3.14.4-17 +- Fix updates-dialog schema errors + Related: rhbz#1302864 + +* Thu Mar 17 2016 Florian Müllner - 3.14.4-16 +- Fix menu category accessibility + Related: rhbz#1263128 + +* Fri Mar 04 2016 Florian Müllner - 3.14.4-15 +- Add updates-dialog extension + Related: rhbz#1302864 + +* Tue Sep 22 2015 Florian Müllner - 3.14.4-14 +- Fix rebase error in last patch series + Related: rhbz#1263368 + +* Fri Sep 18 2015 Florian Müllner - 3.14.4-13 +- Add option to display window-list on all monitors + Resolves: rhbz#1263368 + +* Fri Sep 04 2015 Florian Müllner - 3.14.4-12 +- Fix window list scaling in classic mode + Resolves: rhbz#1229324 + +* Fri Sep 04 2015 Florian Müllner - 3.14.4-11 +- Fix apps-menu taking over panel shortcut + Resolves: rhbz#1255702 + +* Thu Sep 03 2015 Florian Müllner - 3.14.4-10 +- Add back nautilus dependency to classic-session + Resolves: rhbz#1256722 + +* Fri Jul 31 2015 Florian Müllner - 3.14.4-9 +- Number workspaces consistently + Resolves: rhbz#1249018 + +* Thu Jul 30 2015 Florian Müllner - 3.14.4-8 +- Fix window list sorting + Resolves: rhbz#1025370 + +* Fri Jul 24 2015 Florian Müllner - 3.14.4-7 +- Support headless mode + Related: rhbz#1243856 + +* Thu Jun 25 2015 Florian Müllner - 3.14.4-6 +- Scale window list with text + Resolves: rhbz#1229324 + +* Wed Jun 17 2015 Florian Müllner - 3.14.4-5 +- Fix failure of apps-menu to open applications + Resolves: rhbz#1229676 + +* Wed May 20 2015 Florian Müllner - 3.14.4-4 +- Include additional (popular) extensions + Resolves: rhbz#1208513 + +* Thu May 14 2015 Matthias Clasen - 3.14.4-3 +- Make the apps-menu package depend on gnome-menus +- Resolves: #1221531 + +* Tue May 12 2015 Florian Müllner - 3.14.4-2 +- Backport menu arrow fix from 3.16 +- Related: #1174574 + +* Mon Mar 23 2015 Florian Müllner - 3.14.4-1 +- Update to 3.14.4 +- Resolves: #1174574 + +* Tue Oct 07 2014 David King 3.8.4-12 +- Rebuild for libgtop2 soversion change (#1082123) + +* Wed May 28 2014 Lubos Kocman - 3.8.4-11 +- Resolves: #1100332 (bump release against 0day) + +* Tue Feb 19 2014 Florian Müllner - 3.8.4-10 +- Fix odd menu behavior in window list + Resolves: #1025374 + +* Thu Feb 13 2014 Florian Müllner - 3.8.4-9 +- Fix context menu backport + Resolves: #1025374 + +* Mon Feb 10 2014 Debarshi Ray - 3.8.4-8 +- Add Requires: nautilus + Resolves: #1053753 + +* Tue Jan 21 2014 Ray Strode - 3.8.4-7 +- Add logo to apps menu + Resolves: #1052990 + +* Wed Jan 15 2014 Debarshi Ray - 3.8.4-6 +- Shade the overview panel + Resolves: #1053069 + +* Tue Jan 07 2014 Ray Strode - 3.8.4-5 +- Use environment variable for session mode + Related: #1031188 + +* Fri Dec 27 2013 Daniel Mach - 3.8.4-4 +- Mass rebuild 2013-12-27 + +* Thu Dec 12 2013 Matthias Clasen - 3.8.4-3 +- Update translations + Resolves: #1030349 + +* Thu Oct 24 2013 Florian Müllner - 3.8.4-2 +- Backport some classic-mode improvements from 3.10 cycle: + - context menu in window list (close, minimize, maximize) + - use same order for favorites in applications menu as in dash + +* Thu Sep 12 2013 Debarshi Ray - 3.8.4-1 +- Update to 3.8.4 + +* Sun Aug 04 2013 Mohamed El Morabity - 3.8.3.1-1 +- Update to 3.8.3.1 +- Drop places-volume.patch patch, merged upstream + +* Mon Jun 17 2013 Matthias Clasen - 3.8.3-2 +- Fix a problem notices in updates-testing with the places extension + +* Sun Jun 09 2013 Mohamed El Morabity - 3.8.3-1 +- Update to 3.8.3 +- Drop mini-extensions default-min-max and static-workspaces, no longer + available (see https://bugzilla.gnome.org/show_bug.cgi?id=701717) + +* Tue May 14 2013 Mohamed El Morabity - 3.8.2-1 +- Update to 3.8.2 +- Drop useless dependency on libgtop for static-workspaces subpackage + +* Fri May 10 2013 Kalev Lember - 3.8.1-3 +- Obsolete gnome-applet-sensors + +* Wed May 01 2013 Kalev Lember - 3.8.1-2 +- Obsolete a few more fallback mode packages +- Remove gnome-panel provides + +* Tue Apr 16 2013 Matthias Clasen - 3.8.1-1 +- Update to 3.8.1 + +* Tue Mar 26 2013 Mohamed El Morabity - 3.8.0-1 +- Update to 3.8.0 + +* Tue Mar 19 2013 Ray Strode 3.7.92-1 +- Update to 3.7.92 + +* Tue Mar 05 2013 Mohamed El Morabity - 3.7.91-1 +- Update to 3.7.91 + +* Sat Mar 02 2013 Adel Gadllah - 3.7.90-2 +- Obsolete gnome-panel + +* Fri Feb 22 2013 Kalev Lember - 3.7.90-1 +- Update to 3.7.90 + +* Thu Feb 07 2013 Kalev Lember - 3.7.5.1-2 +- Depend on gnome-shell 3.7.5, there's no 3.7.5.1 + +* Thu Feb 07 2013 Mohamed El Morabity - 3.7.5.1-1 +- Update to 3.7.5 +- Enable new launch-new-instance and window-list extensions, and add them in the + classic-mode extension set +- Re-add places-menu in the classic-mode extension set + +* Wed Jan 16 2013 Mohamed El Morabity - 3.7.4-1 +- Update to 3.7.4 +- places-menu extension no longer part of the classic-mode extension set + +* Tue Jan 01 2013 Mohamed El Morabity - 3.7.3-1 +- Update to 3.7.3 +- Enable new default-min-max and static-workspaces extensions +- Provide new subpackage gnome-classic-session +- Revamp summaries and descriptions + +* Tue Oct 30 2012 Mohamed El Morabity - 3.7.1-1 +- Update to 3.7.1 +- Drop dock and gajim extensions, no longer provided + +* Tue Oct 30 2012 Mohamed El Morabity - 3.6.1-1 +- Update to 3.6.1 + +* Tue Oct 02 2012 Mohamed El Morabity - 3.6.0-1 +- Update to 3.6.0 + +* Thu Sep 06 2012 Mohamed El Morabity - 3.5.91-1 +- Update to 3.5.91 + +* Wed Aug 29 2012 Mohamed El Morabity - 3.5.90-1 +- Update to 3.5.90 + +* Sat Aug 11 2012 Mohamed El Morabity - 3.5.5-1 +- Update to 3.5.5 + +* Sun Jul 22 2012 Mohamed El Morabity - 3.5.4-1 +- Update to 3.5.4 + +* Wed Jul 18 2012 Mohamed El Morabity - 3.5.2-1 +- Update to 3.5.2 +- Drop useless Provides/Obsoletes + +* Sat Mar 24 2012 Mohamed El Morabity - 3.4.0-1 +- Update to 3.4.0 +- Minor spec fixes + +* Sat Mar 24 2012 Mohamed El Morabity - 3.3.92-1 +- Update to 3.3.92 + +* Tue Feb 28 2012 Mohamed El Morabity - 3.3.90-1 +- Update to 3.3.90 + +* Thu Feb 16 2012 Mohamed El Morabity - 3.3.5-1 +- Update to 3.3.5 +- Spec cleanup + +* Fri Jan 13 2012 Fedora Release Engineering - 3.3.2-2 +- Rebuilt for https://fedoraproject.org/wiki/Fedora_17_Mass_Rebuild + +* Wed Nov 30 2011 Mohamed El Morabity - 3.3.2-1 +- Update to 3.3.2 + +* Wed Nov 30 2011 Mohamed El Morabity - 3.2.1-1 +- Update to 3.2.1 +- Fix alternative-status-menu extension crash when login + +* Wed Nov 09 2011 Mohamed El Morabity - 3.2.0-2 +- Fix dock and alternate-tab extensions +- Fix GNOME Shell version to work with GS 3.2.1 + +* Mon Oct 03 2011 Mohamed El Morabity - 3.2.0-1 +- Update to 3.2.0 + +* Mon Sep 26 2011 Mohamed El Morabity - 3.1.91-3.20111001gite102c0c6 +- Update to a newer git snapshot +- Fix GNOME Shell version to work with GS 3.2.0 +- Add Requires on GS 3.2.0 or above to gnome-shell-common + +* Wed Sep 14 2011 Mohamed El Morabity - 3.1.91-2 +- Enable xrandr-indicator and workspace-indicator extensions + +* Mon Sep 12 2011 Michel Salim - 3.1.91-1 +- Update to 3.1.91 +- add more documentation + +* Thu Sep 1 2011 Michel Salim - 3.1.4-3.20110830git6b5e3a3e +- Update to git snapshot, for gnome-shell 3.1.90 + +* Sun Aug 21 2011 Michel Salim - 3.1.4-2 +- Enable apps-menu extension +- Spec cleanup + +* Sun Aug 21 2011 Michel Salim - 3.1.4-1 +- Update to 3.1.4 +- Enable systemMonitor extension +- Prepare xrandr-indicator, commenting out since it does not seem to work yet +- Rename subpackages in line with new guidelines (# 715367) +- Sort subpackages in alphabetical order + +* Sat May 28 2011 Timur Kristóf - 3.0.2-1.g63dd27cgit +- Update to a newer git snapshot +- Fix RHBZ bug #708230 +- Enabled systemMonitor extension, but commented out since the requirements are not available + +* Fri May 13 2011 Mohamed El Morabity - 3.0.1-3.03660fgit +- Update to a newer git snapshot +- Enable native-window-placement extension + +* Fri May 06 2011 Rahul Sundaram - 3.0.1-2b20cbagit +- Fix description + +* Thu May 5 2011 Elad Alfassa - 3.0.1-1.b20cbagit +- Update to a newer git snapshot +- Enabled the places-menu extension + +* Tue Apr 26 2011 Mohamed El Morabity - 3.0.1-1.f016b9git +- Update to a newer git snapshot (post-3.0.1 release) +- Enable drive-menu extension + +* Mon Apr 11 2011 Mohamed El Morabity - 3.0.0-5.6d56cfgit +- Enable auto-move-windows extension + +* Sun Apr 10 2011 Rahul Sundaram - 3.0.0-4.6d56cfgit +- Add glib2-devel as build requires + +* Sun Apr 10 2011 Rahul Sundaram - 3.0.0-3.6d56cfgit +- Tweak description +- Fix typo in configure + +* Sun Apr 10 2011 Rahul Sundaram - 3.0.0-2.6d56cfgit +- Added the user-theme extension +- Patch from Timur Kristóf + +* Fri Apr 08 2011 Rahul Sundaram - 3.0.0-1.6d56cfgit +- Make sure configure doesn't get called twice + +* Fri Apr 08 2011 Rahul Sundaram - 3.0.0-0.6d56cfgit +- Initial build