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..9f46e91 --- /dev/null +++ b/SOURCES/0001-Include-top-icons-in-classic-session.patch @@ -0,0 +1,28 @@ +From d5e0f26fc59216da2b2f129d4395a9da0c63e974 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 + +--- + configure.ac | 4 ++-- + 1 file changed, 2 insertions(+), 2 deletions(-) + +diff --git a/configure.ac b/configure.ac +index ddb6422..378783f 100644 +--- a/configure.ac ++++ b/configure.ac +@@ -29,9 +29,9 @@ fi + AC_SUBST([SHELL_VERSION]) + + dnl keep this in alphabetic order +-CLASSIC_EXTENSIONS="apps-menu places-menu alternate-tab launch-new-instance window-list" ++CLASSIC_EXTENSIONS="apps-menu places-menu alternate-tab launch-new-instance top-icons window-list" + DEFAULT_EXTENSIONS="$CLASSIC_EXTENSIONS drive-menu screenshot-window-sizer windowsNavigator workspace-indicator" +-ALL_EXTENSIONS="$DEFAULT_EXTENSIONS auto-move-windows dash-to-dock example native-window-placement no-hot-corner panel-favorites systemMonitor top-icons updates-dialog user-theme" ++ALL_EXTENSIONS="$DEFAULT_EXTENSIONS auto-move-windows dash-to-dock example native-window-placement no-hot-corner panel-favorites systemMonitor updates-dialog user-theme" + AC_SUBST(CLASSIC_EXTENSIONS, [$CLASSIC_EXTENSIONS]) + AC_SUBST(ALL_EXTENSIONS, [$ALL_EXTENSIONS]) + AC_ARG_ENABLE([extensions], +-- +2.14.3 + diff --git a/SOURCES/0001-Update-style.patch b/SOURCES/0001-Update-style.patch new file mode 100644 index 0000000..e92358b --- /dev/null +++ b/SOURCES/0001-Update-style.patch @@ -0,0 +1,36 @@ +From 47d0b2071b07fe70537dde46dc90635e869291f4 Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Florian=20M=C3=BCllner?= +Date: Fri, 6 Oct 2017 19:41:06 +0200 +Subject: [PATCH] Update style + +--- + data/gnome-classic.css | 6 ++++++ + 1 file changed, 6 insertions(+) + +diff --git a/data/gnome-classic.css b/data/gnome-classic.css +index 086fa12..888b695 100644 +--- a/data/gnome-classic.css ++++ b/data/gnome-classic.css +@@ -689,6 +689,9 @@ StScrollBar { + -st-icon-style: symbolic; + margin-left: 4px; + margin-right: 4px; } ++ #panel .panel-button .panel-logo-icon { ++ padding-right: .4em; ++ icon-size: 1em; } + #panel .panel-button .system-status-icon, + #panel .panel-button .app-menu-icon > StIcon, + #panel .panel-button .popup-menu-arrow { +@@ -1714,6 +1717,9 @@ StScrollBar { + padding-bottom: 12px; + spacing: 8px; + width: 23em; } ++ .login-dialog-prompt-layout .login-dialog-timed-login-indicator { ++ height: 2px; ++ background-color: #8b8b8b; } + + .login-dialog-prompt-label { + color: #bebeb6; +-- +2.14.2 + 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..b7a1767 --- /dev/null +++ b/SOURCES/0001-apps-menu-Explicitly-set-label_actor.patch @@ -0,0 +1,40 @@ +From d04e66f692f5022a476c4ad8a9caeb0b5952b7b0 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 41d1faf..03000bf 100644 +--- a/extensions/apps-menu/extension.js ++++ b/extensions/apps-menu/extension.js +@@ -38,7 +38,9 @@ const ActivitiesMenuItem = new Lang.Class({ + _init: function(button) { + this.parent(); + 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: function(event) { +@@ -140,7 +142,9 @@ const CategoryMenuItem = new Lang.Class({ + 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', Lang.bind(this, this._onMotionEvent)); + }, + +-- +2.14.2 + 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-classic-shade-panel-in-overview.patch b/SOURCES/0001-classic-shade-panel-in-overview.patch new file mode 100644 index 0000000..950629f --- /dev/null +++ b/SOURCES/0001-classic-shade-panel-in-overview.patch @@ -0,0 +1,35 @@ +From c948a42b9b5aad6f26a8b8cb61743eedabf3d83f Mon Sep 17 00:00:00 2001 +From: Jakub Steiner +Date: Tue, 14 Jan 2014 17:00:23 +0100 +Subject: [PATCH] classic: shade panel in overview + +- rather than using the top bar styling (negative space), + base the overview panel on the classic grey and "darken" + for overview +--- + data/gnome-classic.css | 8 +++----- + 1 file changed, 3 insertions(+), 5 deletions(-) + +diff --git a/data/gnome-classic.css b/data/gnome-classic.css +index 086fa12..961782f 100644 +--- a/data/gnome-classic.css ++++ b/data/gnome-classic.css +@@ -1891,12 +1891,10 @@ StScrollBar { + border-bottom: 1px solid #666; + app-icon-bottom-clip: 0px; } + #panel:overview { +- background-color: #000; +- background-gradient-end: #000; +- border-top-color: #000; +- border-bottom: 1px solid #000; } ++ background-color: #e0e0e0; ++ background-gradient-end: #d4d4d4; } + #panel:overview .panel-button { +- color: #fff; } ++ color: #222728; } + #panel .panel-button { + -natural-hpadding: 8px; + -minimum-hpadding: 4px; +-- +2.14.2 + diff --git a/SOURCES/0001-loginDialog-make-info-messages-themed.patch b/SOURCES/0001-loginDialog-make-info-messages-themed.patch new file mode 100644 index 0000000..25108d7 --- /dev/null +++ b/SOURCES/0001-loginDialog-make-info-messages-themed.patch @@ -0,0 +1,32 @@ +From 6bb945c95efae345ca388df4a2251a14a55630a5 Mon Sep 17 00:00:00 2001 +From: Ray Strode +Date: Mon, 26 Jun 2017 14:35:05 -0400 +Subject: [PATCH] loginDialog: make info messages themed + +They were lacking a definition before leading them to +show up invisible. +--- + data/gnome-classic.css | 6 ++++-- + 1 file changed, 4 insertions(+), 2 deletions(-) + +diff --git a/data/gnome-classic.css b/data/gnome-classic.css +index d9291a1..4d0d737 100644 +--- a/data/gnome-classic.css ++++ b/data/gnome-classic.css +@@ -1651,9 +1651,11 @@ StScrollBar { + .login-dialog-message-warning { + color: #f57900; } + +-.login-dialog-message-hint { ++.login-dialog-message-hint, .login-dialog-message { ++ color: #bebeb6; + padding-top: 0; +- padding-bottom: 20px; } ++ padding-bottom: 20px; ++ min-height: 2.75em; } + + .login-dialog-user-selection-box { + padding: 100px 0px; } +-- +2.14.2 + diff --git a/SOURCES/add-extra-extensions.patch b/SOURCES/add-extra-extensions.patch new file mode 100644 index 0000000..661d577 --- /dev/null +++ b/SOURCES/add-extra-extensions.patch @@ -0,0 +1,14157 @@ +From 8cf377db7c25c6908803bdf118e8a4c419479b7b 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/5] Add top-icons extension + +--- + configure.ac | 5 +- + extensions/top-icons/Makefile.am | 3 + + extensions/top-icons/extension.js | 225 ++++++++++++++++++++++++++++++++++ + extensions/top-icons/metadata.json.in | 10 ++ + extensions/top-icons/stylesheet.css | 1 + + 5 files changed, 242 insertions(+), 2 deletions(-) + create mode 100644 extensions/top-icons/Makefile.am + create mode 100644 extensions/top-icons/extension.js + create mode 100644 extensions/top-icons/metadata.json.in + create mode 100644 extensions/top-icons/stylesheet.css + +diff --git a/configure.ac b/configure.ac +index 93776e5..8880911 100644 +--- a/configure.ac ++++ b/configure.ac +@@ -31,7 +31,7 @@ AC_SUBST([SHELL_VERSION]) + dnl keep this in alphabetic order + CLASSIC_EXTENSIONS="apps-menu places-menu alternate-tab launch-new-instance window-list" + DEFAULT_EXTENSIONS="$CLASSIC_EXTENSIONS drive-menu screenshot-window-sizer windowsNavigator workspace-indicator" +-ALL_EXTENSIONS="$DEFAULT_EXTENSIONS auto-move-windows example native-window-placement user-theme" ++ALL_EXTENSIONS="$DEFAULT_EXTENSIONS auto-move-windows example native-window-placement top-icons user-theme" + AC_SUBST(CLASSIC_EXTENSIONS, [$CLASSIC_EXTENSIONS]) + AC_SUBST(ALL_EXTENSIONS, [$ALL_EXTENSIONS]) + AC_ARG_ENABLE([extensions], +@@ -63,7 +63,7 @@ ENABLED_EXTENSIONS= + for e in $enable_extensions; do + case $e in + dnl keep this in alphabetic order +- alternate-tab|apps-menu|auto-move-windows|drive-menu|example|launch-new-instance|native-window-placement|places-menu|screenshot-window-sizer|user-theme|window-list|windowsNavigator|workspace-indicator) ++ alternate-tab|apps-menu|auto-move-windows|drive-menu|example|launch-new-instance|native-window-placement|places-menu|screenshot-window-sizer|top-icons|user-theme|window-list|windowsNavigator|workspace-indicator) + ENABLED_EXTENSIONS="$ENABLED_EXTENSIONS $e" + ;; + *) +@@ -87,6 +87,7 @@ AC_CONFIG_FILES([ + extensions/native-window-placement/Makefile + extensions/places-menu/Makefile + extensions/screenshot-window-sizer/Makefile ++ extensions/top-icons/Makefile + extensions/user-theme/Makefile + extensions/window-list/Makefile + extensions/windowsNavigator/Makefile +diff --git a/extensions/top-icons/Makefile.am b/extensions/top-icons/Makefile.am +new file mode 100644 +index 0000000..4650164 +--- /dev/null ++++ b/extensions/top-icons/Makefile.am +@@ -0,0 +1,3 @@ ++EXTENSION_ID = top-icons ++ ++include ../../extension.mk +diff --git a/extensions/top-icons/extension.js b/extensions/top-icons/extension.js +new file mode 100644 +index 0000000..953ea28 +--- /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/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 */ +-- +2.14.2 + + +From 1c3daaf0284f93f8fb0b80acae332c9629aabd6d 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/5] Add dash-to-dock extension + +--- + configure.ac | 5 +- + extensions/dash-to-dock/Makefile.am | 21 + + extensions/dash-to-dock/Settings.ui | 2998 ++++++++++++++++++++ + extensions/dash-to-dock/appIcons.js | 1256 ++++++++ + extensions/dash-to-dock/convenience.js | 74 + + extensions/dash-to-dock/dash.js | 1180 ++++++++ + extensions/dash-to-dock/dockedDash.js | 1684 +++++++++++ + extensions/dash-to-dock/docking.js | 1909 +++++++++++++ + extensions/dash-to-dock/extension.js | 21 + + extensions/dash-to-dock/intellihide.js | 323 +++ + extensions/dash-to-dock/media/logo.svg | 528 ++++ + extensions/dash-to-dock/metadata.json.in | 12 + + ...gnome.shell.extensions.dash-to-dock.gschema.xml | 713 +++++ + extensions/dash-to-dock/prefs.js | 705 +++++ + extensions/dash-to-dock/stylesheet.css | 109 + + extensions/dash-to-dock/theming.js | 293 ++ + extensions/dash-to-dock/utils.js | 123 + + extensions/dash-to-dock/windowPreview.js | 595 ++++ + 18 files changed, 12547 insertions(+), 2 deletions(-) + create mode 100644 extensions/dash-to-dock/Makefile.am + create mode 100644 extensions/dash-to-dock/Settings.ui + 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/dockedDash.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/media/logo.svg + 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/configure.ac b/configure.ac +index 8880911..4178191 100644 +--- a/configure.ac ++++ b/configure.ac +@@ -31,7 +31,7 @@ AC_SUBST([SHELL_VERSION]) + dnl keep this in alphabetic order + CLASSIC_EXTENSIONS="apps-menu places-menu alternate-tab launch-new-instance window-list" + DEFAULT_EXTENSIONS="$CLASSIC_EXTENSIONS drive-menu screenshot-window-sizer windowsNavigator workspace-indicator" +-ALL_EXTENSIONS="$DEFAULT_EXTENSIONS auto-move-windows example native-window-placement top-icons user-theme" ++ALL_EXTENSIONS="$DEFAULT_EXTENSIONS auto-move-windows dash-to-dock example native-window-placement top-icons user-theme" + AC_SUBST(CLASSIC_EXTENSIONS, [$CLASSIC_EXTENSIONS]) + AC_SUBST(ALL_EXTENSIONS, [$ALL_EXTENSIONS]) + AC_ARG_ENABLE([extensions], +@@ -63,7 +63,7 @@ ENABLED_EXTENSIONS= + for e in $enable_extensions; do + case $e in + dnl keep this in alphabetic order +- alternate-tab|apps-menu|auto-move-windows|drive-menu|example|launch-new-instance|native-window-placement|places-menu|screenshot-window-sizer|top-icons|user-theme|window-list|windowsNavigator|workspace-indicator) ++ alternate-tab|apps-menu|auto-move-windows|dash-to-dock|drive-menu|example|launch-new-instance|native-window-placement|places-menu|screenshot-window-sizer|top-icons|user-theme|window-list|windowsNavigator|workspace-indicator) + ENABLED_EXTENSIONS="$ENABLED_EXTENSIONS $e" + ;; + *) +@@ -81,6 +81,7 @@ AC_CONFIG_FILES([ + extensions/alternate-tab/Makefile + extensions/apps-menu/Makefile + extensions/auto-move-windows/Makefile ++ extensions/dash-to-dock/Makefile + extensions/drive-menu/Makefile + extensions/example/Makefile + extensions/launch-new-instance/Makefile +diff --git a/extensions/dash-to-dock/Makefile.am b/extensions/dash-to-dock/Makefile.am +new file mode 100644 +index 0000000..0189a7b +--- /dev/null ++++ b/extensions/dash-to-dock/Makefile.am +@@ -0,0 +1,21 @@ ++EXTENSION_ID = dash-to-dock ++ ++EXTRA_MODULES = \ ++ appIcons.js \ ++ convenience.js \ ++ dash.js \ ++ dockedDash.js \ ++ docking.js \ ++ intellihide.js \ ++ prefs.js \ ++ Settings.ui \ ++ theming.js \ ++ utils.js \ ++ windowPreview.js ++ ++include ../../extension.mk ++include ../../settings.mk ++ ++mediadir = $(extensiondir)/media ++dist_media_DATA = \ ++ media/logo.svg +diff --git a/extensions/dash-to-dock/Settings.ui b/extensions/dash-to-dock/Settings.ui +new file mode 100644 +index 0000000..d8d498f +--- /dev/null ++++ b/extensions/dash-to-dock/Settings.ui +@@ -0,0 +1,2998 @@ ++ ++ ++ ++ ++ ++ 1 ++ 0.050000000000000003 ++ 0.25 ++ ++ ++ 10 ++ 0.250000000000000003 ++ 1 ++ ++ ++ 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 ++ True ++ ++ ++ 1 ++ 0 ++ ++ ++ ++ ++ True ++ False ++ True ++ Customize indicator style ++ fill ++ 0 ++ ++ ++ 0 ++ 0 ++ ++ ++ ++ ++ False ++ True ++ 0 ++ ++ ++ ++ ++ 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 ++ 1 ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ False ++ True ++ 0 ++ ++ ++ ++ ++ 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 ++ 0 ++ Number overlay ++ ++ ++ 0 ++ 0 ++ ++ ++ ++ ++ True ++ False ++ 0 ++ Temporarily show the application numbers over the icons, corresponding to the shortcut. ++ True ++ 40 ++ ++ ++ ++ 0 ++ 1 ++ ++ ++ ++ ++ ++ ++ ++ ++ 100 ++ 80 ++ True ++ True ++ ++ ++ True ++ False ++ 12 ++ 12 ++ 12 ++ 12 ++ 32 ++ ++ ++ True ++ True ++ end ++ center ++ ++ ++ 1 ++ 0 ++ 2 ++ ++ ++ ++ ++ True ++ False ++ True ++ 0 ++ Show the dock if it is hidden ++ ++ ++ 0 ++ 0 ++ ++ ++ ++ ++ True ++ False ++ 0 ++ If using autohide, the dock will appear for a short time when triggering the shortcut. ++ True ++ 40 ++ ++ ++ ++ 0 ++ 1 ++ ++ ++ ++ ++ ++ ++ ++ ++ 100 ++ 80 ++ True ++ True ++ ++ ++ True ++ False ++ 12 ++ 12 ++ 12 ++ 12 ++ 32 ++ ++ ++ 12 ++ center ++ ++ ++ 1 ++ 0 ++ ++ ++ ++ ++ True ++ False ++ True ++ 0 ++ Shortcut for the options above ++ ++ ++ 0 ++ 0 ++ ++ ++ ++ ++ True ++ False ++ 0 ++ 40 ++ Syntax: <Shift>, <Ctrl>, <Alt>, <Super> ++ True ++ ++ ++ ++ 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 ++ 0 ++ Hide timeout (s) ++ ++ ++ 0 ++ 0 ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ False ++ True ++ 0 ++ ++ ++ ++ ++ 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 ++ 0 ++ 40 ++ When set to minimize, double clicking minimizes all the windows of the application. ++ True ++ ++ ++ ++ 0 ++ 1 ++ ++ ++ ++ ++ True ++ False ++ True ++ 0 ++ Shift+Click action ++ ++ ++ 0 ++ 0 ++ ++ ++ ++ ++ True ++ False ++ center ++ ++ Raise window ++ Minimize window ++ Launch new instance ++ Cycle through windows ++ Minimize or overview ++ Show window previews ++ Quit ++ ++ ++ ++ 1 ++ 0 ++ 2 ++ ++ ++ ++ ++ ++ ++ ++ ++ 100 ++ 80 ++ True ++ True ++ ++ ++ True ++ False ++ 12 ++ 12 ++ 12 ++ 12 ++ 32 ++ ++ ++ True ++ False ++ True ++ 0 ++ 40 ++ Behavior for Middle-Click. ++ True ++ ++ ++ ++ 0 ++ 1 ++ ++ ++ ++ ++ True ++ False ++ True ++ 0 ++ Middle-Click action ++ ++ ++ 0 ++ 0 ++ ++ ++ ++ ++ True ++ False ++ center ++ ++ Raise window ++ Minimize window ++ Launch new instance ++ Cycle through windows ++ Minimize or overview ++ Show window previews ++ Quit ++ ++ ++ ++ 1 ++ 0 ++ 2 ++ ++ ++ ++ ++ ++ ++ ++ ++ 100 ++ 80 ++ True ++ True ++ ++ ++ True ++ False ++ 12 ++ 12 ++ 12 ++ 12 ++ 32 ++ ++ ++ True ++ False ++ True ++ 0 ++ 40 ++ Behavior for Shift+Middle-Click. ++ True ++ ++ ++ ++ 0 ++ 1 ++ ++ ++ ++ ++ True ++ False ++ True ++ 0 ++ Shift+Middle-Click action ++ ++ ++ 0 ++ 0 ++ ++ ++ ++ ++ True ++ False ++ center ++ ++ Raise window ++ Minimize window ++ Launch new instance ++ Cycle through windows ++ Minimize or overview ++ Show window previews ++ Quit ++ ++ ++ ++ 1 ++ 0 ++ 2 ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ 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 ++ 0 ++ Show the dock on ++ ++ ++ 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 ++ 0 ++ Position on screen ++ ++ ++ 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 ++ 0 ++ Hide the dock when it obstructs a window of the the current application. More refined settings are available. ++ True ++ ++ ++ ++ 0 ++ 1 ++ ++ ++ ++ ++ True ++ False ++ True ++ 0 ++ Intelligent autohide ++ ++ ++ 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 ++ 0 ++ Dock size limit ++ ++ ++ 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 ++ 0 ++ Icon size limit ++ ++ ++ 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 ++ 0 ++ Show favorite applications ++ ++ ++ 0 ++ 0 ++ ++ ++ ++ ++ ++ ++ ++ ++ True ++ True ++ ++ ++ True ++ False ++ 12 ++ 12 ++ 12 ++ 12 ++ 32 ++ ++ ++ True ++ True ++ end ++ center ++ ++ ++ 1 ++ 0 ++ ++ ++ ++ ++ True ++ False ++ True ++ 0 ++ Show running applications ++ ++ ++ 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 ++ 0 ++ If disabled, these settings are accessible from gnome-tweak-tool or the extension website. ++ True ++ ++ ++ ++ 0 ++ 1 ++ ++ ++ ++ ++ True ++ False ++ True ++ 0 ++ Show <i>Applications</i> icon ++ True ++ ++ ++ 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 ++ 0 ++ Enable Super+(0-9) as shortcuts to activate apps. It can also be used together with Shift and Ctrl. ++ True ++ True ++ ++ ++ ++ 0 ++ 1 ++ ++ ++ ++ ++ True ++ False ++ True ++ 0 ++ Use keyboard shortcuts to activate apps ++ ++ ++ 0 ++ 0 ++ ++ ++ ++ ++ True ++ False ++ 6 ++ ++ ++ True ++ True ++ True ++ center ++ center ++ 0.46000000834465027 ++ ++ ++ True ++ False ++ emblem-system-symbolic ++ ++ ++ ++ ++ ++ False ++ True ++ 0 ++ ++ ++ ++ ++ True ++ True ++ end ++ center ++ ++ ++ ++ ++ 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 ++ 0 ++ Behaviour when clicking on the icon of a running application. ++ True ++ ++ ++ ++ 0 ++ 1 ++ ++ ++ ++ ++ True ++ False ++ True ++ 0 ++ Click action ++ ++ ++ 0 ++ 0 ++ ++ ++ ++ ++ True ++ False ++ 6 ++ ++ ++ True ++ True ++ True ++ center ++ ++ ++ True ++ False ++ emblem-system-symbolic ++ ++ ++ ++ ++ ++ ++ ++ True ++ False ++ center ++ ++ Raise window ++ Minimize ++ Launch new instance ++ Cycle through windows ++ Minimize or overview ++ Show window previews ++ ++ ++ ++ ++ ++ 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 ++ 0 ++ Few customizations meant to integrate the dock with the default GNOME theme. Alternatively, specific options can be enabled below. ++ True ++ ++ ++ ++ 0 ++ 1 ++ ++ ++ ++ ++ True ++ False ++ True ++ 0 ++ Use built-in theme ++ ++ ++ 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 ++ 0 ++ Save space reducing padding and border radius. ++ ++ ++ ++ 0 ++ 1 ++ ++ ++ ++ ++ True ++ False ++ True ++ 0 ++ Shrink the dash ++ ++ ++ 0 ++ 0 ++ ++ ++ ++ ++ ++ ++ ++ ++ True ++ True ++ ++ ++ True ++ False ++ 12 ++ 12 ++ 12 ++ 12 ++ 32 ++ ++ ++ True ++ False ++ True ++ 0 ++ Show a dot for each windows of the application. ++ ++ ++ ++ 0 ++ 1 ++ ++ ++ ++ ++ True ++ False ++ True ++ 0 ++ Show windows counter indicators ++ ++ ++ 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 ++ ++ ++ ++ ++ ++ ++ ++ ++ True ++ True ++ ++ ++ True ++ False ++ 12 ++ 12 ++ 12 ++ 12 ++ 32 ++ ++ ++ True ++ False ++ True ++ 0 ++ Set the background color for the dash. ++ True ++ ++ ++ ++ 0 ++ 1 ++ ++ ++ ++ ++ True ++ False ++ True ++ 0 ++ Customize the dash color ++ ++ ++ 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 ++ True ++ end ++ center ++ ++ ++ 1 ++ 0 ++ 2 ++ ++ ++ ++ ++ True ++ False ++ True ++ 0 ++ Tune the dash background opacity. ++ ++ ++ ++ 0 ++ 1 ++ ++ ++ ++ ++ True ++ False ++ True ++ 0 ++ Customize opacity ++ ++ ++ 0 ++ 0 ++ ++ ++ ++ ++ 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 ++ ++ ++ ++ ++ 1000 ++ 50 ++ 250 ++ ++ ++ 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 ++ 0 ++ Show the dock by mouse hover on the screen edge. ++ True ++ ++ ++ ++ 0 ++ 1 ++ ++ ++ ++ ++ True ++ False ++ True ++ 0 ++ Autohide ++ ++ ++ 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 ++ 0 ++ Show the dock when it doesn't obstruct application windows. ++ True ++ ++ ++ ++ 0 ++ 1 ++ ++ ++ ++ ++ True ++ False ++ True ++ 0 ++ Dodge windows ++ ++ ++ 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 ++ 0 ++ Animation duration (s) ++ ++ ++ 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 ++ 0 ++ Hide timeout (s) ++ ++ ++ 0 ++ 1 ++ ++ ++ ++ ++ True ++ False ++ True ++ 0 ++ Show timeout (s) ++ ++ ++ 0 ++ 2 ++ ++ ++ ++ ++ True ++ False ++ True ++ 0 ++ Pressure threshold ++ ++ ++ 0 ++ 3 ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ False ++ True ++ 0 ++ ++ ++ ++ +diff --git a/extensions/dash-to-dock/appIcons.js b/extensions/dash-to-dock/appIcons.js +new file mode 100644 +index 0000000..ac90585 +--- /dev/null ++++ b/extensions/dash-to-dock/appIcons.js +@@ -0,0 +1,1256 @@ ++// -*- 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; ++ ++// 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; ++ ++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, ++ QUIT: 6 ++}; ++ ++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); ++ * - Draw a dot for each window of the application based on 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 ++ */ ++const MyAppIcon = new Lang.Class({ ++ Name: 'DashToDock.AppIcon', ++ Extends: AppDisplay.AppIcon, ++ ++ // settings are required inside. ++ _init: function(settings, 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._nWindows = 0; ++ ++ this.parent(app, iconParams); ++ ++ // 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._stateChangedId = this.app.connect('windows-changed', ++ Lang.bind(this, ++ this.onWindowsChanged)); ++ this._focusAppChangeId = tracker.connect('notify::focus-app', ++ Lang.bind(this, ++ this._onFocusAppChanged)); ++ this._enteredMonitorId = global.screen.connect('window-entered-monitor', ++ Lang.bind(this, ++ this.onWindowsChanged)); ++ ++ this._dots = null; ++ ++ let keys = ['apply-custom-theme', ++ 'custom-theme-running-dots', ++ 'custom-theme-customize-running-dots', ++ 'custom-theme-running-dots-color', ++ 'custom-theme-running-dots-border-color', ++ 'custom-theme-running-dots-border-width']; ++ ++ keys.forEach(function(key) { ++ this._signalsHandler.add([ ++ this._dtdSettings, ++ 'changed::' + key, ++ Lang.bind(this, this._toggleDots) ++ ]); ++ }, this); ++ ++ this._toggleDots(); ++ ++ 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 ++ // stateChangedId is already handled by parent) ++ if (this._focusAppChangeId > 0) { ++ tracker.disconnect(this._focusAppChangeId); ++ this._focusAppChangeId = 0; ++ } ++ ++ if (this._enteredMonitorId > 0) { ++ global.screen.disconnect(this._enteredMonitorId); ++ this._enteredMonitorId = 0; ++ } ++ ++ this._signalsHandler.destroy(); ++ ++ if (this._scrollEventHandler) ++ this.actor.disconnect(this._scrollEventHandler); ++ }, ++ ++ _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._updateRunningStyle(); ++ 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); ++ }); ++ }, ++ ++ _toggleDots: function() { ++ if (this._dtdSettings.get_boolean('custom-theme-running-dots') || this._dtdSettings.get_boolean('apply-custom-theme')) ++ this._showDots(); ++ else ++ this._hideDots(); ++ }, ++ ++ _showDots: function() { ++ // I use opacity to hide the default dot because the show/hide function ++ // are used by the parent class. ++ this._dot.opacity = 0; ++ ++ // Just update style if dots already exist ++ if (this._dots) { ++ this._updateCounterClass(); ++ return; ++ } ++ ++ this._dots = new St.DrawingArea({x_expand: true, y_expand: true}); ++ this._dots.connect('repaint', Lang.bind(this, function() { ++ this._drawCircles(this._dots, Utils.getPosition(this._dtdSettings)); ++ })); ++ this._iconContainer.add_child(this._dots); ++ this._updateCounterClass(); ++ }, ++ ++ _hideDots: function() { ++ this._dot.opacity = 255; ++ if (this._dots) ++ this._dots.destroy() ++ this._dots = null; ++ }, ++ ++ _updateRunningStyle: function() { ++ // When using workspace isolation, we need to hide the dots of apps with ++ // no windows in the current workspace ++ if (this._dtdSettings.get_boolean('isolate-workspaces') || ++ this._dtdSettings.get_boolean('isolate-monitors')) { ++ if (this.app.state != Shell.AppState.STOPPED ++ && this.getInterestingWindows().length != 0) ++ this._dot.show(); ++ else ++ this._dot.hide(); ++ } ++ else ++ this.parent(); ++ this._onFocusAppChanged(); ++ this._updateCounterClass(); ++ }, ++ ++ 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() { ++ // We need to check the number of windows, as the focus might be ++ // happening on another monitor if using isolation ++ if (tracker.focus_app == this.app && this.getInterestingWindows().length != 0) ++ this.actor.add_style_class_name('focused'); ++ else ++ this.actor.remove_style_class_name('focused'); ++ }, ++ ++ activate: function(button) { ++ ++ if (!this._dtdSettings.get_boolean('customize-click')) { ++ this.parent(button); ++ return; ++ } ++ ++ 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.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(); ++ } ++ }, ++ ++ _updateCounterClass: function() { ++ let maxN = 4; ++ this._nWindows = Math.min(this.getInterestingWindows().length, maxN); ++ ++ for (let i = 1; i <= maxN; i++) { ++ let className = 'running' + i; ++ if (i != this._nWindows) ++ this.actor.remove_style_class_name(className); ++ else ++ this.actor.add_style_class_name(className); ++ } ++ ++ if (this._dots) ++ this._dots.queue_repaint(); ++ }, ++ ++ _drawCircles: function(area, side) { ++ let borderColor, borderWidth, bodyColor; ++ ++ if (!this._dtdSettings.get_boolean('apply-custom-theme') ++ && this._dtdSettings.get_boolean('custom-theme-running-dots') ++ && this._dtdSettings.get_boolean('custom-theme-customize-running-dots')) { ++ borderColor = Clutter.color_from_string(this._dtdSettings.get_string('custom-theme-running-dots-border-color'))[1]; ++ borderWidth = this._dtdSettings.get_int('custom-theme-running-dots-border-width'); ++ bodyColor = Clutter.color_from_string(this._dtdSettings.get_string('custom-theme-running-dots-color'))[1]; ++ } ++ else { ++ // Re-use the style - background color, and border width and color - ++ // of the default dot ++ let themeNode = this._dot.get_theme_node(); ++ borderColor = themeNode.get_border_color(side); ++ borderWidth = themeNode.get_border_width(side); ++ bodyColor = themeNode.get_background_color(); ++ } ++ ++ let [width, height] = area.get_surface_size(); ++ let cr = area.get_context(); ++ ++ // Draw the required numbers of dots ++ // Define the radius as an arbitrary size, but keep large enough to account ++ // for the drawing of the border. ++ let radius = Math.max(width/22, borderWidth/2); ++ let padding = 0; // distance from the margin ++ let spacing = radius + borderWidth; // separation between the dots ++ let n = this._nWindows; ++ ++ cr.setLineWidth(borderWidth); ++ Clutter.cairo_set_source_color(cr, borderColor); ++ ++ switch (side) { ++ case St.Side.TOP: ++ cr.translate((width - (2*n)*radius - (n-1)*spacing)/2, padding); ++ for (let i = 0; i < n; i++) { ++ cr.newSubPath(); ++ cr.arc((2*i+1)*radius + i*spacing, radius + borderWidth/2, radius, 0, 2*Math.PI); ++ } ++ break; ++ ++ case St.Side.BOTTOM: ++ cr.translate((width - (2*n)*radius - (n-1)*spacing)/2, height - padding); ++ for (let i = 0; i < n; i++) { ++ cr.newSubPath(); ++ cr.arc((2*i+1)*radius + i*spacing, -radius - borderWidth/2, radius, 0, 2*Math.PI); ++ } ++ break; ++ ++ case St.Side.LEFT: ++ cr.translate(padding, (height - (2*n)*radius - (n-1)*spacing)/2); ++ for (let i = 0; i < n; i++) { ++ cr.newSubPath(); ++ cr.arc(radius + borderWidth/2, (2*i+1)*radius + i*spacing, radius, 0, 2*Math.PI); ++ } ++ break; ++ ++ case St.Side.RIGHT: ++ cr.translate(width - padding , (height - (2*n)*radius - (n-1)*spacing)/2); ++ for (let i = 0; i < n; i++) { ++ cr.newSubPath(); ++ cr.arc(-radius - borderWidth/2, (2*i+1)*radius + i*spacing, radius, 0, 2*Math.PI); ++ } ++ break; ++ } ++ ++ cr.strokePreserve(); ++ ++ Clutter.cairo_set_source_color(cr, bodyColor); ++ cr.fill(); ++ cr.$dispose(); ++ }, ++ ++ _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; ++} ++ ++/** ++ * Extend ShowAppsIcon ++ * ++ * - Pass settings to the constructor ++ * - set label position based on dash orientation ++ * - implement a popupMenu based on the AppIcon code ++ * ++ * 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 extendShowAppsIcon(showAppsIcon, settings) { ++ showAppsIcon._dtdSettings = settings; ++ /* the variable equivalent to toggleButton has a different name in the appIcon class ++ (actor): duplicate reference to easily reuse appIcon methods */ ++ showAppsIcon.actor = showAppsIcon.toggleButton; ++ ++ // Re-use appIcon methods ++ showAppsIcon._removeMenuTimeout = AppDisplay.AppIcon.prototype._removeMenuTimeout; ++ showAppsIcon._setPopupTimeout = AppDisplay.AppIcon.prototype._setPopupTimeout; ++ showAppsIcon._onButtonPress = AppDisplay.AppIcon.prototype._onButtonPress; ++ showAppsIcon._onKeyboardPopupMenu = AppDisplay.AppIcon.prototype._onKeyboardPopupMenu; ++ showAppsIcon._onLeaveEvent = AppDisplay.AppIcon.prototype._onLeaveEvent; ++ showAppsIcon._onTouchEvent = AppDisplay.AppIcon.prototype._onTouchEvent; ++ showAppsIcon._onMenuPoppedDown = AppDisplay.AppIcon.prototype._onMenuPoppedDown; ++ ++ ++ // No action on clicked (showing of the appsview is controlled elsewhere) ++ showAppsIcon._onClicked = function(actor, button) { ++ showAppsIcon._removeMenuTimeout(); ++ }; ++ ++ showAppsIcon.actor.connect('leave-event', Lang.bind(showAppsIcon, showAppsIcon._onLeaveEvent)); ++ showAppsIcon.actor.connect('button-press-event', Lang.bind(showAppsIcon, showAppsIcon._onButtonPress)); ++ showAppsIcon.actor.connect('touch-event', Lang.bind(showAppsIcon, showAppsIcon._onTouchEvent)); ++ showAppsIcon.actor.connect('clicked', Lang.bind(showAppsIcon, showAppsIcon._onClicked)); ++ showAppsIcon.actor.connect('popup-menu', Lang.bind(showAppsIcon, showAppsIcon._onKeyboardPopupMenu)); ++ ++ showAppsIcon._menu = null; ++ showAppsIcon._menuManager = new PopupMenu.PopupMenuManager(showAppsIcon); ++ showAppsIcon._menuTimeoutId = 0; ++ ++ showAppsIcon.showLabel = itemShowLabel; ++ ++ showAppsIcon.popupMenu = function() { ++ showAppsIcon._removeMenuTimeout(); ++ showAppsIcon.actor.fake_release(); ++ ++ if (!showAppsIcon._menu) { ++ showAppsIcon._menu = new MyShowAppsIconMenu(showAppsIcon, showAppsIcon._dtdSettings); ++ showAppsIcon._menu.connect('open-state-changed', Lang.bind(showAppsIcon, function(menu, isPoppedUp) { ++ if (!isPoppedUp) ++ showAppsIcon._onMenuPoppedDown(); ++ })); ++ let id = Main.overview.connect('hiding', Lang.bind(showAppsIcon, function() { ++ showAppsIcon._menu.close(); ++ })); ++ showAppsIcon._menu.actor.connect('destroy', function() { ++ Main.overview.disconnect(id); ++ }); ++ showAppsIcon._menuManager.addMenu(showAppsIcon._menu); ++ } ++ ++ showAppsIcon.emit('menu-state-changed', true); ++ ++ showAppsIcon.actor.set_hover(true); ++ showAppsIcon._menu.popup(); ++ showAppsIcon._menuManager.ignoreRelease(); ++ showAppsIcon.emit('sync-tooltip'); ++ ++ return false; ++ }; ++ ++ Signals.addSignalMethods(showAppsIcon); ++} ++ ++/** ++ * 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..9c912bf +--- /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: ++ * http://git.gnome.org/browse/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..593185a +--- /dev/null ++++ b/extensions/dash-to-dock/dash.js +@@ -0,0 +1,1180 @@ ++// -*- 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. ++ */ ++const MyDash = new Lang.Class({ ++ Name: 'DashToDock.MyDash', ++ ++ _init: function(settings, monitorIndex) { ++ this._maxHeight = -1; ++ this.iconSize = 64; ++ this._availableIconSizes = baseIconSizes; ++ this._shownInitially = false; ++ ++ this._dtdSettings = settings; ++ 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); ++ ++ this._showAppsIcon = new Dash.ShowAppsIcon(); ++ AppIcons.extendShowAppsIcon(this._showAppsIcon, this._dtdSettings); ++ this._showAppsIcon.childScale = 1; ++ this._showAppsIcon.childOpacity = 255; ++ this._showAppsIcon.icon.setIconSize(this.iconSize); ++ this._hookUpLabel(this._showAppsIcon); ++ ++ let appsIcon = this._showAppsIcon; ++ appsIcon.connect('menu-state-changed', Lang.bind(this, function(appsIcon, opened) { ++ this._itemMenuStateChanged(appsIcon, opened); ++ })); ++ ++ 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; ++ ++ // 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 ((this._position == St.Side.LEFT && event_x <= 1) ++ || (this._position == St.Side.RIGHT && event_x >= actor_w - 2) ++ || (this._position == St.Side.TOP && event_y <= 1) ++ || (this._position == St.Side.BOTTOM && event_y >= actor_h - 2)) ++ 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, 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); ++ }); ++ }, ++ ++ setIconSize: function(max_size, doNotAnimate) { ++ 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/dockedDash.js b/extensions/dash-to-dock/dockedDash.js +new file mode 100644 +index 0000000..1e145c6 +--- /dev/null ++++ b/extensions/dash-to-dock/dockedDash.js +@@ -0,0 +1,1684 @@ ++// -*- 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 MessageTray = imports.ui.messageTray; ++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.myConvenience; ++const Intellihide = Me.imports.intellihide; ++const MyDash = Me.imports.myDash; ++ ++const DOCK_DWELL_CHECK_INTERVAL = 100; //TODO ++ ++const State = { ++ HIDDEN: 0, ++ SHOWING: 1, ++ SHOWN: 2, ++ HIDING: 3 ++}; ++ ++/* 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; ++} ++ ++/* ++ * 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: '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: 'dockedDash', ++ ++ _init: function(settings) { ++ ++ this._rtl = Clutter.get_default_text_direction() == Clutter.TextDirection.RTL; ++ ++ // Load settings ++ this._settings = settings; ++ this._bindSettingsChanges(); ++ ++ this._position = 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); ++ ++ // initialize dock state ++ this._dockState = State.HIDDEN; ++ ++ /* status variable: true when the overview is shown through the dash ++ * applications button. ++ */ ++ this.forcedOverview = false; ++ ++ // Put dock on the primary monitor ++ this._monitor = Main.layoutManager.primaryMonitor; ++ ++ // 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._messageTrayShowing = false; ++ 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); ++ ++ // set stored icon size to the new dash ++ Main.overview.dashIconSize = this.dash.iconSize; ++ ++ // connect app icon into the view selector ++ this.dash.showAppsButton.connect('notify::checked', Lang.bind(this, this._onShowAppsButtonToggled)); ++ ++ // 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); ++ ++ // Connect global signals ++ this._signalsHandler = new Convenience.GlobalSignalsHandler(); ++ 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 wne monitor changes, for instance in multimonitor when monitor are attached ++ [ ++ global.screen, ++ 'monitors-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) ++ ], ++ [ ++ Main.messageTray, ++ 'showing', ++ Lang.bind(this, this._onMessageTrayShowing) ++ ], ++ [ ++ Main.messageTray, ++ 'hiding', ++ Lang.bind(this, this._onMessageTrayHiding) ++ ], ++ // 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 Convenience.InjectionsHandler(); ++ this._themeManager = new themeManager(this._settings, this.actor, this.dash); ++ ++ // 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();})); ++ ++ // Restore dash accessibility ++ Main.ctrlAltTabManager.addGroup( ++ this.dash.actor, _("Dash"),'user-bookmarks-symbolic', ++ {focusCallback: Lang.bind(this, this._onAccessibilityFocus)}); ++ ++ // Load optional features ++ 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)); ++ ++ // 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); ++ ++ // 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); ++ ++ // shift overview messageIndicator for bottom dock ++ this._oldMessageIndicatorPosition = Main.overview._controls._indicator.actor.get_first_child().y; ++ if (this._position == St.Side.BOTTOM) { ++ this._dashSpacer.connect('notify::height', Lang.bind(this, function(){ ++ Main.overview._controls._indicator.actor.get_first_child().y = -this._dashSpacer.height; ++ })); ++ } ++ ++ 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 (old affectsinputRegion: false that was removed). ++ // The public method trackChrome requires the actor to be child of a tracked actor. Since I don't want the parent ++ // to be tracked I use the private internal _trackActor instead. ++ Main.uiGroup.add_child(this.actor); ++ Main.layoutManager._trackActor(this._slider.actor, {trackFullscreen: true}); ++ ++ // Keep the dash below the modalDialogGroup ++ Main.layoutManager.uiGroup.set_child_below_sibling(this.actor,Main.layoutManager.modalDialogGroup); ++ ++ if ( this._settings.get_boolean('dock-fixed') ) ++ Main.layoutManager._trackActor(this.dash.actor, {affectsStruts: true}); ++ ++ // pretend this._slider is isToplevel child so that fullscreen is actually tracked ++ let index = Main.layoutManager._findActor(this._slider.actor); ++ Main.layoutManager._trackedActors[index].isToplevel = true ; ++ ++ // Set initial position ++ this._resetPosition(); ++ ++ }, ++ ++ _initialize: function(){ ++ ++ if(this._paintId>0){ ++ this.actor.disconnect(this._paintId); ++ this._paintId=0; ++ } ++ ++ this.dash.setIconSize(this._settings.get_int('dash-max-icon-size'), true); ++ ++ // 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(); ++ ++ // Setup pressure barrier (GS38+ only) ++ this._updatePressureBarrier(); ++ this._updateBarrier(); ++ ++ // setup dwelling system if pressure barriers are not available ++ this._setupDockDwellIfNeeded(); ++ ++ // Insensitive Message Tray ++ this._updateInsensitiveTray(); ++ }, ++ ++ destroy: function(){ ++ ++ // Disconnect global signals ++ this._signalsHandler.destroy(); ++ // The dash and intellihide have global signals as well internally ++ this.dash.destroy(); ++ this._intellihide.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 messageIndicator position ++ Main.overview._controls._indicator.actor.get_first_child().y = this._oldMessageIndicatorPosition; ++ ++ // Reshow normal dash previously hidden, restore panel position if changed. ++ 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; ++ // Reshow panel corners ++ this._revertPanelCorners(); ++ }, ++ ++ _bindSettingsChanges: function() { ++ ++ this._settings.connect('changed::scroll-switch-workspace', Lang.bind(this, function(){ ++ this._optionalScrollWorkspaceSwitch(this._settings.get_boolean('scroll-switch-workspace')); ++ })); ++ ++ this._settings.connect('changed::dash-max-icon-size', Lang.bind(this, function(){ ++ this.dash.setIconSize(this._settings.get_int('dash-max-icon-size')); ++ })); ++ ++ this._settings.connect('changed::icon-size-fixed', Lang.bind(this, function(){ ++ this.dash.setIconSize(this._settings.get_int('dash-max-icon-size')); ++ })); ++ ++ this._settings.connect('changed::show-running', Lang.bind(this, function(){ ++ this.dash.resetAppIcons(); ++ })); ++ ++ this._settings.connect('changed::show-apps-at-top', Lang.bind(this, function(){ ++ this.dash.resetAppIcons(); ++ })); ++ ++ this._settings.connect('changed::dock-fixed', Lang.bind(this, function(){ ++ ++ if(this._settings.get_boolean('dock-fixed')) { ++ Main.layoutManager._trackActor(this.dash.actor, {affectsStruts: true}); ++ } else { ++ Main.layoutManager._untrackActor(this.dash.actor); ++ } ++ ++ this._resetPosition(); ++ ++ // Add or remove barrier depending on if dock-fixed ++ this._updateBarrier(); ++ ++ this._updateVisibilityMode(); ++ })); ++ ++ this._settings.connect('changed::intellihide', Lang.bind(this, this._updateVisibilityMode)); ++ ++ this._settings.connect('changed::intellihide-perapp', Lang.bind(this, function(){ ++ this._intellihide.forceUpdate(); ++ })); ++ ++ this._settings.connect('changed::autohide', Lang.bind(this, function(){ ++ this._updateVisibilityMode(); ++ this._updateBarrier(); ++ })); ++ this._settings.connect('changed::extend-height', Lang.bind(this,this._resetPosition)); ++ this._settings.connect('changed::preferred-monitor', Lang.bind(this,this._resetPosition)); ++ this._settings.connect('changed::height-fraction', Lang.bind(this,this._resetPosition)); ++ this._settings.connect('changed::insensitive-message-tray', Lang.bind(this,this._updateInsensitiveTray)); ++ this._settings.connect('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.connect('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(); ++ } ++ } ++ } ++ }, ++ ++ _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; ++ 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 monitor = this._monitor; ++ ++ // Check for the dock area ++ let shouldDwell = (x >= this.staticBox.x1 && x <= this.staticBox.x2 && ++ y >= this.staticBox.y1 && y <= this.staticBox.y2); ++ ++ // Check for the correct screen edge ++ // Position is approximated to the lower integer ++ if(this._position==St.Side.LEFT){ ++ shouldDwell = shouldDwell && x == this._monitor.x; ++ } else if(this._position==St.Side.RIGHT) { ++ shouldDwell = shouldDwell && x == this._monitor.x + this._monitor.width - 1; ++ } else if(this._position==St.Side.TOP) { ++ shouldDwell = shouldDwell && y == this._monitor.y; ++ } else if (this._position==St.Side.BOTTOM) { ++ shouldDwell = shouldDwell && y == this._monitor.y + this._monitor.height - 1; ++ } ++ ++ 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._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; ++ } ++ ++ // 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.KeyBindingMode.NORMAL | Shell.KeyBindingMode.OVERVIEW); ++ this._pressureBarrier.connect('trigger', Lang.bind(this, function(barrier){ ++ if (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; ++ this._hoverChanged(); ++ return GLib.SOURCE_REMOVE; ++ })); ++ ++ this._show(); ++ }, ++ ++ _onMessageTrayShowing: function() { ++ ++ // Temporary move the dash below the top panel so that it slide below it. ++ Main.layoutManager.uiGroup.set_child_below_sibling(this.actor, Main.layoutManager.panelBox); ++ ++ // Remove other tweens that could mess with the state machine ++ Tweener.removeTweens(this.actor); ++ this._oldignoreHover = this._ignoreHover; ++ this._ignoreHover = true; ++ this.dash.cleanUpLabels(); ++ Tweener.addTween(this.actor, { ++ y: this._y0 - Main.messageTray.actor.height, ++ time: MessageTray.ANIMATION_TIME, ++ transition: 'easeOutQuad' ++ }); ++ this._messageTrayShowing = true; ++ this._updateBarrier(); ++ }, ++ ++ _onMessageTrayHiding: function() { ++ ++ // Remove other tweens that could mess with the state machine ++ Tweener.removeTweens(this.actor); ++ Tweener.addTween(this.actor, { ++ y: this._y0, ++ time: MessageTray.ANIMATION_TIME, ++ transition: 'easeOutQuad', ++ onComplete: Lang.bind(this, function(){ ++ // Reset desired dash stack order (on top to accept dnd of app icons) ++ Main.layoutManager.uiGroup.set_child_below_sibling(this.actor, Main.layoutManager.modalDialogGroup); ++ // restore previous ignoreHover. If it was not set, set it to false ++ if (this._oldignoreHover !== null) ++ this._ignoreHover = this._oldignoreHover; ++ this._oldignoreHover == null; ++ if (!isMouseHover(this._box)) ++ this._box.hover = false; ++ }) ++ }); ++ ++ this._messageTrayShowing = false; ++ this._updateBarrier(); ++ }, ++ ++ // 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(); ++ ++ // 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 ++ // Note: dash in fixed position doesn't use pressure barrier ++ if (this._slider.actor.visible && this._canUsePressure && this._autohideIsEnabled && this._settings.get_boolean('require-pressure-to-show') && !this._messageTrayShowing) { ++ let x1, x2, y1, y2, direction; ++ ++ if(this._position==St.Side.LEFT){ ++ x1 = this.staticBox.x1; ++ x2 = this.staticBox.x1; ++ y1 = this.staticBox.y1; ++ y2 = this.staticBox.y2; ++ direction = Meta.BarrierDirection.POSITIVE_X; ++ } else if(this._position==St.Side.RIGHT) { ++ x1 = this.staticBox.x2; ++ x2 = this.staticBox.x2; ++ y1 = this.staticBox.y1; ++ y2 = this.staticBox.y2; ++ direction = Meta.BarrierDirection.NEGATIVE_X; ++ } else if(this._position==St.Side.TOP) { ++ x1 = this.staticBox.x1; ++ x2 = this.staticBox.x2; ++ y1 = this.staticBox.y1; ++ y2 = this.staticBox.y1; ++ direction = Meta.BarrierDirection.POSITIVE_Y; ++ } else if (this._position==St.Side.BOTTOM) { ++ x1 = this.staticBox.x1; ++ x2 = this.staticBox.x2; ++ y1 = this.staticBox.y2; ++ y2 = this.staticBox.y2; ++ 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._monitor.x == Main.layoutManager.primaryMonitor.x && ++ this._monitor.y == Main.layoutManager.primaryMonitor.y); ++ }, ++ ++ _resetPosition: function() { ++ ++ // Ensure variables linked to settings are updated. ++ this._updateVisibilityMode(); ++ ++ this._monitor = this._getMonitor(); ++ ++ let unavailableTopSpace = 0; ++ let unavailableBottomSpace = 0; ++ ++ let extendHeight = this._settings.get_boolean('extend-height'); ++ ++ // Reserve space for the dash on the overview ++ // if the dock is on the primary monitor ++ if (this._isPrimaryMonitor()){ ++ unavailableTopSpace = Main.panel.actor.height; ++ 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){ ++ ++ let availableWidth = this._monitor.width; ++ this.actor.width = Math.round( fraction * availableWidth); ++ ++ 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 + unavailableTopSpace; ++ anchor_point = Clutter.Gravity.NORTH_WEST; ++ } ++ ++ this.actor.move_anchor_point_from_gravity(anchor_point); ++ this.actor.x = this._monitor.x + Math.round( (1-fraction)/2 * availableWidth); ++ 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 { ++ ++ let availableHeight = this._monitor.height - unavailableTopSpace - unavailableBottomSpace; ++ this.actor.height = Math.round( fraction * availableHeight); ++ ++ 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 = this._monitor.y + unavailableTopSpace + Math.round( (1-fraction)/2 * availableHeight); ++ ++ 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._adjustPanelCorners(); ++ ++ this._updateStaticBox(); ++ }, ++ ++ _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); ++ }, ++ ++ // Adjust Panel corners ++ _adjustPanelCorners: function() { ++ let extendHeight = this._settings.get_boolean('extend-height'); ++ if (!this._isHorizontal && this._isPrimaryMonitor() && extendHeight && this._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(); ++ }, ++ ++ _getMonitor: function(){ ++ ++ let monitorIndex = this._settings.get_int('preferred-monitor'); ++ let monitor; ++ ++ if (monitorIndex >0 && monitorIndex< Main.layoutManager.monitors.length) ++ monitor = Main.layoutManager.monitors[monitorIndex]; ++ else ++ monitor = Main.layoutManager.primaryMonitor; ++ ++ return monitor; ++ }, ++ ++ _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 ++ Main.layoutManager.uiGroup.set_child_below_sibling(this.actor, Main.layoutManager.modalDialogGroup); ++ 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); ++ }, ++ ++ _onShowAppsButtonToggled: function() { ++ ++ // 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 selector = Main.overview.viewSelector; ++ ++ if(selector._showAppsButton.checked !== this.dash.showAppsButton.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(this.dash.showAppsButton.checked){ ++ // force entering overview if needed ++ if (!Main.overview._shown) { ++ ++ let view = Main.overview.viewSelector.appDisplay._views[visibleView].view; ++ let grid = view._grid; ++ ++ // 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; ++ selector._showAppsButton.checked = true; ++ ++ // 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.dash.showAppsButton); ++ })); ++ })); ++ ++ // Finally show the overview ++ Main.overview.show(); ++ this.forcedOverview = true; ++ } else { ++ selector._showAppsButton.checked = true; ++ } ++ } else { ++ if (this.forcedOverview) { ++ // force exiting overview if needed ++ ++ // 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 { ++ selector._showAppsButton.checked = false; ++ } ++ ++ } ++ } ++ ++ // whenever the button is unactivated even if not by the user still reset the ++ // forcedOverview flag ++ if( this.dash.showAppsButton.checked==false) ++ this.forcedOverview = false; ++ }, ++ ++ // 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 enable/disable ++ ++ // Switch workspace by scrolling over the dock ++ _optionalScrollWorkspaceSwitch: function() { ++ ++ let label = 'optionalScrollWorkspaceSwitch'; ++ ++ this._settings.connect('changed::scroll-switch-workspace',Lang.bind(this, function(){ ++ if(this._settings.get_boolean('scroll-switch-workspace')) ++ Lang.bind(this, enable)(); ++ else ++ Lang.bind(this, disable)(); ++ })); ++ ++ if(this._settings.get_boolean('scroll-switch-workspace')) ++ 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; ++ } ++ } ++ ++ }, ++ ++ // Makes the message not being triggered by mouse. SOURCE: insensitive-tray extension. ++ _updateInsensitiveTray: function() { ++ ++ let insensitive = this._settings.get_boolean('insensitive-message-tray'); ++ ++ if("_trayPressure" in LayoutManager) { ++ // Systems supporting pressure ++ if (insensitive) { ++ LayoutManager._trayPressure._keybindingMode = null; ++ } else { ++ LayoutManager._trayPressure._keybindingMode = Shell.KeyBindingMode.NORMAL ++ | Shell.KeyBindingMode.OVERVIEW; ++ } ++ } else { ++ //systems using the old dwell mechanism ++ if (insensitive) { ++ this._injectionsHandler.addWithLabel('insensitive-message-tray', ++ [ ++ Main.messageTray, ++ '_trayDwellTimeout', ++ function() { return false; } ++ ] ++ ); ++ } else { ++ this._injectionsHandler.removeWithLabel('insensitive-message-tray') ++ } ++ } ++ } ++}); ++Signals.addSignalMethods(dockedDash.prototype); ++ ++/* ++ * Manage theme customization and custom theme support ++*/ ++const themeManager = new Lang.Class({ ++ Name: 'ThemeManager', ++ ++ _init: function(settings, actor, dash) { ++ ++ this._settings = settings; ++ this._bindSettingsChanges(); ++ this._actor = actor; ++ this._dash = dash; ++ ++ // initialize colors with generic values ++ this._defaultBackground = {red: 0, green:0, blue: 0, alpha:0}; ++ this._defaultBackgroundColor = {red: 0, green:0, blue: 0, alpha:0}; ++ this._customizedBackground = {red: 0, green:0, blue: 0, alpha:0}; ++ ++ this._signalsHandler = new Convenience.GlobalSignalsHandler(); ++ 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: function() { ++ this._signalsHandler.destroy(); ++ }, ++ ++ _onOverviewShowing: function() { ++ this._actor.add_style_pseudo_class('overview'); ++ }, ++ ++ _onOverviewHiding: function() { ++ this._actor.remove_style_pseudo_class('overview'); ++ }, ++ ++ _updateBackgroundOpacity: function() { ++ ++ let newAlpha = this._settings.get_double('background-opacity'); ++ ++ this._defaultBackground = 'rgba('+ ++ this._defaultBackgroundColor.red + ','+ ++ this._defaultBackgroundColor.green + ','+ ++ this._defaultBackgroundColor.blue + ','+ ++ Math.round(this._defaultBackgroundColor.alpha/2.55)/100 + ')'; ++ ++ this._customizedBackground = 'rgba('+ ++ this._defaultBackgroundColor.red + ','+ ++ this._defaultBackgroundColor.green + ','+ ++ this._defaultBackgroundColor.blue + ','+ ++ newAlpha + ')'; ++ }, ++ ++ _getBackgroundColor: 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 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); ++ ++ this._defaultBackgroundColor = themeNode.get_background_color(); ++ }, ++ ++ _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_boolean('custom-theme-running-dots')) ++ this._actor.add_style_class_name('running-dots'); ++ else { ++ this._actor.remove_style_class_name('running-dots'); ++ } ++ ++ }, ++ ++ updateCustomTheme: function() { ++ this._updateCustomStyleClasses(); ++ this._getBackgroundColor(); ++ this._updateBackgroundOpacity(); ++ 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); ++ ++ /* If built-in theme is enabled do nothing else */ ++ if( this._settings.get_boolean('apply-custom-theme') ) ++ return; ++ ++ let newStyle = ''; ++ let position = 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 */ ++ if ( this._settings.get_boolean('opaque-background') ) { ++ newStyle = newStyle + 'background-color:'+ this._customizedBackground + '; ' + ++ 'transition-delay: 0s; transition-duration: 0.250s;'; ++ this._dash._container.set_style(newStyle); ++ } ++ }, ++ ++ _bindSettingsChanges: function() { ++ ++ let keys = ['opaque-background', ++ 'background-opacity', ++ 'apply-custom-theme', ++ 'custom-theme-shrink', ++ 'custom-theme-running-dots', ++ 'extend-height']; ++ ++ keys.forEach(function(key){ ++ this._settings.connect('changed::'+key, ++ Lang.bind(this, this.updateCustomTheme) ++ ); ++ }, this ); ++ ++ } ++}); ++ ++ ++/* ++ * Manually check if mouse "can be" hover from the mouse position. The hover porperty ++ * is not reliable when focus move from the clutter actors to the the windows, giving a false ++ * positive hover status. If the mouse pointer is not in the right position I can be sure ++ * that the hover has to be false. ++*/ ++function isMouseHover(actor) { ++ let [pointerX, pointerY, mods] = global.get_pointer(); ++ ++ let [x, y] = actor.get_transformed_position(); ++ let [width, height] =actor.get_transformed_size(); ++ ++ let test = (pointerX < x + width) && ++ (pointerX > x) && ++ (pointerY < y + height) && ++ (pointerY > y); ++ ++ return test; ++} +diff --git a/extensions/dash-to-dock/docking.js b/extensions/dash-to-dock/docking.js +new file mode 100644 +index 0000000..08707b4 +--- /dev/null ++++ b/extensions/dash-to-dock/docking.js +@@ -0,0 +1,1909 @@ ++// -*- 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 DOCK_DWELL_CHECK_INTERVAL = 100; ++ ++const 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, monitorIndex) { ++ this._rtl = (Clutter.get_default_text_direction() == Clutter.TextDirection.RTL); ++ ++ // Load settings ++ this._settings = settings; ++ 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._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.actor, this.dash); ++ ++ // 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; ++ } ++ ++ this.dash.setIconSize(this._settings.get_int('dash-max-icon-size'), true); ++ ++ // 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(); ++ } ++ } ++ }, ++ ++ _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; ++ 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() { ++ 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() { ++ 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-', // Regular numbers ++ 'app-hotkey-kp-', 'app-shift-hotkey-kp-', 'app-ctrl-hotkey-kp-']; // Key-pad numbers ++ 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-', // Regular numbers ++ 'app-hotkey-kp-', 'app-shift-hotkey-kp-', 'app-ctrl-hotkey-kp-']; // Key-pad numbers ++ 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(); ++ } ++ ++}); ++ ++ ++const DockManager = new Lang.Class({ ++ Name: 'DashToDock.DockManager', ++ ++ _init: function() { ++ 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._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, 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(); ++ }, ++ ++ /** ++ * 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..86ef700 +--- /dev/null ++++ b/extensions/dash-to-dock/extension.js +@@ -0,0 +1,21 @@ ++// -*- 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; ++ ++let 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..0d9fabd +--- /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; ++ */ ++const 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/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/metadata.json.in b/extensions/dash-to-dock/metadata.json.in +new file mode 100644 +index 0000000..a090272 +--- /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..0f43bd7 +--- /dev/null ++++ b/extensions/dash-to-dock/org.gnome.shell.extensions.dash-to-dock.gschema.xml +@@ -0,0 +1,713 @@ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ '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. ++ ++ ++ false ++ Dash background is opaque ++ Makes the background of the dash opaque improving readability when in autohide mode. ++ ++ ++ 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 ++ 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 ++ Customize click behaviour ++ Customize action on various mouse events ++ ++ ++ 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. ++ ++ ++ ++ KP_1']]]> ++ Keybinding to launch 1st dash app ++ ++ Keybinding to launch 1st app. ++ ++ ++ ++ KP_2']]]> ++ Keybinding to launch 2nd dash app ++ ++ Keybinding to launch 2nd app. ++ ++ ++ ++ KP_3']]]> ++ Keybinding to launch 3rd dash app ++ ++ Keybinding to launch 3rd app. ++ ++ ++ ++ KP_4']]]> ++ Keybinding to launch 4th dash app ++ ++ Keybinding to launch 4th app. ++ ++ ++ ++ KP_5']]]> ++ Keybinding to launch 5th dash app ++ ++ Keybinding to launch 5th app. ++ ++ ++ ++ KP_6']]]> ++ Keybinding to launch 6th dash app ++ ++ Keybinding to launch 6th app. ++ ++ ++ ++ KP_7']]]> ++ Keybinding to launch 7th dash app ++ ++ Keybinding to launch 7th app. ++ ++ ++ ++ KP_8']]]> ++ Keybinding to launch 8th dash app ++ ++ Keybinding to launch 8th app. ++ ++ ++ ++ KP_9']]]> ++ Keybinding to launch 9th dash app ++ ++ Keybinding to launch 9th app. ++ ++ ++ ++ KP_0']]]> ++ Keybinding to launch 10th dash app ++ ++ Keybinding to launch 10th app. ++ ++ ++ ++ KP_1']]]> ++ Keybinding to trigger 1st dash app with shift behavior ++ ++ Keybinding to trigger 1st app with shift behavior. ++ ++ ++ ++ KP_2']]]> ++ Keybinding to trigger 2nd dash app with shift behavior ++ ++ Keybinding to trigger 2nd app with shift behavior. ++ ++ ++ ++ KP_3']]]> ++ Keybinding to trigger 3rd dash app with shift behavior ++ ++ Keybinding to trigger 3rd app with shift behavior. ++ ++ ++ ++ KP_4']]]> ++ Keybinding to trigger 4th dash app with shift behavior ++ ++ Keybinding to trigger 4th app with shift behavior. ++ ++ ++ ++ KP_5']]]> ++ Keybinding to trigger 5th dash app with shift behavior ++ ++ Keybinding to trigger 5th app with shift behavior. ++ ++ ++ ++ KP_6']]]> ++ Keybinding to trigger 6th dash app with shift behavior ++ ++ Keybinding to trigger 6th app with shift behavior. ++ ++ ++ ++ KP_7']]]> ++ Keybinding to trigger 7th dash app with shift behavior ++ ++ Keybinding to trigger 7th app with shift behavior. ++ ++ ++ ++ KP_8']]]> ++ Keybinding to trigger 8th dash app with shift behavior ++ ++ Keybinding to trigger 8th app with shift behavior. ++ ++ ++ ++ KP_9']]]> ++ Keybinding to trigger 9th dash app with shift behavior ++ ++ Keybinding to trigger 9th app with shift behavior. ++ ++ ++ ++ KP_0']]]> ++ Keybinding to trigger 10th dash app with shift behavior ++ ++ Keybinding to trigger 10th app with shift behavior. ++ ++ ++ ++ KP_1']]]> ++ Keybinding to trigger 1st dash app ++ ++ Keybinding to either show or launch the 1st application in the dash. ++ ++ ++ ++ KP_2']]]> ++ Keybinding to trigger 2nd dash app ++ ++ Keybinding to either show or launch the 2nd application in the dash. ++ ++ ++ ++ KP_3']]]> ++ Keybinding to trigger 3rd dash app ++ ++ Keybinding to either show or launch the 3rd application in the dash. ++ ++ ++ ++ KP_4']]]> ++ Keybinding to trigger 4th dash app ++ ++ Keybinding to either show or launch the 4th application in the dash. ++ ++ ++ ++ KP_5']]]> ++ Keybinding to trigger 5th dash app ++ ++ Keybinding to either show or launch the 5th application in the dash. ++ ++ ++ ++ KP_6']]]> ++ Keybinding to trigger 6th dash app ++ ++ Keybinding to either show or launch the 6th application in the dash. ++ ++ ++ ++ KP_7']]]> ++ Keybinding to trigger 7th dash app ++ ++ Keybinding to either show or launch the 7th application in the dash. ++ ++ ++ ++ KP_8']]]> ++ Keybinding to trigger 8th dash app ++ ++ Keybinding to either show or launch the 8th application in the dash. ++ ++ ++ ++ KP_9']]]> ++ Keybinding to trigger 9th dash app ++ ++ Keybinding to either show or launch the 9th application in the dash. ++ ++ ++ ++ KP_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 ++ ++ ++ +diff --git a/extensions/dash-to-dock/prefs.js b/extensions/dash-to-dock/prefs.js +new file mode 100644 +index 0000000..5f1f378 +--- /dev/null ++++ b/extensions/dash-to-dock/prefs.js +@@ -0,0 +1,705 @@ ++// -*- 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 ]; ++ ++/** ++ * 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 = this._builder.get_object('settings_notebook'); ++ ++ // 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); ++ ++ this._settings.bind('custom-theme-running-dots', ++ this._builder.get_object('running_dots_switch'), ++ 'active', ++ Gio.SettingsBindFlags.DEFAULT); ++ this._settings.bind('custom-theme-running-dots', ++ this._builder.get_object('running_dots_advance_settings_button'), ++ 'sensitive', ++ Gio.SettingsBindFlags.DEFAULT); ++ ++ // Create dialog for running dots advanced settings ++ this._builder.get_object('running_dots_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('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); ++ })); ++ ++ this._settings.bind('opaque-background', this._builder.get_object('customize_opacity_switch'), 'active', Gio.SettingsBindFlags.DEFAULT); ++ this._builder.get_object('custom_opacity_scale').set_value(this._settings.get_double('background-opacity')); ++ this._settings.bind('opaque-background', this._builder.get_object('custom_opacity'), 'sensitive', 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; ++ })); ++ }, ++ ++ custom_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..0a306b4 +--- /dev/null ++++ b/extensions/dash-to-dock/theming.js +@@ -0,0 +1,293 @@ ++// -*- 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; ++ ++/** ++ * Manage theme customization and custom theme support ++ */ ++const ThemeManager = new Lang.Class({ ++ Name: 'DashToDock.ThemeManager', ++ ++ _init: function(settings, actor, dash) { ++ this._settings = settings; ++ this._signalsHandler = new Utils.GlobalSignalsHandler(); ++ this._bindSettingsChanges(); ++ this._actor = actor; ++ this._dash = 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._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(); ++ }, ++ ++ _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() { ++ if (this._settings.get_boolean('custom-background-color')) { ++ let [backgroundColor, borderColor] = this._getDefaultColors(); ++ ++ if (backgroundColor==null) ++ return; ++ ++ let newAlpha = Math.round(backgroundColor.alpha/2.55)/100; ++ if (this._settings.get_boolean('opaque-background')) ++ newAlpha = this._settings.get_double('background-opacity'); ++ ++ let newColor = Clutter.color_from_string(this._settings.get_string('background-color'))[1]; ++ this._customizedBackground = 'rgba(' + ++ newColor.red + ',' + ++ newColor.green + ',' + ++ newColor.blue + ',' + ++ newAlpha + ')'; ++ ++ this._customizedBorder = this._customizedBackground; ++ } ++ }, ++ ++ _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_boolean('custom-theme-running-dots')) ++ 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); ++ ++ // 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 ++ if (this._settings.get_boolean('opaque-background') || 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 = ['opaque-background', ++ '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); ++ } ++}); +diff --git a/extensions/dash-to-dock/utils.js b/extensions/dash-to-dock/utils.js +new file mode 100644 +index 0000000..b98fe45 +--- /dev/null ++++ b/extensions/dash-to-dock/utils.js +@@ -0,0 +1,123 @@ ++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++) { ++ this._storage[label].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 ++ */ ++const 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]); ++ } ++}); ++ ++/** ++ * Manage function injection: both instances and prototype can be overridden ++ * and restored ++ */ ++const 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; ++} +diff --git a/extensions/dash-to-dock/windowPreview.js b/extensions/dash-to-dock/windowPreview.js +new file mode 100644 +index 0000000..4f84b4d +--- /dev/null ++++ b/extensions/dash-to-dock/windowPreview.js +@@ -0,0 +1,595 @@ ++/* ++ * 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.AUTOMATIC, ++ vscrollbar_policy: Gtk.PolicyType.AUTOMATIC, ++ 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); ++ }, ++ ++ 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; ++ params = Params.parse(params, { style_class: 'app-well-preview-menu-item' }); ++ this.parent(params); ++ ++ 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: 5px'); ++ ++ 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; ++ } ++ } ++ ++}); ++ +-- +2.14.2 + + +From 9f6e5b7f81fe3796d04ee8585a8319bb6aa17b74 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/5] Add panel-favorites extension + +--- + configure.ac | 5 +- + extensions/panel-favorites/Makefile.am | 3 + + extensions/panel-favorites/extension.js | 267 ++++++++++++++++++++++++++++ + extensions/panel-favorites/metadata.json.in | 10 ++ + extensions/panel-favorites/stylesheet.css | 14 ++ + 5 files changed, 297 insertions(+), 2 deletions(-) + create mode 100644 extensions/panel-favorites/Makefile.am + create mode 100644 extensions/panel-favorites/extension.js + create mode 100644 extensions/panel-favorites/metadata.json.in + create mode 100644 extensions/panel-favorites/stylesheet.css + +diff --git a/configure.ac b/configure.ac +index 4178191..0602094 100644 +--- a/configure.ac ++++ b/configure.ac +@@ -31,7 +31,7 @@ AC_SUBST([SHELL_VERSION]) + dnl keep this in alphabetic order + CLASSIC_EXTENSIONS="apps-menu places-menu alternate-tab launch-new-instance window-list" + DEFAULT_EXTENSIONS="$CLASSIC_EXTENSIONS drive-menu screenshot-window-sizer windowsNavigator workspace-indicator" +-ALL_EXTENSIONS="$DEFAULT_EXTENSIONS auto-move-windows dash-to-dock example native-window-placement top-icons user-theme" ++ALL_EXTENSIONS="$DEFAULT_EXTENSIONS auto-move-windows dash-to-dock example native-window-placement panel-favorites top-icons user-theme" + AC_SUBST(CLASSIC_EXTENSIONS, [$CLASSIC_EXTENSIONS]) + AC_SUBST(ALL_EXTENSIONS, [$ALL_EXTENSIONS]) + AC_ARG_ENABLE([extensions], +@@ -63,7 +63,7 @@ ENABLED_EXTENSIONS= + for e in $enable_extensions; do + case $e in + dnl keep this in alphabetic order +- alternate-tab|apps-menu|auto-move-windows|dash-to-dock|drive-menu|example|launch-new-instance|native-window-placement|places-menu|screenshot-window-sizer|top-icons|user-theme|window-list|windowsNavigator|workspace-indicator) ++ alternate-tab|apps-menu|auto-move-windows|dash-to-dock|drive-menu|example|launch-new-instance|native-window-placement|panel-favorites|places-menu|screenshot-window-sizer|top-icons|user-theme|window-list|windowsNavigator|workspace-indicator) + ENABLED_EXTENSIONS="$ENABLED_EXTENSIONS $e" + ;; + *) +@@ -86,6 +86,7 @@ AC_CONFIG_FILES([ + extensions/example/Makefile + extensions/launch-new-instance/Makefile + extensions/native-window-placement/Makefile ++ extensions/panel-favorites/Makefile + extensions/places-menu/Makefile + extensions/screenshot-window-sizer/Makefile + extensions/top-icons/Makefile +diff --git a/extensions/panel-favorites/Makefile.am b/extensions/panel-favorites/Makefile.am +new file mode 100644 +index 0000000..4ce3128 +--- /dev/null ++++ b/extensions/panel-favorites/Makefile.am +@@ -0,0 +1,3 @@ ++EXTENSION_ID = panel-favorites ++ ++include ../../extension.mk +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/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; ++} +-- +2.14.2 + + +From baec74c44a6d57b4525466403d164a5ab0112259 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/5] Add updates-dialog extension + +--- + configure.ac | 5 +- + extensions/updates-dialog/Makefile.am | 5 + + extensions/updates-dialog/extension.js | 490 +++++++++++++++++++++ + extensions/updates-dialog/metadata.json.in | 10 + + ...ome.shell.extensions.updates-dialog.gschema.xml | 30 ++ + extensions/updates-dialog/stylesheet.css | 1 + + po/POTFILES.in | 2 + + 7 files changed, 541 insertions(+), 2 deletions(-) + create mode 100644 extensions/updates-dialog/Makefile.am + create mode 100644 extensions/updates-dialog/extension.js + 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/configure.ac b/configure.ac +index 0602094..98c6983 100644 +--- a/configure.ac ++++ b/configure.ac +@@ -31,7 +31,7 @@ AC_SUBST([SHELL_VERSION]) + dnl keep this in alphabetic order + CLASSIC_EXTENSIONS="apps-menu places-menu alternate-tab launch-new-instance window-list" + DEFAULT_EXTENSIONS="$CLASSIC_EXTENSIONS drive-menu screenshot-window-sizer windowsNavigator workspace-indicator" +-ALL_EXTENSIONS="$DEFAULT_EXTENSIONS auto-move-windows dash-to-dock example native-window-placement panel-favorites top-icons user-theme" ++ALL_EXTENSIONS="$DEFAULT_EXTENSIONS auto-move-windows dash-to-dock example native-window-placement panel-favorites top-icons updates-dialog user-theme" + AC_SUBST(CLASSIC_EXTENSIONS, [$CLASSIC_EXTENSIONS]) + AC_SUBST(ALL_EXTENSIONS, [$ALL_EXTENSIONS]) + AC_ARG_ENABLE([extensions], +@@ -63,7 +63,7 @@ ENABLED_EXTENSIONS= + for e in $enable_extensions; do + case $e in + dnl keep this in alphabetic order +- alternate-tab|apps-menu|auto-move-windows|dash-to-dock|drive-menu|example|launch-new-instance|native-window-placement|panel-favorites|places-menu|screenshot-window-sizer|top-icons|user-theme|window-list|windowsNavigator|workspace-indicator) ++ alternate-tab|apps-menu|auto-move-windows|dash-to-dock|drive-menu|example|launch-new-instance|native-window-placement|panel-favorites|places-menu|screenshot-window-sizer|top-icons|updates-dialog|user-theme|window-list|windowsNavigator|workspace-indicator) + ENABLED_EXTENSIONS="$ENABLED_EXTENSIONS $e" + ;; + *) +@@ -90,6 +90,7 @@ AC_CONFIG_FILES([ + extensions/places-menu/Makefile + extensions/screenshot-window-sizer/Makefile + extensions/top-icons/Makefile ++ extensions/updates-dialog/Makefile + extensions/user-theme/Makefile + extensions/window-list/Makefile + extensions/windowsNavigator/Makefile +diff --git a/extensions/updates-dialog/Makefile.am b/extensions/updates-dialog/Makefile.am +new file mode 100644 +index 0000000..4da9b17 +--- /dev/null ++++ b/extensions/updates-dialog/Makefile.am +@@ -0,0 +1,5 @@ ++EXTENSION_ID = updates-dialog ++ ++include ../../extension.mk ++include ../../settings.mk ++ +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/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/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.14.2 + + +From d640b8f483903cf13c0a1c09fbc4b064dea3b920 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/5] Add no-hot-corner extension + +--- + configure.ac | 5 +++-- + extensions/no-hot-corner/Makefile.am | 3 +++ + extensions/no-hot-corner/extension.js | 31 +++++++++++++++++++++++++++++++ + extensions/no-hot-corner/metadata.json.in | 9 +++++++++ + extensions/no-hot-corner/stylesheet.css | 1 + + 5 files changed, 47 insertions(+), 2 deletions(-) + create mode 100644 extensions/no-hot-corner/Makefile.am + create mode 100644 extensions/no-hot-corner/extension.js + create mode 100644 extensions/no-hot-corner/metadata.json.in + create mode 100644 extensions/no-hot-corner/stylesheet.css + +diff --git a/configure.ac b/configure.ac +index 98c6983..34b2171 100644 +--- a/configure.ac ++++ b/configure.ac +@@ -31,7 +31,7 @@ AC_SUBST([SHELL_VERSION]) + dnl keep this in alphabetic order + CLASSIC_EXTENSIONS="apps-menu places-menu alternate-tab launch-new-instance window-list" + DEFAULT_EXTENSIONS="$CLASSIC_EXTENSIONS drive-menu screenshot-window-sizer windowsNavigator workspace-indicator" +-ALL_EXTENSIONS="$DEFAULT_EXTENSIONS auto-move-windows dash-to-dock example native-window-placement panel-favorites top-icons updates-dialog user-theme" ++ALL_EXTENSIONS="$DEFAULT_EXTENSIONS auto-move-windows dash-to-dock example native-window-placement no-hot-corner panel-favorites top-icons updates-dialog user-theme" + AC_SUBST(CLASSIC_EXTENSIONS, [$CLASSIC_EXTENSIONS]) + AC_SUBST(ALL_EXTENSIONS, [$ALL_EXTENSIONS]) + AC_ARG_ENABLE([extensions], +@@ -63,7 +63,7 @@ ENABLED_EXTENSIONS= + for e in $enable_extensions; do + case $e in + dnl keep this in alphabetic order +- alternate-tab|apps-menu|auto-move-windows|dash-to-dock|drive-menu|example|launch-new-instance|native-window-placement|panel-favorites|places-menu|screenshot-window-sizer|top-icons|updates-dialog|user-theme|window-list|windowsNavigator|workspace-indicator) ++ alternate-tab|apps-menu|auto-move-windows|dash-to-dock|drive-menu|example|launch-new-instance|native-window-placement|no-hot-corner|panel-favorites|places-menu|screenshot-window-sizer|top-icons|updates-dialog|user-theme|window-list|windowsNavigator|workspace-indicator) + ENABLED_EXTENSIONS="$ENABLED_EXTENSIONS $e" + ;; + *) +@@ -86,6 +86,7 @@ AC_CONFIG_FILES([ + extensions/example/Makefile + extensions/launch-new-instance/Makefile + extensions/native-window-placement/Makefile ++ extensions/no-hot-corner/Makefile + extensions/panel-favorites/Makefile + extensions/places-menu/Makefile + extensions/screenshot-window-sizer/Makefile +diff --git a/extensions/no-hot-corner/Makefile.am b/extensions/no-hot-corner/Makefile.am +new file mode 100644 +index 0000000..903b272 +--- /dev/null ++++ b/extensions/no-hot-corner/Makefile.am +@@ -0,0 +1,3 @@ ++EXTENSION_ID = no-hot-corner ++ ++include ../../extension.mk +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/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 */ +-- +2.14.2 + diff --git a/SOURCES/apps-menu-follow-sort-order.patch b/SOURCES/apps-menu-follow-sort-order.patch new file mode 100644 index 0000000..d6238ea --- /dev/null +++ b/SOURCES/apps-menu-follow-sort-order.patch @@ -0,0 +1,107 @@ +From d19a80a7e7b7a19a723365bae85cc99ebd5bc25b Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Florian=20M=C3=BCllner?= +Date: Fri, 13 Oct 2017 00:30:31 +0200 +Subject: [PATCH 1/2] apps-menu: Don't override sort order + +Keep the order in which GMenu returns loaded apps, so users can +reorder entries in Alacarte. + +https://bugzilla.gnome.org/show_bug.cgi?id=788939 +--- + extensions/apps-menu/extension.js | 3 --- + 1 file changed, 3 deletions(-) + +diff --git a/extensions/apps-menu/extension.js b/extensions/apps-menu/extension.js +index e430140..dba31e7 100644 +--- a/extensions/apps-menu/extension.js ++++ b/extensions/apps-menu/extension.js +@@ -745,9 +745,6 @@ const ApplicationsButton = new Lang.Class({ + + if (category_menu_id) { + applist = this.applicationsByCategory[category_menu_id]; +- applist.sort(function(a,b) { +- return a.get_name().toLowerCase() > b.get_name().toLowerCase(); +- }); + } else { + applist = new Array(); + let favorites = global.settings.get_strv('favorite-apps'); +-- +2.14.2 + + +From a9d09c692cfb2944cdfd5b5c61140b3f9aa4b250 Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Florian=20M=C3=BCllner?= +Date: Fri, 13 Oct 2017 01:43:20 +0200 +Subject: [PATCH 2/2] apps-menu: Reload on tree changes + +Now that we respect the sort order defined in the .menu file, make +sure to reload the menu on those changes as well, not just when +the installed apps themselves change. + +https://bugzilla.gnome.org/show_bug.cgi?id=788939 +--- + extensions/apps-menu/extension.js | 30 +++++++++++++++++++----------- + 1 file changed, 19 insertions(+), 11 deletions(-) + +diff --git a/extensions/apps-menu/extension.js b/extensions/apps-menu/extension.js +index dba31e7..600eda3 100644 +--- a/extensions/apps-menu/extension.js ++++ b/extensions/apps-menu/extension.js +@@ -466,18 +466,25 @@ const ApplicationsButton = new Lang.Class({ + }); + }); + ++ this._tree = new GMenu.Tree({ menu_basename: 'applications.menu' }); ++ this._treeChangedId = this._tree.connect('changed', ++ Lang.bind(this, this._onTreeChanged)); ++ + this._applicationsButtons = new Map(); + this.reloadFlag = false; + this._createLayout(); + this._display(); +- this._installedChangedId = appSys.connect('installed-changed', Lang.bind(this, function() { +- if (this.menu.isOpen) { +- this._redisplay(); +- this.mainBox.show(); +- } else { +- this.reloadFlag = true; +- } +- })); ++ this._installedChangedId = appSys.connect('installed-changed', ++ Lang.bind(this, this._onTreeChanged)); ++ }, ++ ++ _onTreeChanged: function() { ++ if (this.menu.isOpen) { ++ this._redisplay(); ++ this.mainBox.show(); ++ } else { ++ this.reloadFlag = true; ++ } + }, + + get hotCorner() { +@@ -495,6 +502,8 @@ const ApplicationsButton = new Lang.Class({ + Main.overview.disconnect(this._showingId); + Main.overview.disconnect(this._hidingId); + appSys.disconnect(this._installedChangedId); ++ this._tree.disconnect(this._treeChangedId); ++ this._tree = null; + + Main.wm.setCustomKeybindingHandler('panel-main-menu', + Shell.ActionMode.NORMAL | +@@ -675,9 +684,8 @@ const ApplicationsButton = new Lang.Class({ + + //Load categories + this.applicationsByCategory = {}; +- let tree = new GMenu.Tree({ menu_basename: 'applications.menu' }); +- tree.load_sync(); +- let root = tree.get_root_directory(); ++ this._tree.load_sync(); ++ let root = this._tree.get_root_directory(); + let categoryMenuItem = new CategoryMenuItem(this, null); + this.categoriesBox.add_actor(categoryMenuItem.actor); + let iter = root.iter(); +-- +2.14.2 + diff --git a/SOURCES/apps-menu-support-separators.patch b/SOURCES/apps-menu-support-separators.patch new file mode 100644 index 0000000..fdd7dbb --- /dev/null +++ b/SOURCES/apps-menu-support-separators.patch @@ -0,0 +1,100 @@ +From a91f33c152c1f463d21a388727e64945c88e4d54 Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Florian=20M=C3=BCllner?= +Date: Fri, 13 Oct 2017 01:20:17 +0200 +Subject: [PATCH 1/2] apps-menu: Minor code cleanup + +The parameter to _clearApplicationBox() has never been used, so +remove it. In fact, modern javascript makes the function so compact +that we can just move the code inline. + +https://bugzilla.gnome.org/show_bug.cgi?id=788939 +--- + extensions/apps-menu/extension.js | 15 +++------------ + 1 file changed, 3 insertions(+), 12 deletions(-) + +diff --git a/extensions/apps-menu/extension.js b/extensions/apps-menu/extension.js +index 600eda3..8138d4c 100644 +--- a/extensions/apps-menu/extension.js ++++ b/extensions/apps-menu/extension.js +@@ -712,19 +712,10 @@ const ApplicationsButton = new Lang.Class({ + this.mainBox.style+=('height: ' + height); + }, + +- _clearApplicationsBox: function(selectedActor) { +- let actors = this.applicationsBox.get_children(); +- for (let i = 0; i < actors.length; i++) { +- let actor = actors[i]; +- this.applicationsBox.remove_actor(actor); +- } +- }, +- + selectCategory: function(dir, categoryMenuItem) { +- if (categoryMenuItem) +- this._clearApplicationsBox(categoryMenuItem.actor); +- else +- this._clearApplicationsBox(null); ++ this.applicationsBox.get_children().forEach(c => { ++ this.applicationsBox.remove_actor(c); ++ }); + + if (dir) + this._displayButtons(this._listApplications(dir.get_menu_id())); +-- +2.14.2 + + +From dd79357f429fabfec63ab1ea772428000bf038d0 Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Florian=20M=C3=BCllner?= +Date: Fri, 13 Oct 2017 01:44:19 +0200 +Subject: [PATCH 2/2] apps-menu: Support separators + +We currently only load entries and directories, and ignore any +separators defined by the user/admin. Make some people happy +by supporting them ... + +https://bugzilla.gnome.org/show_bug.cgi?id=788939 +--- + extensions/apps-menu/extension.js | 13 +++++++++++-- + 1 file changed, 11 insertions(+), 2 deletions(-) + +diff --git a/extensions/apps-menu/extension.js b/extensions/apps-menu/extension.js +index 8138d4c..c2ecf5f 100644 +--- a/extensions/apps-menu/extension.js ++++ b/extensions/apps-menu/extension.js +@@ -592,6 +592,8 @@ const ApplicationsButton = new Lang.Class({ + app = new Shell.App({ app_info: entry.get_app_info() }); + if (app.get_app_info().should_show()) + this.applicationsByCategory[categoryId].push(app); ++ } else if (nextType == GMenu.TreeItemType.SEPARATOR) { ++ this.applicationsByCategory[categoryId].push('separator'); + } else if (nextType == GMenu.TreeItemType.DIRECTORY) { + let subdir = iter.get_directory(); + if (!subdir.get_is_nodisplay()) +@@ -714,7 +716,10 @@ const ApplicationsButton = new Lang.Class({ + + selectCategory: function(dir, categoryMenuItem) { + this.applicationsBox.get_children().forEach(c => { +- this.applicationsBox.remove_actor(c); ++ if (c._delegate instanceof PopupMenu.PopupSeparatorMenuItem) ++ c._delegate.destroy(); ++ else ++ this.applicationsBox.remove_actor(c); + }); + + if (dir) +@@ -727,7 +732,11 @@ const ApplicationsButton = new Lang.Class({ + if (apps) { + for (let i = 0; i < apps.length; i++) { + let app = apps[i]; +- let item = this._applicationsButtons.get(app); ++ let item; ++ if (app instanceof Shell.App) ++ item = this._applicationsButtons.get(app); ++ else ++ item = new PopupMenu.PopupSeparatorMenuItem(); + if (!item) { + item = new ApplicationMenuItem(this, app); + item.setDragEnabled(this._desktopTarget.hasDesktop); +-- +2.14.2 + diff --git a/SOURCES/classic-style-fixes.patch b/SOURCES/classic-style-fixes.patch new file mode 100644 index 0000000..47d21e7 --- /dev/null +++ b/SOURCES/classic-style-fixes.patch @@ -0,0 +1,89 @@ +From 145845f99ac42d0355752d60bbcf561ce31afb55 Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Florian=20M=C3=BCllner?= +Date: Wed, 17 Jan 2018 18:26:51 +0100 +Subject: [PATCH 1/3] classic: Fix "Clear All" button readability + +Most buttons appear in modal dialogs which keep their normal +appearance in the classic theme, except for the calendar's +"Clear All" which needs a dark text color to be readable on +the light background. + +https://gitlab.gnome.org/GNOME/gnome-shell-extensions/issues/26 +--- + data/gnome-classic.css | 3 +++ + 2 files changed, 7 insertions(+) + +diff --git a/data/gnome-classic.css b/data/gnome-classic.css +index 4d0d737..be332c9 100644 +--- a/data/gnome-classic.css ++++ b/data/gnome-classic.css +@@ -1961,3 +1961,6 @@ StScrollBar { + + .calendar-day-with-events { + background-image: url("calendar-today.svg"); } ++ ++.message-list-clear-button.button { ++ color: #2e3436; } +-- +2.14.3 + + +From c882621cb806474c077f1511a0f0cfd87432d4c7 Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Jonas=20K=C3=BCmmerlin?= +Date: Wed, 24 Jan 2018 22:07:36 +0100 +Subject: [PATCH 2/3] classic: make notifications legible again + +https://gitlab.gnome.org/GNOME/gnome-shell-extensions/issues/41 + +As requested in discussion about !26, this is resolved by +switching to a light background. +--- + data/gnome-classic.css | 8 ++++++++ + 2 files changed, 17 insertions(+) + +diff --git a/data/gnome-classic.css b/data/gnome-classic.css +index be332c9..6dad881 100644 +--- a/data/gnome-classic.css ++++ b/data/gnome-classic.css +@@ -1964,3 +1964,11 @@ StScrollBar { + + .message-list-clear-button.button { + color: #2e3436; } ++ ++.notification-banner { ++ background-color: #ededed !important; ++ color: #2e3436; } ++ .notification-banner .notification-button { ++ background-color: #e0e0e0; } ++ .notification-banner .notification-button:hover, .notification-banner .notification-buttonfocus { ++ background-color: #e8e8e8; } +-- +2.14.3 + + +From eac93f3f081306d62d82e8d2e45f73cd38c318b1 Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Jonas=20K=C3=BCmmerlin?= +Date: Wed, 24 Jan 2018 22:24:06 +0100 +Subject: [PATCH 3/3] classic: never show drop shadow for panel icons + +https://gitlab.gnome.org/GNOME/gnome-shell-extensions/issues/39 +--- + data/gnome-classic.css | 2 ++ + 2 files changed, 6 insertions(+) + +diff --git a/data/gnome-classic.css b/data/gnome-classic.css +index 6dad881..93d84ba 100644 +--- a/data/gnome-classic.css ++++ b/data/gnome-classic.css +@@ -1923,6 +1923,8 @@ StScrollBar { + width: 0; + height: 0; + margin: 0; } ++ #panel .panel-button .system-status-icon { ++ icon-shadow: none; } + #panel .panel-corner, + #panel .panel-corner:active, + #panel .panel-corner:overview, +-- +2.14.3 + diff --git a/SOURCES/resurrect-system-monitor.patch b/SOURCES/resurrect-system-monitor.patch new file mode 100644 index 0000000..57865cf --- /dev/null +++ b/SOURCES/resurrect-system-monitor.patch @@ -0,0 +1,804 @@ +From 5b4f81b1b510cc254221cac762dd282408c18a8c 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. +--- + README | 4 + + configure.ac | 8 +- + extensions/systemMonitor/Makefile.am | 3 + + extensions/systemMonitor/extension.js | 376 ++++++++++++++++++++++++++++++ + extensions/systemMonitor/metadata.json.in | 11 + + extensions/systemMonitor/stylesheet.css | 35 +++ + 6 files changed, 436 insertions(+), 1 deletion(-) + create mode 100644 extensions/systemMonitor/Makefile.am + create mode 100644 extensions/systemMonitor/extension.js + create mode 100644 extensions/systemMonitor/metadata.json.in + create mode 100644 extensions/systemMonitor/stylesheet.css + +diff --git a/README b/README +index cc53a8d..e2bea7a 100644 +--- a/README ++++ b/README +@@ -57,6 +57,10 @@ places-menu + + Shows a status Indicator for navigating to Places. + ++systemMonitor ++ ++ An message tray indicator showing CPU and memory loads. ++ + user-theme + + Loads a shell theme from ~/.themes//gnome-shell. +diff --git a/configure.ac b/configure.ac +index 34b2171..2c0036c 100644 +--- a/configure.ac ++++ b/configure.ac +@@ -31,7 +31,7 @@ AC_SUBST([SHELL_VERSION]) + dnl keep this in alphabetic order + CLASSIC_EXTENSIONS="apps-menu places-menu alternate-tab launch-new-instance window-list" + DEFAULT_EXTENSIONS="$CLASSIC_EXTENSIONS drive-menu screenshot-window-sizer windowsNavigator workspace-indicator" +-ALL_EXTENSIONS="$DEFAULT_EXTENSIONS auto-move-windows dash-to-dock example native-window-placement no-hot-corner panel-favorites top-icons updates-dialog user-theme" ++ALL_EXTENSIONS="$DEFAULT_EXTENSIONS auto-move-windows dash-to-dock example native-window-placement no-hot-corner panel-favorites systemMonitor top-icons updates-dialog user-theme" + AC_SUBST(CLASSIC_EXTENSIONS, [$CLASSIC_EXTENSIONS]) + AC_SUBST(ALL_EXTENSIONS, [$ALL_EXTENSIONS]) + AC_ARG_ENABLE([extensions], +@@ -62,6 +62,11 @@ AM_CONDITIONAL([CLASSIC_MODE], [test x"$enable_classic_mode" != xno]) + ENABLED_EXTENSIONS= + for e in $enable_extensions; do + case $e in ++ systemMonitor) ++ PKG_CHECK_MODULES(GTOP, libgtop-2.0 >= 2.28.3, ++ [ENABLED_EXTENSIONS="$ENABLED_EXTENSIONS $e"], ++ [AC_MSG_WARN([libgtop-2.0 not found, disabling systemMonitor])]) ++ ;; + dnl keep this in alphabetic order + alternate-tab|apps-menu|auto-move-windows|dash-to-dock|drive-menu|example|launch-new-instance|native-window-placement|no-hot-corner|panel-favorites|places-menu|screenshot-window-sizer|top-icons|updates-dialog|user-theme|window-list|windowsNavigator|workspace-indicator) + ENABLED_EXTENSIONS="$ENABLED_EXTENSIONS $e" +@@ -90,6 +95,7 @@ AC_CONFIG_FILES([ + extensions/panel-favorites/Makefile + extensions/places-menu/Makefile + extensions/screenshot-window-sizer/Makefile ++ extensions/systemMonitor/Makefile + extensions/top-icons/Makefile + extensions/updates-dialog/Makefile + extensions/user-theme/Makefile +diff --git a/extensions/systemMonitor/Makefile.am b/extensions/systemMonitor/Makefile.am +new file mode 100644 +index 0000000..50ce6d2 +--- /dev/null ++++ b/extensions/systemMonitor/Makefile.am +@@ -0,0 +1,3 @@ ++EXTENSION_ID = systemMonitor ++ ++include ../../extension.mk +diff --git a/extensions/systemMonitor/extension.js b/extensions/systemMonitor/extension.js +new file mode 100644 +index 0000000..58a2f57 +--- /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/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; ++} +-- +2.14.2 + + +From 529c0c1da0259953130a0e098820854336b1c87e 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 58a2f57..01146fa 100644 +--- a/extensions/systemMonitor/extension.js ++++ b/extensions/systemMonitor/extension.js +@@ -4,10 +4,12 @@ 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'); +@@ -29,18 +31,21 @@ const Indicator = new Lang.Class({ + + _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(); +@@ -73,6 +78,7 @@ const Indicator = new Lang.Class({ + 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, +@@ -100,6 +106,14 @@ const Indicator = new Lang.Class({ + }); + }, + ++ /* MessageList.Message boilerplate */ ++ canClose: function() { ++ return false; ++ }, ++ ++ clear: function() { ++ }, ++ + destroy: function() { + Mainloop.source_remove(this._timeout); + +@@ -194,6 +208,7 @@ const Indicator = new Lang.Class({ + } + } + }); ++Signals.addSignalMethods(Indicator.prototype); // For MessageList.Message compat + + const CpuIndicator = new Lang.Class({ + Name: 'SystemMonitor.CpuIndicator', +@@ -302,9 +317,7 @@ const Extension = new Lang.Class({ + }, + + 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++) { +@@ -313,31 +326,18 @@ const Extension = new Lang.Class({ + 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) { +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,17 +1,4 @@ +-.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); +@@ -21,7 +8,6 @@ + -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 { +-- +2.14.2 + + +From 77669f312fcd4cdc823c74fecb12480fb5e9769f 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 01146fa..cc49cbb 100644 +--- a/extensions/systemMonitor/extension.js ++++ b/extensions/systemMonitor/extension.js +@@ -303,6 +303,24 @@ const MemoryIndicator = new Lang.Class({ + } + }); + ++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({ +@@ -317,7 +335,7 @@ const Extension = new Lang.Class({ + }, + + enable: function() { +- this._section = new MessageList.MessageListSection(_("System Monitor")); ++ this._section = new SystemMonitorSection(); + this._indicators = [ ]; + + for (let i = 0; i < INDICATORS.length; i++) { +-- +2.14.2 + + +From 7ba627fcc4646366c851fb0e4a4aa0ddf521b6b7 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/Makefile.am | 6 ++++++ + extensions/systemMonitor/classic.css | 6 ++++++ + 2 files changed, 12 insertions(+) + create mode 100644 extensions/systemMonitor/classic.css + +diff --git a/extensions/systemMonitor/Makefile.am b/extensions/systemMonitor/Makefile.am +index 50ce6d2..64a61df 100644 +--- a/extensions/systemMonitor/Makefile.am ++++ b/extensions/systemMonitor/Makefile.am +@@ -1,3 +1,9 @@ + EXTENSION_ID = systemMonitor + ++EXTRA_MODULES = ++ ++if CLASSIC_MODE ++ EXTRA_MODULES += classic.css ++endif ++ + include ../../extension.mk +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; ++} +-- +2.14.2 + diff --git a/SPECS/gnome-shell-extensions.spec b/SPECS/gnome-shell-extensions.spec new file mode 100644 index 0000000..3bd14ba --- /dev/null +++ b/SPECS/gnome-shell-extensions.spec @@ -0,0 +1,859 @@ +%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.26.2 +Release: 3%{?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 +# BuildRequires: gnome-common +BuildRequires: autoconf automake +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-classic-shade-panel-in-overview.patch +Patch3: 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-loginDialog-make-info-messages-themed.patch +Patch8: apps-menu-follow-sort-order.patch +Patch9: apps-menu-support-separators.patch +Patch10: classic-style-fixes.patch +Patch11: 0001-Include-top-icons-in-classic-session.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 + * 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}-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}-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}-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-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 +%autosetup -S git + + +%build +autoreconf -f +# In case we build from a Git checkout +[ -x autogen.sh ] && NOCONFIGURE=1 ./autogen.sh +%configure --enable-extensions="all" +make %{?_smp_mflags} + + +%install +%make_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 + + +%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}-drive-menu +%{_datadir}/gnome-shell/extensions/drive-menu*/ + + +%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-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-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 +* 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