From bc3989cb84b9497167632ab4a9769d21c5092071 Mon Sep 17 00:00:00 2001 From: CentOS Sources Date: Oct 30 2018 06:30:33 +0000 Subject: import gnome-shell-extensions-3.28.1-5.el7 --- diff --git a/.gitignore b/.gitignore index 6eaa884..0fcaf39 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +1,3 @@ -SOURCES/gnome-shell-extensions-3.26.2.tar.xz +SOURCES/3.4.1.tar.gz +SOURCES/3.4.5.tar.gz +SOURCES/gnome-shell-extensions-3.28.1.tar.xz diff --git a/.gnome-shell-extensions.metadata b/.gnome-shell-extensions.metadata index dbbdacb..73d481f 100644 --- a/.gnome-shell-extensions.metadata +++ b/.gnome-shell-extensions.metadata @@ -1 +1,3 @@ -47f7e208484a2a49f87b4db7b66bd4cb8cdbce8e SOURCES/gnome-shell-extensions-3.26.2.tar.xz +97020dcf5d0a8f2cad8c6e8672c4a837c8fd9a05 SOURCES/3.4.1.tar.gz +dcb2d3fcf3b1f577c8cf4cff0d77d21819189ea1 SOURCES/3.4.5.tar.gz +51b02a3157aa4c36af145b0c57b8132203954fc2 SOURCES/gnome-shell-extensions-3.28.1.tar.xz diff --git a/SOURCES/0001-Include-top-icons-in-classic-session.patch b/SOURCES/0001-Include-top-icons-in-classic-session.patch index 9f46e91..bea23c6 100644 --- a/SOURCES/0001-Include-top-icons-in-classic-session.patch +++ b/SOURCES/0001-Include-top-icons-in-classic-session.patch @@ -1,28 +1,99 @@ -From d5e0f26fc59216da2b2f129d4395a9da0c63e974 Mon Sep 17 00:00:00 2001 +From bf91d6c08f471ab729507d1ebd4c46b336ca2cef 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(-) + meson.build | 2 +- + 1 file changed, 1 insertion(+), 1 deletion(-) -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], +diff --git a/meson.build b/meson.build +index cde2d34..7a4c0d6 100644 +--- a/meson.build ++++ b/meson.build +@@ -11,81 +11,81 @@ i18n = import('i18n') + + datadir = get_option('datadir') + + shelldir = join_paths(datadir, 'gnome-shell') + extensiondir = join_paths(shelldir, 'extensions') + modedir = join_paths(shelldir, 'modes') + themedir = join_paths(shelldir, 'theme') + + schemadir = join_paths(datadir, 'glib-2.0', 'schemas') + sessiondir = join_paths(datadir, 'gnome-session', 'sessions') + xsessiondir = join_paths(datadir, 'xsessions') + + extensionlib = files('lib/convenience.js') + + js52 = find_program('js52', required: false) + + ver_arr = meson.project_version().split('.') + if ver_arr[1].to_int().is_even() + shell_version = '@0@.@1@'.format(ver_arr[0], ver_arr[1]) + else + shell_version = '.'.join(ver_arr) + endif + + uuid_suffix = '@gnome-shell-extensions.gcampax.github.com' + + classic_extensions = [ + 'alternate-tab', + 'apps-menu', + 'places-menu', + 'launch-new-instance', ++ 'top-icons', + 'window-list' + ] + + default_extensions = classic_extensions + default_extensions += [ + 'drive-menu', + 'screenshot-window-sizer', + 'windowsNavigator', + 'workspace-indicator' + ] + + all_extensions = default_extensions + all_extensions += [ + 'auto-move-windows', + 'dash-to-dock', + 'example', + 'native-window-placement', + 'no-hot-corner', + 'panel-favorites', + 'systemMonitor', +- 'top-icons', + 'updates-dialog', + 'user-theme' + ] + + enabled_extensions = get_option('enable_extensions') + + if enabled_extensions.length() == 0 + set = get_option('extension_set') + + if set == 'classic' + enabled_extensions += classic_extensions + elif set == 'default' + enabled_extensions += default_extensions + elif set == 'all' + enabled_extensions += all_extensions + endif + endif + + classic_mode_enabled = get_option('classic_mode') + + if classic_mode_enabled + # Sanity check: Make sure all classic extensions are enabled + foreach e : classic_extensions + if not enabled_extensions.contains(e) + error('Classic mode is enabled, ' + + 'but the required extension @0@ is not.'.format(e)) + endif + endforeach + endif + -- -2.14.3 +2.17.1 diff --git a/SOURCES/0001-Revert-data-Remove-nautilus-classic.patch b/SOURCES/0001-Revert-data-Remove-nautilus-classic.patch new file mode 100644 index 0000000..84bb163 --- /dev/null +++ b/SOURCES/0001-Revert-data-Remove-nautilus-classic.patch @@ -0,0 +1,22 @@ +From de72f146aa090957352e1bfb431e5965e20a9127 Mon Sep 17 00:00:00 2001 +From: Ray Strode +Date: Tue, 31 Jul 2018 14:34:22 -0400 +Subject: [PATCH] Revert "data: Remove nautilus classic" + +This reverts commit 0e625bedbadbf28d23cf0e0f1a53512785016789. +--- + data/gnome-classic.session.desktop.in | 2 +- + 1 file changed, 1 insertion(+), 1 deletion(-) + +diff --git a/data/gnome-classic.session.desktop.in b/data/gnome-classic.session.desktop.in +index f359a77..c26a887 100644 +--- a/data/gnome-classic.session.desktop.in ++++ b/data/gnome-classic.session.desktop.in +@@ -1,3 +1,3 @@ + [GNOME Session] + Name=GNOME Classic +-RequiredComponents=org.gnome.Shell;org.gnome.SettingsDaemon.A11ySettings;org.gnome.SettingsDaemon.Clipboard;org.gnome.SettingsDaemon.Color;org.gnome.SettingsDaemon.Datetime;org.gnome.SettingsDaemon.Housekeeping;org.gnome.SettingsDaemon.Keyboard;org.gnome.SettingsDaemon.MediaKeys;org.gnome.SettingsDaemon.Mouse;org.gnome.SettingsDaemon.Power;org.gnome.SettingsDaemon.PrintNotifications;org.gnome.SettingsDaemon.Rfkill;org.gnome.SettingsDaemon.ScreensaverProxy;org.gnome.SettingsDaemon.Sharing;org.gnome.SettingsDaemon.Smartcard;org.gnome.SettingsDaemon.Sound;org.gnome.SettingsDaemon.Wacom;org.gnome.SettingsDaemon.XSettings; ++RequiredComponents=org.gnome.Shell;org.gnome.SettingsDaemon.A11ySettings;org.gnome.SettingsDaemon.Clipboard;org.gnome.SettingsDaemon.Color;org.gnome.SettingsDaemon.Datetime;org.gnome.SettingsDaemon.Housekeeping;org.gnome.SettingsDaemon.Keyboard;org.gnome.SettingsDaemon.MediaKeys;org.gnome.SettingsDaemon.Mouse;org.gnome.SettingsDaemon.Power;org.gnome.SettingsDaemon.PrintNotifications;org.gnome.SettingsDaemon.Rfkill;org.gnome.SettingsDaemon.ScreensaverProxy;org.gnome.SettingsDaemon.Sharing;org.gnome.SettingsDaemon.Smartcard;org.gnome.SettingsDaemon.Sound;org.gnome.SettingsDaemon.Wacom;org.gnome.SettingsDaemon.XSettings;nautilus-classic; +-- +2.17.1 + diff --git a/SOURCES/0001-Update-style.patch b/SOURCES/0001-Update-style.patch index e92358b..091f008 100644 --- a/SOURCES/0001-Update-style.patch +++ b/SOURCES/0001-Update-style.patch @@ -1,36 +1,65 @@ -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 +From bddab939dedf770220f59394b4d4d5534063f0f1 Mon Sep 17 00:00:00 2001 +From: rpm-build +Date: Mon, 11 Jun 2018 16:40:34 -0400 Subject: [PATCH] Update style --- - data/gnome-classic.css | 6 ++++++ - 1 file changed, 6 insertions(+) + data/gnome-shell-sass/_common.scss | 19 +++++++++++++++++-- + 1 file changed, 17 insertions(+), 2 deletions(-) -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 { +diff --git a/data/gnome-shell-sass/_common.scss b/data/gnome-shell-sass/_common.scss +index 2f05098df..de3a9cdbc 100644 +--- a/data/gnome-shell-sass/_common.scss ++++ b/data/gnome-shell-sass/_common.scss +@@ -776,6 +776,11 @@ StScrollBar { + //dimensions of the icon are hardcoded + } + ++ .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; } ++ icon-size: 1em; ++ } ++ + .system-status-icon, + .app-menu-icon > StIcon, + .popup-menu-arrow { +@@ -1397,6 +1402,14 @@ StScrollBar { - .login-dialog-prompt-label { - color: #bebeb6; --- -2.14.2 - + } + ++ .app-well-hover-text { ++ text-align: center; ++ color: $osd_fg_color; ++ background-color: $osd_bg_color; ++ border-radius: 5px; ++ padding: 3px; ++ } ++ + .app-well-app-running-dot { //running apps indicator + width: 10px; height: 3px; + background-color: $selected_bg_color; +@@ -1769,7 +1782,12 @@ StScrollBar { + .login-dialog-banner { color: darken($osd_fg_color,10%); } + .login-dialog-button-box { spacing: 5px; } + .login-dialog-message-warning { color: $warning_color; } +- .login-dialog-message-hint { padding-top: 0; padding-bottom: 20px; } ++ .login-dialog-message-hint, .login-dialog-message { ++ color: darken($osd_fg_color, 20%); ++ padding-top: 0; ++ padding-bottom: 20px; ++ min-height: 2.75em; ++ } + .login-dialog-user-selection-box { padding: 100px 0px; } + .login-dialog-not-listed-label { + padding-left: 2px; +@@ -1825,6 +1843,10 @@ StScrollBar { + padding-bottom: 12px; + spacing: 8px; + width: 23em; ++ .login-dialog-timed-login-indicator { ++ height: 2px; ++ background-color: darken($fg_color,40%); ++ } + } + + .login-dialog-prompt-label { diff --git a/SOURCES/0001-apps-menu-Explicitly-set-label_actor.patch b/SOURCES/0001-apps-menu-Explicitly-set-label_actor.patch index b7a1767..50ab75d 100644 --- a/SOURCES/0001-apps-menu-Explicitly-set-label_actor.patch +++ b/SOURCES/0001-apps-menu-Explicitly-set-label_actor.patch @@ -1,4 +1,4 @@ -From d04e66f692f5022a476c4ad8a9caeb0b5952b7b0 Mon Sep 17 00:00:00 2001 +From c6d579383b1e3f092cc289291d8f701011d37a67 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Florian=20M=C3=BCllner?= Date: Thu, 17 Mar 2016 17:15:38 +0100 Subject: [PATCH] apps-menu: Explicitly set label_actor @@ -10,21 +10,102 @@ so set the label_actor explicitly as workaround. 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 +index 5067b63..49a05c7 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(); +@@ -7,61 +7,63 @@ const Shell = imports.gi.Shell; + const St = imports.gi.St; + const Clutter = imports.gi.Clutter; + const Main = imports.ui.main; + const Meta = imports.gi.Meta; + const PanelMenu = imports.ui.panelMenu; + const PopupMenu = imports.ui.popupMenu; + const Gtk = imports.gi.Gtk; + const GLib = imports.gi.GLib; + const Gio = imports.gi.Gio; + const Signals = imports.signals; + const Pango = imports.gi.Pango; + + const Gettext = imports.gettext.domain('gnome-shell-extensions'); + const _ = Gettext.gettext; + + const ExtensionUtils = imports.misc.extensionUtils; + const Me = ExtensionUtils.getCurrentExtension(); + const Convenience = Me.imports.convenience; + + const appSys = Shell.AppSystem.get_default(); + + const APPLICATION_ICON_SIZE = 32; + const HORIZ_FACTOR = 5; + const MENU_HEIGHT_OFFSET = 132; + const NAVIGATION_REGION_OVERSHOOT = 50; + + class ActivitiesMenuItem extends PopupMenu.PopupBaseMenuItem { + constructor(button) { + super(); this._button = button; - this.actor.add_child(new St.Label({ text: _("Activities Overview") })); + let label = new St.Label({ text: _("Activities Overview") }); + this.actor.add_child(label); + this.actor.label_actor = label; - }, + } + + activate(event) { + this._button.menu.toggle(); + Main.overview.toggle(); + super.activate(event); + } + }; + + class ApplicationMenuItem extends PopupMenu.PopupBaseMenuItem { + constructor(button, app) { + super(); + this._app = app; + this._button = button; + + this._iconBin = new St.Bin(); + this.actor.add_child(this._iconBin); + + let appLabel = new St.Label({ text: app.get_name(), y_expand: true, + y_align: Clutter.ActorAlign.CENTER }); + this.actor.add_child(appLabel); + this.actor.label_actor = appLabel; + + let textureCache = St.TextureCache.get_default(); + let iconThemeChangedId = textureCache.connect('icon-theme-changed', + this._updateIcon.bind(this)); + this.actor.connect('destroy', () => { + textureCache.disconnect(iconThemeChangedId); + }); + this._updateIcon(); +@@ -102,61 +104,63 @@ class ApplicationMenuItem extends PopupMenu.PopupBaseMenuItem { + } + + getDragActor() { + return this._app.create_icon_texture(APPLICATION_ICON_SIZE); + } - activate: function(event) { -@@ -140,7 +142,9 @@ const CategoryMenuItem = new Lang.Class({ + getDragActorSource() { + return this._iconBin; + } + + _updateIcon() { + this._iconBin.set_child(this.getDragActor()); + } + }; + + class CategoryMenuItem extends PopupMenu.PopupBaseMenuItem { + constructor(button, category) { + super(); + this._category = category; + this._button = button; + + this._oldX = -1; + this._oldY = -1; + + let name; + if (this._category) + name = this._category.get_name(); else name = _("Favorites"); @@ -32,9 +113,36 @@ index 41d1faf..03000bf 100644 + 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)); - }, + this.actor.connect('motion-event', this._onMotionEvent.bind(this)); + } + + activate(event) { + this._button.selectCategory(this._category, this); + this._button.scrollToCatButton(this); + super.activate(event); + } + + _isNavigatingSubmenu([x, y]) { + let [posX, posY] = this.actor.get_transformed_position(); + + if (this._oldX == -1) { + this._oldX = x; + this._oldY = y; + return true; + } + + let deltaX = Math.abs(x - this._oldX); + let deltaY = Math.abs(y - this._oldY); + + this._oldX = x; + this._oldY = y; + + // If it lies outside the x-coordinates then it is definitely outside. + if (posX > x || posX + this.actor.width < x) + return false; + // If it lies inside the menu item then it is definitely inside. + if (posY <= y && posY + this.actor.height >= y) -- -2.14.2 +2.17.1 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..2d943e4 --- /dev/null +++ b/SOURCES/0001-classic-Shade-panel-in-overview.patch @@ -0,0 +1,34 @@ +From 91ed30147a69d53d7c170b65602be5f90851666e 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.scss | 8 +++----- + 1 file changed, 3 insertions(+), 5 deletions(-) + +diff --git a/data/gnome-classic.scss b/data/gnome-classic.scss +index 9e23506..e8f4803 100644 +--- a/data/gnome-classic.scss ++++ b/data/gnome-classic.scss +@@ -19,11 +19,9 @@ $variant: 'light'; + border-bottom: 1px solid #666; + app-icon-bottom-clip: 0px; + &:overview { +- background-color: #000; +- background-gradient-end: #000; +- border-top-color: #000; +- border-bottom: 1px solid #000; +- .panel-button { color: #fff; } ++ background-color: darken($bg_color,5%); ++ background-gradient-end: darken($bg_color,10%); ++ .panel-button { color: darken($fg_color,5%); } + } + + .panel-button { +-- +2.17.1 + diff --git a/SOURCES/0001-classic-shade-panel-in-overview.patch b/SOURCES/0001-classic-shade-panel-in-overview.patch deleted file mode 100644 index 950629f..0000000 --- a/SOURCES/0001-classic-shade-panel-in-overview.patch +++ /dev/null @@ -1,35 +0,0 @@ -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-data-drop-app-icon-styling.patch b/SOURCES/0001-data-drop-app-icon-styling.patch new file mode 100644 index 0000000..c1e993d --- /dev/null +++ b/SOURCES/0001-data-drop-app-icon-styling.patch @@ -0,0 +1,162 @@ +From 524bc0710f6dbbbb6b8135253f03ce5e0059da02 Mon Sep 17 00:00:00 2001 +From: rpm-build +Date: Tue, 4 Sep 2018 09:58:57 -0400 +Subject: [PATCH] data: drop app icon styling + +classic session doesn't show an app icon in the app menu, so +putting a drop shadow around where it would be creates screen +artifacts. + +This commit drops the styling of the app icon that doesn't exist. +--- + data/gnome-shell-sass/_common.scss | 10 ---------- + 1 file changed, 10 deletions(-) + +diff --git a/data/gnome-shell-sass/_common.scss b/data/gnome-shell-sass/_common.scss +index 1ceadf4..3cce6c1 100644 +--- a/data/gnome-shell-sass/_common.scss ++++ b/data/gnome-shell-sass/_common.scss +@@ -742,140 +742,130 @@ StScrollBar { + + #panelLeft, #panelCenter { // spacing between activities<>app menu and such + spacing: 4px; + } + + .panel-corner { + -panel-corner-radius: $panel-corner-radius; + -panel-corner-background-color: rgba(0, 0, 0, 0.35); + -panel-corner-border-width: 2px; + -panel-corner-border-color: transparent; + + &:active, &:overview, &:focus { + -panel-corner-border-color: lighten($selected_bg_color,5%); + } + + &.lock-screen, &.login-screen, &.unlock-screen { + -panel-corner-radius: 0; + -panel-corner-background-color: transparent; + -panel-corner-border-color: transparent; + } + } + + .panel-button { + -natural-hpadding: 12px; + -minimum-hpadding: 6px; + font-weight: bold; + color: #eee; + text-shadow: 0px 1px 2px rgba(0, 0, 0, 0.9); + transition-duration: 100ms; + +- .app-menu-icon { +- -st-icon-style: symbolic; +- margin-left: 4px; +- margin-right: 4px; +- //dimensions of the icon are hardcoded +- } +- + .panel-logo-icon { + padding-right: .4em; + icon-size: 1em; + } + + .system-status-icon, +- .app-menu-icon > StIcon, + .popup-menu-arrow { + icon-shadow: 0px 1px 2px rgba(0, 0, 0, 0.9); + } + + &:hover { + color: lighten($fg_color, 10%); + text-shadow: 0px 1px 6px rgba(0, 0, 0, 1); + + .system-status-icon, +- .app-menu-icon > StIcon, + .popup-menu-arrow { + icon-shadow: 0px 1px 6px rgba(0, 0, 0, 1); + } + } + + &:active, &:overview, &:focus, &:checked { + // Trick due to St limitations. It needs a background to draw + // a box-shadow + background-color: rgba(0, 0, 0, 0.01); + box-shadow: inset 0 -2px 0px lighten($selected_bg_color,5%); + color: lighten($fg_color,10%); + + & > .system-status-icon { icon-shadow: black 0 2px 2px; } + } + + .system-status-icon { icon-size: 1.09em; padding: 0 5px; } + .unlock-screen &, + .login-screen &, + .lock-screen & { + color: lighten($fg_color, 10%); + &:focus, &:hover, &:active { color: lighten($fg_color, 10%); } + } + } + + .panel-status-indicators-box, + .panel-status-menu-box { + spacing: 2px; + } + + // spacing between power icon and (optional) percentage label + .power-status.panel-status-indicators-box { + spacing: 0; + } + + .screencast-indicator { color: $warning_color; } + + &.solid { + background-color: black; + /* transition from transparent to solid */ + transition-duration: 300ms; + + .panel-corner { + -panel-corner-background-color: black; + } + + .panel-button { + color: #ccc; + text-shadow: none; + + &:hover, &:active, &:overview, &:focus, &:checked { + color: lighten($fg_color, 10%); + } + } + + .system-status-icon, +- .app-menu-icon > StIcon, + .popup-menu-arrow { + icon-shadow: none; + } + } + } + + // calendar popover + #calendarArea { + padding: 0.75em 1.0em; + } + + .calendar { + margin-bottom: 1em; + } + + .calendar, + .datemenu-today-button, + .datemenu-displays-box, + .message-list-sections { + margin: 0 1.5em; + } + + .datemenu-calendar-column { spacing: 0.5em; } + .datemenu-displays-section { padding-bottom: 3em; } + .datemenu-displays-box { spacing: 1em; } + + .datemenu-calendar-column { + border: 0 solid lighten($bg_color,5%); + &:ltr { border-left-width: 1px; } + &:rtl { border-right-width: 1px; } +-- +2.17.1 + diff --git a/SOURCES/0001-loginDialog-make-info-messages-themed.patch b/SOURCES/0001-loginDialog-make-info-messages-themed.patch deleted file mode 100644 index 25108d7..0000000 --- a/SOURCES/0001-loginDialog-make-info-messages-themed.patch +++ /dev/null @@ -1,32 +0,0 @@ -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 index 661d577..068e606 100644 --- a/SOURCES/add-extra-extensions.patch +++ b/SOURCES/add-extra-extensions.patch @@ -1,62 +1,23 @@ -From 8cf377db7c25c6908803bdf118e8a4c419479b7b Mon Sep 17 00:00:00 2001 +From 4a5b83835224c80847892029213838545df065e2 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/extension.js | 225 ++++++++++++++++++++++++++ + extensions/top-icons/meson.build | 5 + 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 + meson.build | 1 + + 5 files changed, 242 insertions(+) create mode 100644 extensions/top-icons/extension.js + create mode 100644 extensions/top-icons/meson.build create mode 100644 extensions/top-icons/metadata.json.in create mode 100644 extensions/top-icons/stylesheet.css -diff --git a/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 +index 0000000..7312a26 --- /dev/null +++ b/extensions/top-icons/extension.js @@ -0,0 +1,225 @@ @@ -114,7 +75,7 @@ index 0000000..953ea28 + createTray(); +} + -+function createSource (title, pid, ndata, sender, trayIcon) { ++function createSource (title, pid, ndata, sender, trayIcon) { + if (trayIcon) { + onTrayIconAdded(this, trayIcon, title); + return null; @@ -212,7 +173,7 @@ index 0000000..953ea28 + 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 = []; @@ -252,7 +213,7 @@ index 0000000..953ea28 + 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', @@ -275,7 +236,7 @@ index 0000000..953ea28 + parent.destroy(); + notificationDaemon._onTrayIconAdded(notificationDaemon, icon); + } -+ ++ + icons = []; +} + @@ -285,6 +246,17 @@ index 0000000..953ea28 + else + destroyTray(); +} +diff --git a/extensions/top-icons/meson.build b/extensions/top-icons/meson.build +new file mode 100644 +index 0000000..48504f6 +--- /dev/null ++++ b/extensions/top-icons/meson.build +@@ -0,0 +1,5 @@ ++extension_data += configure_file( ++ input: metadata_name + '.in', ++ output: metadata_name, ++ configuration: metadata_conf ++) diff --git a/extensions/top-icons/metadata.json.in b/extensions/top-icons/metadata.json.in new file mode 100644 index 0000000..f1e2436 @@ -308,45 +280,61 @@ index 0000000..25134b6 +++ b/extensions/top-icons/stylesheet.css @@ -0,0 +1 @@ +/* This extensions requires no special styling */ +diff --git a/meson.build b/meson.build +index 40a8275..c16bde1 100644 +--- a/meson.build ++++ b/meson.build +@@ -54,6 +54,7 @@ all_extensions += [ + 'auto-move-windows', + 'example', + 'native-window-placement', ++ 'top-icons', + 'user-theme' + ] + -- -2.14.2 +2.17.1 -From 1c3daaf0284f93f8fb0b80acae332c9629aabd6d Mon Sep 17 00:00:00 2001 +From cbf1d6142b0b46fdd3947606286eb5abc0b1e02c 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 + extensions/dash-to-dock/Settings.ui | 3332 +++++++++++++++++ + extensions/dash-to-dock/appIconIndicators.js | 1124 ++++++ + extensions/dash-to-dock/appIcons.js | 1171 ++++++ + extensions/dash-to-dock/convenience.js | 74 + + extensions/dash-to-dock/dash.js | 1175 ++++++ + extensions/dash-to-dock/docking.js | 1925 ++++++++++ + extensions/dash-to-dock/extension.js | 23 + + extensions/dash-to-dock/intellihide.js | 323 ++ + extensions/dash-to-dock/launcherAPI.js | 244 ++ + extensions/dash-to-dock/media/glossy.svg | 139 + + extensions/dash-to-dock/media/logo.svg | 528 +++ + extensions/dash-to-dock/meson.build | 23 + + extensions/dash-to-dock/metadata.json.in | 12 + + ....shell.extensions.dash-to-dock.gschema.xml | 540 +++ + extensions/dash-to-dock/prefs.js | 868 +++++ + extensions/dash-to-dock/stylesheet.css | 109 + + extensions/dash-to-dock/theming.js | 672 ++++ + extensions/dash-to-dock/utils.js | 255 ++ + extensions/dash-to-dock/windowPreview.js | 630 ++++ + meson.build | 1 + + 20 files changed, 13168 insertions(+) create mode 100644 extensions/dash-to-dock/Settings.ui + create mode 100644 extensions/dash-to-dock/appIconIndicators.js create mode 100644 extensions/dash-to-dock/appIcons.js create mode 100644 extensions/dash-to-dock/convenience.js create mode 100644 extensions/dash-to-dock/dash.js - create mode 100644 extensions/dash-to-dock/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/launcherAPI.js + create mode 100644 extensions/dash-to-dock/media/glossy.svg create mode 100644 extensions/dash-to-dock/media/logo.svg + create mode 100644 extensions/dash-to-dock/meson.build create mode 100644 extensions/dash-to-dock/metadata.json.in create mode 100644 extensions/dash-to-dock/org.gnome.shell.extensions.dash-to-dock.gschema.xml create mode 100644 extensions/dash-to-dock/prefs.js @@ -355,100 +343,22 @@ Subject: [PATCH 2/5] Add dash-to-dock extension 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 +index 0000000..2b164a8 --- /dev/null +++ b/extensions/dash-to-dock/Settings.ui -@@ -0,0 +1,2998 @@ +@@ -0,0 +1,3332 @@ + -+ ++ + -+ ++ + + 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 @@ -457,237 +367,96 @@ index 0000000..d8d498f + 12 + vertical + -+ ++ + True + False + 0 + in + -+ ++ + True + False + none + -+ ++ + 100 + 80 + True + True + -+ ++ + True + False + 12 + 12 + 12 + 12 -+ vertical -+ 12 ++ 32 + -+ ++ + True + False -+ 32 -+ -+ -+ True -+ True -+ -+ -+ 1 -+ 0 -+ -+ -+ -+ -+ True -+ False -+ True -+ Customize indicator style -+ fill -+ 0 -+ -+ -+ 0 -+ 0 -+ -+ ++ True ++ When set to minimize, double clicking minimizes all the windows of the application. ++ True ++ 40 ++ 0 ++ + + -+ False -+ True -+ 0 ++ 0 ++ 1 + + + -+ ++ + 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 -+ -+ ++ True ++ Shift+Click action ++ 0 + + -+ False -+ True -+ 1 ++ 0 ++ 0 ++ ++ ++ ++ ++ True ++ False ++ center ++ ++ Raise window ++ Minimize window ++ Launch new instance ++ Cycle through windows ++ Minimize or overview ++ Show window previews ++ Minimize or show previews ++ Quit ++ ++ ++ ++ 1 ++ 0 ++ 2 + + + + + + -+ -+ -+ -+ -+ -+ -+ -+ False -+ True -+ 0 -+ -+ -+ -+ -+ True -+ False -+ 12 -+ 12 -+ 12 -+ 12 -+ vertical -+ -+ -+ True -+ False -+ 0 -+ in -+ -+ -+ True -+ False -+ none + -+ ++ + 100 + 80 + True + True + -+ ++ + True + False + 12 @@ -696,39 +465,14 @@ index 0000000..d8d498f + 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. ++ Behavior for Middle-Click. + True -+ 40 ++ 40 ++ 0 + @@ -738,45 +482,13 @@ index 0000000..d8d498f + 1 + + -+ -+ -+ -+ -+ -+ -+ 100 -+ 80 -+ True -+ True -+ -+ -+ True -+ False -+ 12 -+ 12 -+ 12 -+ 12 -+ 32 -+ -+ -+ True -+ True -+ end -+ center -+ -+ -+ 1 -+ 0 -+ 2 -+ -+ + -+ ++ + True + False + True ++ Middle-Click action + 0 -+ Show the dock if it is hidden + + + 0 @@ -784,20 +496,25 @@ index 0000000..d8d498f + + + -+ ++ + True + False -+ 0 -+ If using autohide, the dock will appear for a short time when triggering the shortcut. -+ True -+ 40 -+ ++ center ++ ++ Raise window ++ Minimize window ++ Launch new instance ++ Cycle through windows ++ Minimize or overview ++ Show window previews ++ Minimize or show previews ++ Quit ++ + + -+ 0 -+ 1 ++ 1 ++ 0 ++ 2 + + + @@ -805,13 +522,13 @@ index 0000000..d8d498f + + + -+ ++ + 100 + 80 + True + True + -+ ++ + True + False + 12 @@ -820,36 +537,14 @@ index 0000000..d8d498f + 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> ++ Behavior for Shift+Middle-Click. + True ++ 40 ++ 0 + @@ -859,49 +554,39 @@ index 0000000..d8d498f + 1 + + -+ -+ -+ -+ -+ -+ -+ True -+ True -+ -+ -+ True -+ False -+ 12 -+ 12 -+ 12 -+ 12 -+ True -+ 6 -+ 32 + -+ ++ + True -+ True -+ end -+ shortcut_time_adjustment -+ 3 ++ False ++ True ++ Shift+Middle-Click action ++ 0 + + -+ 1 ++ 0 + 0 + + + -+ ++ + True + False -+ True -+ 0 -+ Hide timeout (s) ++ center ++ ++ Raise window ++ Minimize window ++ Launch new instance ++ Cycle through windows ++ Minimize or overview ++ Show window previews ++ Minimize or show previews ++ Quit ++ + + -+ 0 ++ 1 + 0 ++ 2 + + + @@ -921,7 +606,23 @@ index 0000000..d8d498f + + + -+ ++ ++ 1 ++ 0.01 ++ 0.10000000000000001 ++ ++ ++ 0.33000000000000002 ++ 1 ++ 0.01 ++ 0.10000000000000001 ++ ++ ++ 10 ++ 1 ++ 5 ++ ++ + True + False + 12 @@ -930,223 +631,264 @@ index 0000000..d8d498f + 12 + vertical + -+ ++ + True + False + 0 + in + -+ ++ + True + False + none + -+ ++ + 100 + 80 + True + True + -+ ++ + True + False + 12 + 12 + 12 + 12 -+ 32 ++ vertical ++ 12 + -+ ++ + True + False -+ True -+ 0 -+ 40 -+ When set to minimize, double clicking minimizes all the windows of the application. -+ True -+ ++ 32 ++ ++ ++ True ++ False ++ center ++ Enable Unity7 like glossy backlit items ++ 0 ++ ++ ++ True ++ True ++ 0 ++ ++ ++ ++ ++ True ++ True ++ center ++ ++ ++ False ++ True ++ 1 ++ ++ + + -+ 0 -+ 1 ++ False ++ True ++ 0 + + + -+ ++ + 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 -+ ++ ++ ++ True ++ False ++ start ++ Use dominant color ++ ++ ++ True ++ True ++ 0 ++ ++ ++ ++ ++ True ++ True ++ ++ ++ False ++ True ++ 1 ++ ++ + + -+ 0 -+ 1 ++ False ++ True ++ 1 + + + -+ ++ + True + False -+ True -+ 0 -+ Middle-Click action ++ 32 ++ ++ ++ True ++ True ++ ++ ++ 1 ++ 0 ++ ++ ++ ++ ++ True ++ False ++ True ++ Customize indicator style ++ fill ++ 0 ++ ++ ++ 0 ++ 0 ++ ++ + + -+ 0 -+ 0 ++ False ++ True ++ 2 + + + -+ ++ + True + False -+ center -+ -+ Raise window -+ Minimize window -+ Launch new instance -+ Cycle through windows -+ Minimize or overview -+ Show window previews -+ Quit -+ ++ 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 ++ ++ + + -+ 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 ++ 3 + + + @@ -1223,8 +965,8 @@ index 0000000..d8d498f + True + False + True -+ 0 + Show the dock on ++ 0 + + + 0 @@ -1259,6 +1001,12 @@ index 0000000..d8d498f + 2 + + ++ ++ ++ ++ ++ ++ + + + @@ -1282,8 +1030,8 @@ index 0000000..d8d498f + True + False + True -+ 0 + Position on screen ++ 0 + + + False @@ -1423,9 +1171,9 @@ index 0000000..d8d498f + True + False + True -+ 0 -+ Hide the dock when it obstructs a window of the the current application. More refined settings are available. ++ Hide the dock when it obstructs a window of the current application. More refined settings are available. + True ++ 0 + @@ -1440,8 +1188,8 @@ index 0000000..d8d498f + True + False + True -+ 0 + Intelligent autohide ++ 0 + + + 0 @@ -1544,8 +1292,8 @@ index 0000000..d8d498f + + True + False -+ 0 + Dock size limit ++ 0 + + + 0 @@ -1607,8 +1355,8 @@ index 0000000..d8d498f + + True + False -+ 0 + Icon size limit ++ 0 + + + 0 @@ -1728,8 +1476,8 @@ index 0000000..d8d498f + True + False + True -+ 0 + Show favorite applications ++ 0 + + + 0 @@ -1770,8 +1518,8 @@ index 0000000..d8d498f + True + False + True -+ 0 + Show running applications ++ 0 + + + 0 @@ -1868,9 +1616,9 @@ index 0000000..d8d498f + True + False + True -+ 0 + If disabled, these settings are accessible from gnome-tweak-tool or the extension website. + True ++ 0 + @@ -1885,9 +1633,9 @@ index 0000000..d8d498f + True + False + True -+ 0 + Show <i>Applications</i> icon + True ++ 0 + + + 0 @@ -2005,10 +1753,10 @@ index 0000000..d8d498f + 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 + @@ -2023,8 +1771,8 @@ index 0000000..d8d498f + True + False + True -+ 0 + Use keyboard shortcuts to activate apps ++ 0 + + + 0 @@ -2068,6 +1816,11 @@ index 0000000..d8d498f + end + center + ++ ++ False ++ True ++ 1 ++ + + + @@ -2121,9 +1874,9 @@ index 0000000..d8d498f + True + False + True -+ 0 + Behaviour when clicking on the icon of a running application. + True ++ 0 + @@ -2138,8 +1891,8 @@ index 0000000..d8d498f + True + False + True -+ 0 + Click action ++ 0 + + + 0 @@ -2168,6 +1921,11 @@ index 0000000..d8d498f + + + ++ ++ False ++ True ++ 0 ++ + + + @@ -2181,11 +1939,19 @@ index 0000000..d8d498f + Cycle through windows + Minimize or overview + Show window previews ++ Minimize or show previews + + ++ ++ False ++ True ++ 1 ++ + + + ++ 1 ++ 0 + 2 + + @@ -2359,9 +2125,9 @@ index 0000000..d8d498f + 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 + @@ -2376,8 +2142,8 @@ index 0000000..d8d498f + True + False + True -+ 0 + Use built-in theme ++ 0 + + + 0 @@ -2455,8 +2221,8 @@ index 0000000..d8d498f + True + False + True -+ 0 + Save space reducing padding and border radius. ++ 0 + @@ -2471,8 +2237,8 @@ index 0000000..d8d498f + True + False + True -+ 0 + Shrink the dash ++ 0 + + + 0 @@ -2497,28 +2263,12 @@ index 0000000..d8d498f + 12 + 32 + -+ -+ True -+ False -+ True -+ 0 -+ Show a dot for each windows of the application. -+ -+ -+ -+ 0 -+ 1 -+ -+ -+ + + True + False + True ++ Customize windows counter indicators + 0 -+ Show windows counter indicators + + + 0 @@ -2531,7 +2281,7 @@ index 0000000..d8d498f + False + 6 + -+ ++ + True + True + True @@ -2556,11 +2306,19 @@ index 0000000..d8d498f + + + -+ ++ + True -+ True -+ end -+ center ++ False ++ ++ Default ++ Dots ++ Squares ++ Dashes ++ Segmented ++ Solid ++ Ciliora ++ Metro ++ + + + False @@ -2575,6 +2333,9 @@ index 0000000..d8d498f + 2 + + ++ ++ ++ + + + @@ -2597,9 +2358,9 @@ index 0000000..d8d498f + True + False + True -+ 0 + Set the background color for the dash. + True ++ 0 + @@ -2614,8 +2375,8 @@ index 0000000..d8d498f + True + False + True -+ 0 + Customize the dash color ++ 0 + + + 0 @@ -2685,25 +2446,12 @@ index 0000000..d8d498f + 12 + 32 + -+ -+ True -+ True -+ end -+ center -+ -+ -+ 1 -+ 0 -+ 2 -+ -+ -+ + + True + False + True -+ 0 + Tune the dash background opacity. ++ 0 + @@ -2718,14 +2466,68 @@ index 0000000..d8d498f + True + False + True -+ 0 + Customize opacity ++ 0 + + + 0 + 0 + + ++ ++ ++ True ++ False ++ 6 ++ ++ ++ True ++ True ++ True ++ center ++ center ++ ++ ++ True ++ False ++ emblem-system-symbolic ++ ++ ++ ++ ++ ++ False ++ True ++ 0 ++ ++ ++ ++ ++ True ++ False ++ center ++ ++ Default ++ Fixed ++ Adaptive ++ Dynamic ++ ++ ++ ++ False ++ True ++ 1 ++ ++ ++ ++ ++ 1 ++ 0 ++ 2 ++ ++ + + + False @@ -3034,17 +2836,17 @@ index 0000000..d8d498f + + + -+ -+ 1000 -+ 50 -+ 250 ++ ++ 1 ++ 0.01 ++ 0.10000000000000001 + -+ ++ + 1 -+ 0.050000000000000003 -+ 0.25 ++ 0.01 ++ 0.10000000000000001 + -+ ++ + True + False + 12 @@ -3053,113 +2855,218 @@ index 0000000..d8d498f + 12 + vertical + -+ ++ + True + False + 0 + in + -+ ++ + True + False + none + -+ ++ ++ 100 ++ 80 + True + True + -+ ++ + True + False + 12 + 12 + 12 + 12 -+ 32 ++ vertical ++ 12 + -+ ++ + True + False -+ True -+ 0 -+ Show the dock by mouse hover on the screen edge. -+ True -+ ++ 32 ++ ++ ++ True ++ True ++ ++ ++ 1 ++ 0 ++ ++ ++ ++ ++ True ++ False ++ True ++ Customize minimum and maximum opacity values ++ fill ++ 0 ++ ++ ++ 0 ++ 0 ++ ++ + + -+ 0 -+ 1 ++ False ++ True ++ 0 + + + -+ ++ + 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 ++ 12 ++ 12 ++ 12 ++ 12 ++ 32 ++ ++ ++ True ++ False ++ Minimum opacity ++ ++ ++ False ++ True ++ 0 ++ ++ ++ ++ ++ True ++ True ++ min_opacity_adjustement ++ on ++ False ++ 0 ++ 0 ++ 2 ++ right ++ ++ ++ ++ ++ True ++ True ++ 1 ++ ++ + + -+ 0 -+ 3 -+ 2 ++ False ++ True ++ 1 + + + -+ -+ Enable in fullscreen mode ++ + True -+ True -+ False ++ False ++ 12 ++ 12 + 12 -+ 0 -+ True ++ 12 ++ 32 ++ ++ ++ True ++ False ++ Maximum opacity ++ ++ ++ False ++ True ++ 0 ++ ++ ++ ++ ++ True ++ True ++ max_opacity_adjustement ++ on ++ False ++ 0 ++ 0 ++ 2 ++ right ++ ++ ++ ++ ++ True ++ True ++ 1 ++ ++ + + -+ 0 -+ 2 -+ 2 ++ False ++ True ++ 1 + + + + + + ++ ++ ++ ++ ++ ++ ++ ++ False ++ True ++ 0 ++ ++ ++ ++ ++ 1000 ++ 50 ++ 250 ++ ++ ++ 10 ++ 0.25 ++ 1 ++ ++ ++ True ++ False ++ 12 ++ 12 ++ 12 ++ 12 ++ vertical ++ ++ ++ True ++ False ++ 0 ++ in ++ ++ ++ True ++ False ++ none + -+ ++ ++ 100 ++ 80 + True + True + -+ ++ + True + False + 12 @@ -3168,29 +3075,25 @@ index 0000000..d8d498f + 12 + 32 + -+ ++ + True -+ False -+ True -+ 0 -+ Show the dock when it doesn't obstruct application windows. -+ True -+ ++ True ++ end ++ center + + -+ 0 -+ 1 ++ 1 ++ 0 ++ 2 + + + -+ ++ + True + False + True ++ Number overlay + 0 -+ Dodge windows + + + 0 @@ -3198,82 +3101,20 @@ index 0000000..d8d498f + + + -+ -+ 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 -+ -+ ++ Temporarily show the application numbers over the icons, corresponding to the shortcut. ++ True ++ 40 ++ 0 ++ + + + 0 -+ 2 -+ 2 ++ 1 + + + @@ -3281,40 +3122,40 @@ index 0000000..d8d498f + + + -+ ++ ++ 100 ++ 80 + True + True + -+ ++ + True + False + 12 + 12 + 12 + 12 -+ True -+ 6 + 32 + -+ ++ + True + True + end -+ animation_time_adjustment -+ 3 ++ center + + + 1 + 0 ++ 2 + + + -+ ++ + True + False + True ++ Show the dock if it is hidden + 0 -+ Animation duration (s) + + + 0 @@ -3322,50 +3163,76 @@ index 0000000..d8d498f + + + -+ ++ + True -+ True -+ end -+ hide_timeout_adjustment -+ 3 -+ -+ -+ 1 ++ False ++ If using autohide, the dock will appear for a short time when triggering the shortcut. ++ True ++ 40 ++ 0 ++ ++ ++ ++ 0 + 1 + + ++ ++ ++ ++ ++ ++ ++ 100 ++ 80 ++ True ++ True ++ ++ ++ True ++ False ++ 12 ++ 12 ++ 12 ++ 12 ++ 32 + -+ -+ True -+ True -+ end -+ show_timeout_adjustment -+ 3 ++ ++ False ++ center ++ 12 + + + 1 -+ 2 ++ 0 + + + -+ ++ + True -+ True -+ 0.000 -+ pressure_threshold_adjustment ++ False ++ True ++ Shortcut for the options above ++ 0 + + -+ 1 -+ 3 ++ 0 ++ 0 + + + -+ ++ + True + False -+ True ++ Syntax: <Shift>, <Ctrl>, <Alt>, <Super> ++ True ++ 40 + 0 -+ Hide timeout (s) ++ + + + 0 @@ -3373,29 +3240,51 @@ index 0000000..d8d498f + + + -+ ++ ++ ++ ++ ++ ++ ++ ++ ++ True ++ True ++ ++ ++ True ++ False ++ 12 ++ 12 ++ 12 ++ 12 ++ True ++ 6 ++ 32 ++ ++ + True -+ False -+ True -+ 0 -+ Show timeout (s) ++ True ++ end ++ shortcut_time_adjustment ++ 3 + + -+ 0 -+ 2 ++ 1 ++ 0 + + + -+ ++ + True + False + True ++ Hide timeout (s) + 0 -+ Pressure threshold + + + 0 -+ 3 ++ 0 + + + @@ -3415,4231 +3304,3957 @@ index 0000000..d8d498f + + + -+ -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); -+ })); -+ } ++ ++ 1 ++ 0.050000000000000003 ++ 0.25 ++ ++ ++ True ++ False ++ 12 ++ 12 ++ 12 ++ 12 ++ vertical ++ ++ ++ True ++ False ++ 0 ++ in ++ ++ ++ True ++ False ++ none ++ ++ ++ True ++ True ++ ++ ++ True ++ False ++ 12 ++ 12 ++ 12 ++ 12 ++ 32 ++ ++ ++ True ++ False ++ True ++ Show the dock by mouse hover on the screen edge. ++ True ++ 0 ++ ++ ++ ++ 0 ++ 1 ++ ++ ++ ++ ++ True ++ False ++ True ++ Autohide ++ 0 ++ ++ ++ 0 ++ 0 ++ ++ ++ ++ ++ True ++ True ++ end ++ center ++ ++ ++ 1 ++ 0 ++ 2 ++ ++ ++ ++ ++ Push to show: require pressure to show the dock ++ True ++ True ++ False ++ 0 ++ True ++ ++ ++ 0 ++ 3 ++ 2 ++ ++ ++ ++ ++ Enable in fullscreen mode ++ True ++ True ++ False ++ 12 ++ 0 ++ True ++ ++ ++ 0 ++ 2 ++ 2 ++ ++ ++ ++ ++ ++ ++ ++ ++ True ++ True ++ ++ ++ True ++ False ++ 12 ++ 12 ++ 12 ++ 12 ++ 32 ++ ++ ++ True ++ False ++ True ++ Show the dock when it doesn't obstruct application windows. ++ True ++ 0 ++ ++ ++ ++ 0 ++ 1 ++ ++ ++ ++ ++ True ++ False ++ True ++ Dodge windows ++ 0 ++ ++ ++ 0 ++ 0 ++ ++ ++ ++ ++ True ++ True ++ end ++ center ++ ++ ++ 1 ++ 0 ++ 2 ++ ++ ++ ++ ++ True ++ False ++ vertical ++ ++ ++ All windows ++ True ++ True ++ False ++ 12 ++ 0 ++ True ++ True ++ ++ ++ ++ False ++ True ++ 0 ++ ++ ++ ++ ++ Only focused application's windows ++ True ++ True ++ False ++ 0 ++ True ++ True ++ all_windows_radio_button ++ ++ ++ ++ False ++ True ++ 1 ++ ++ ++ ++ ++ Only maximized windows ++ True ++ True ++ False ++ 0 ++ True ++ True ++ all_windows_radio_button ++ ++ ++ ++ False ++ True ++ 2 ++ ++ ++ ++ ++ 0 ++ 2 ++ 2 ++ ++ ++ ++ ++ ++ ++ ++ ++ True ++ True ++ ++ ++ True ++ False ++ 12 ++ 12 ++ 12 ++ 12 ++ True ++ 6 ++ 32 ++ ++ ++ True ++ True ++ end ++ animation_time_adjustment ++ 3 ++ ++ ++ 1 ++ 0 ++ ++ ++ ++ ++ True ++ False ++ True ++ Animation duration (s) ++ 0 ++ ++ ++ 0 ++ 0 ++ ++ ++ ++ ++ True ++ True ++ end ++ hide_timeout_adjustment ++ 3 ++ ++ ++ 1 ++ 1 ++ ++ ++ ++ ++ True ++ True ++ end ++ show_timeout_adjustment ++ 3 ++ ++ ++ 1 ++ 2 ++ ++ ++ ++ ++ True ++ True ++ 0.000 ++ pressure_threshold_adjustment ++ ++ ++ 1 ++ 3 ++ ++ ++ ++ ++ True ++ False ++ True ++ Hide timeout (s) ++ 0 ++ ++ ++ 0 ++ 1 ++ ++ ++ ++ ++ True ++ False ++ True ++ Show timeout (s) ++ 0 ++ ++ ++ 0 ++ 2 ++ ++ ++ ++ ++ True ++ False ++ True ++ Pressure threshold ++ 0 ++ ++ ++ 0 ++ 3 ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ False ++ True ++ 0 ++ ++ ++ ++ +diff --git a/extensions/dash-to-dock/appIconIndicators.js b/extensions/dash-to-dock/appIconIndicators.js +new file mode 100644 +index 0000000..6eb0706 +--- /dev/null ++++ b/extensions/dash-to-dock/appIconIndicators.js +@@ -0,0 +1,1124 @@ ++const Cogl = imports.gi.Cogl; ++const Cairo = imports.cairo; ++const Clutter = imports.gi.Clutter; ++const GdkPixbuf = imports.gi.GdkPixbuf ++const Gio = imports.gi.Gio; ++const Gtk = imports.gi.Gtk; ++const Lang = imports.lang; ++const Pango = imports.gi.Pango; ++const Shell = imports.gi.Shell; ++const St = imports.gi.St; + -+ let canFavorite = global.settings.is_writable('favorite-apps'); ++const Util = imports.misc.util; + -+ if (canFavorite) { -+ this._appendSeparator(); ++const Me = imports.misc.extensionUtils.getCurrentExtension(); ++const Utils = Me.imports.utils; + -+ let isFavorite = AppFavorites.getAppFavorites().isFavorite(this._source.app.get_id()); ++let tracker = Shell.WindowTracker.get_default(); + -+ 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()); -+ })); -+ } -+ } ++const RunningIndicatorStyle = { ++ DEFAULT: 0, ++ DOTS: 1, ++ SQUARES: 2, ++ DASHES: 3, ++ SEGMENTED: 4, ++ SOLID: 5, ++ CILIORA: 6, ++ METRO: 7 ++}; + -+ 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(); -+ }); -+ })); -+ } -+ } ++const MAX_WINDOWS_CLASSES = 4; + -+ } else { -+ this.parent(); -+ } + -+ // quit menu -+ this._appendSeparator(); -+ this._quitfromDashMenuItem = this._appendMenuItem(_("Quit")); -+ this._quitfromDashMenuItem.connect('activate', Lang.bind(this, function() { -+ this._source.closeAllWindows(); -+ })); ++/* ++ * This is the main indicator class to be used. The desired bahviour is ++ * obtained by composing the desired classes below based on the settings. ++ * ++ */ ++var AppIconIndicator = new Lang.Class({ + -+ this.update(); -+ }, ++ Name: 'DashToDock.AppIconIndicator', + -+ // 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() { ++ _init: function(source, settings) { ++ this._indicators = []; + -+ if(this._dtdSettings.get_boolean('show-windows-preview')){ ++ // Unity indicators always enabled for now ++ let unityIndicator = new UnityIndicator(source, settings); ++ this._indicators.push(unityIndicator); + -+ let windows = this._source.getInterestingWindows(); ++ // Choose the style for the running indicators ++ let runningIndicator = null; ++ let runningIndicatorStyle; + -+ // 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")); ++ if (settings.get_boolean('apply-custom-theme' )) { ++ runningIndicatorStyle = RunningIndicatorStyle.DOTS; ++ } else { ++ runningIndicatorStyle = settings.get_enum('running-indicator-style'); ++ } + -+ this._quitfromDashMenuItem.actor.show(); ++ switch (runningIndicatorStyle) { ++ case RunningIndicatorStyle.DEFAULT: ++ runningIndicator = new RunningIndicatorBase(source, settings); ++ break; + -+ } else { -+ this._quitfromDashMenuItem.actor.hide(); -+ } ++ case RunningIndicatorStyle.DOTS: ++ runningIndicator = new RunningIndicatorDots(source, settings); ++ break; + -+ // 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; -+ }); ++ case RunningIndicatorStyle.SQUARES: ++ runningIndicator = new RunningIndicatorSquares(source, settings); ++ break; + -+ let new_windows = windows.filter(function(w) {return old_windows.indexOf(w) < 0;}); -+ if (new_windows.length > 0) { -+ this._populateAllWindowMenu(windows); ++ case RunningIndicatorStyle.DASHES: ++ runningIndicator = new RunningIndicatorDashes(source, settings); ++ break; + -+ // 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; ++ case RunningIndicatorStyle.SEGMENTED: ++ runningIndicator = new RunningIndicatorSegmented(source, settings); ++ break; + -+ } ++ case RunningIndicatorStyle.SOLID: ++ runningIndicator = new RunningIndicatorSolid(source, settings); ++ break; + -+ // 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); -+ } ++ case RunningIndicatorStyle.CILIORA: ++ runningIndicator = new RunningIndicatorCiliora(source, settings); ++ break; + -+ // Update separators -+ this._getMenuItems().forEach(Lang.bind(this, this._updateSeparatorVisibility)); -+ } ++ case RunningIndicatorStyle.METRO: ++ runningIndicator = new RunningIndicatorMetro(source, settings); ++ break; + ++ default: ++ runningIndicator = new RunningIndicatorBase(source, settings); ++ } + ++ this._indicators.push(runningIndicator); + }, + -+ _populateAllWindowMenu: function(windows) { ++ update: function() { ++ for (let i=0; i 0) { ++/* ++ * Base class to be inherited by all indicators of any kind ++*/ ++const IndicatorBase = new Lang.Class({ + -+ let activeWorkspace = global.screen.get_active_workspace(); -+ let separatorShown = windows[0].get_workspace() != activeWorkspace; ++ Name: 'DashToDock.IndicatorBase', + -+ 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; -+ } ++ _init: function(source, settings) { ++ this._settings = settings; ++ this._source = source; ++ this._signalsHandler = new Utils.GlobalSignalsHandler(); + -+ let item = new WindowPreview.WindowPreviewMenuItem(window); -+ this._allWindowsMenuItem.menu.addMenuItem(item); -+ item.connect('activate', Lang.bind(this, function() { -+ this.emit('activate-window', window); -+ })); ++ this._sourceDestroyId = this._source.actor.connect('destroy', ++ Lang.bind(this._signalsHandler, this._signalsHandler.destroy) ++ ); ++ }, + -+ // 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); -+ })); -+ } -+ } ++ update: function() { + }, ++ ++ destroy: function() { ++ this._source.actor.disconnect(this._sourceDestroyId); ++ this._signalsHandler.destroy(); ++ } +}); -+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; -+ }); ++/* ++ * A base indicator class for running style, from which all other EunningIndicators should derive, ++ * providing some basic methods, variables definitions and their update, css style classes handling. ++ * ++ */ ++const RunningIndicatorBase = new Lang.Class({ + -+ // 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(); -+ }); ++ Name: 'DashToDock.RunningIndicatorBase', ++ Extends: IndicatorBase, + -+ if (settings.get_boolean('isolate-monitors')) -+ windows = windows.filter(function(w) { -+ return w.get_monitor() == monitorIndex; -+ }); ++ _init: function(source, settings) { + -+ return windows; -+} ++ this.parent(source, settings) + -+/** -+ * 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(); -+ }; ++ this._side = Utils.getPosition(this._settings); ++ this._nWindows = 0; + -+ 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)); ++ this._dominantColorExtractor = new DominantColorExtractor(this._source.app); + -+ showAppsIcon._menu = null; -+ showAppsIcon._menuManager = new PopupMenu.PopupMenuManager(showAppsIcon); -+ showAppsIcon._menuTimeoutId = 0; ++ // These statuses take into account the workspace/monitor isolation ++ this._isFocused = false; ++ this._isRunning = false; ++ }, + -+ showAppsIcon.showLabel = itemShowLabel; ++ update: function() { ++ // Limit to 1 to MAX_WINDOWS_CLASSES windows classes ++ this._nWindows = Math.min(this._source.getInterestingWindows().length, MAX_WINDOWS_CLASSES); + -+ showAppsIcon.popupMenu = function() { -+ showAppsIcon._removeMenuTimeout(); -+ showAppsIcon.actor.fake_release(); ++ // 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._source.app && this._nWindows > 0) ++ this._isFocused = true; ++ else ++ this._isFocused = false; + -+ 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); -+ } ++ // In the case of workspace isolation, we need to hide the dots of apps with ++ // no windows in the current workspace ++ if (this._source.app.state != Shell.AppState.STOPPED && this._nWindows > 0) ++ this._isRunning = true; ++ else ++ this._isRunning = false; + -+ showAppsIcon.emit('menu-state-changed', true); ++ this._updateCounterClass(); ++ this._updateFocusClass(); ++ this._updateDefaultDot(); ++ }, + -+ showAppsIcon.actor.set_hover(true); -+ showAppsIcon._menu.popup(); -+ showAppsIcon._menuManager.ignoreRelease(); -+ showAppsIcon.emit('sync-tooltip'); ++ _updateCounterClass: function() { ++ for (let i = 1; i <= MAX_WINDOWS_CLASSES; i++) { ++ let className = 'running' + i; ++ if (i != this._nWindows) ++ this._source.actor.remove_style_class_name(className); ++ else ++ this._source.actor.add_style_class_name(className); ++ } ++ }, + -+ return false; -+ }; ++ _updateFocusClass: function() { ++ if (this._isFocused) ++ this._source.actor.add_style_class_name('focused'); ++ else ++ this._source.actor.remove_style_class_name('focused'); ++ }, + -+ Signals.addSignalMethods(showAppsIcon); -+} ++ _updateDefaultDot: function() { ++ if (this._isRunning) ++ this._source._dot.show(); ++ else ++ this._source._dot.hide(); ++ }, + -+/** -+ * A menu for the showAppsIcon -+ */ -+const MyShowAppsIconMenu = new Lang.Class({ -+ Name: 'DashToDock.ShowAppsIconMenu', -+ Extends: MyAppIconMenu, ++ _hideDefaultDot: function() { ++ // I use opacity to hide the default dot because the show/hide function ++ // are used by the parent class. ++ this._source._dot.opacity = 0; ++ }, + -+ _redisplay: function() { -+ this.removeAll(); ++ _restoreDefaultDot: function() { ++ this._source._dot.opacity = 255; ++ }, + -+ /* 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); ++ _enableBacklight: function() { + -+ item.connect('activate', function () { -+ Util.spawn(["gnome-shell-extension-prefs", Me.metadata.uuid]); -+ }); -+ } -+}); ++ let colorPalette = this._dominantColorExtractor._getColorPalette(); + -+/** -+ * 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; ++ // Fallback ++ if (colorPalette === null) { ++ this._source._iconContainer.set_style( ++ 'border-radius: 5px;' + ++ 'background-gradient-direction: vertical;' + ++ 'background-gradient-start: #e0e0e0;' + ++ 'background-gradient-end: darkgray;' ++ ); + -+ this.label.set_text(this._labelText); -+ this.label.opacity = 0; -+ this.label.show(); ++ return; ++ } + -+ let [stageX, stageY] = this.get_transformed_position(); -+ let node = this.label.get_theme_node(); ++ this._source._iconContainer.set_style( ++ 'border-radius: 5px;' + ++ 'background-gradient-direction: vertical;' + ++ 'background-gradient-start: ' + colorPalette.original + ';' + ++ 'background-gradient-end: ' + colorPalette.darker + ';' ++ ); + -+ 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(); ++ _disableBacklight: function() { ++ this._source._iconContainer.set_style(null); ++ }, ++ ++ destroy: function() { ++ this.parent(); ++ this._disableBacklight(); ++ // Remove glossy background if the children still exists ++ if (this._source._iconContainer.get_children().length > 1) ++ this._source._iconContainer.get_children()[1].set_style(null); ++ this._restoreDefaultDot(); ++ } ++}); + -+ 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'); ++const RunningIndicatorDots = new Lang.Class({ + -+ 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; -+ } ++ Name: 'DashToDock.RunningIndicatorDots', ++ Extends: RunningIndicatorBase, + -+ // keep the label inside the screen border -+ // Only needed fot the x coordinate. ++ _init: function(source, settings) { + -+ // 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.parent(source, settings) + -+ 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 -*- */ ++ this._hideDefaultDot(); + -+/* -+ * Part of this file comes from gnome-shell-extensions: -+ * http://git.gnome.org/browse/gnome-shell-extensions/ -+ */ ++ this._area = new St.DrawingArea({x_expand: true, y_expand: true}); + -+const Gettext = imports.gettext; -+const Gio = imports.gi.Gio; ++ // We draw for the bottom case and rotate the canvas for other placements ++ //set center of rotatoins to the center ++ this._area.set_pivot_point(0.5, 0.5); ++ // prepare transformation matrix ++ let m = new Cogl.Matrix(); ++ m.init_identity(); + -+const Config = imports.misc.config; -+const ExtensionUtils = imports.misc.extensionUtils; ++ switch (this._side) { ++ case St.Side.TOP: ++ m.xx = -1; ++ m.rotate(180, 0, 0, 1); ++ break + -+/** -+ * 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(); ++ case St.Side.BOTTOM: ++ // nothing ++ break; + -+ domain = domain || extension.metadata['gettext-domain']; ++ case St.Side.LEFT: ++ m.yy = -1; ++ m.rotate(90, 0, 0, 1); ++ break; + -+ // 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); -+} ++ case St.Side.RIGHT: ++ m.rotate(-90, 0, 0, 1); ++ break ++ } + -+/** -+ * 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(); ++ this._area.set_transform(m); + -+ schema = schema || extension.metadata['settings-schema']; ++ this._area.connect('repaint', Lang.bind(this, this._updateIndicator)); ++ this._source._iconContainer.add_child(this._area); + -+ const GioSSS = Gio.SettingsSchemaSource; ++ let keys = ['custom-theme-running-dots-color', ++ 'custom-theme-running-dots-border-color', ++ 'custom-theme-running-dots-border-width', ++ 'custom-theme-customize-running-dots', ++ 'unity-backlit-items', ++ 'running-indicator-dominant-color']; + -+ // 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(); ++ keys.forEach(function(key) { ++ this._signalsHandler.add([ ++ this._settings, ++ 'changed::' + key, ++ Lang.bind(this, this.update) ++ ]); ++ }, this); + -+ 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.'); ++ // Apply glossy background ++ // TODO: move to enable/disableBacklit to apply itonly to the running apps? ++ // TODO: move to css class for theming support ++ let path = imports.misc.extensionUtils.getCurrentExtension().path; ++ this._glossyBackgroundStyle = 'background-image: url(\'' + path + '/media/glossy.svg\');' + ++ 'background-size: contain;'; + -+ 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; ++ update: function() { ++ this.parent(); + -+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; ++ // Enable / Disable the backlight of running apps ++ if (!this._settings.get_boolean('apply-custom-theme') && this._settings.get_boolean('unity-backlit-items')) { ++ this._source._iconContainer.get_children()[1].set_style(this._glossyBackgroundStyle); ++ if (this._isRunning) ++ this._enableBacklight(); ++ else ++ this._disableBacklight(); ++ } else { ++ this._disableBacklight(); ++ this._source._iconContainer.get_children()[1].set_style(null); ++ } + -+const Me = imports.misc.extensionUtils.getCurrentExtension(); -+const Utils = Me.imports.utils; -+const AppIcons = Me.imports.appIcons; ++ if (this._area) ++ this._area.queue_repaint(); ++ }, + -+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; ++ _computeStyle: function() { + -+/** -+ * 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; -+} ++ let [width, height] = this._area.get_surface_size(); ++ this._width = height; ++ this._height = width; + -+/** -+ * 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', ++ // By defaut re-use the style - background color, and border width and color - ++ // of the default dot ++ let themeNode = this._source._dot.get_theme_node(); ++ this._borderColor = themeNode.get_border_color(this._side); ++ this._borderWidth = themeNode.get_border_width(this._side); ++ this._bodyColor = themeNode.get_background_color(); + -+ _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); ++ if (!this._settings.get_boolean('apply-custom-theme')) { ++ // Adjust for the backlit case ++ if (this._settings.get_boolean('unity-backlit-items')) { ++ // Use dominant color for dots too if the backlit is enables ++ let colorPalette = this._dominantColorExtractor._getColorPalette(); + -+ this._position = Utils.getPosition(settings); -+ this._isHorizontal = ((this._position == St.Side.TOP) || -+ (this._position == St.Side.BOTTOM)); ++ // Slightly adjust the styling ++ this._borderWidth = 2; + -+ let layout = new Clutter.BoxLayout({ -+ orientation: this._isHorizontal ? Clutter.Orientation.HORIZONTAL : Clutter.Orientation.VERTICAL -+ }); ++ if (colorPalette !== null) { ++ this._borderColor = Clutter.color_from_string(colorPalette.lighter)[1] ; ++ this._bodyColor = Clutter.color_from_string(colorPalette.darker)[1]; ++ } else { ++ // Fallback ++ this._borderColor = Clutter.color_from_string('white')[1]; ++ this._bodyColor = Clutter.color_from_string('gray')[1]; ++ } ++ } + -+ 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)); ++ // Apply dominant color if requested ++ if (this._settings.get_boolean('running-indicator-dominant-color')) { ++ let colorPalette = this._dominantColorExtractor._getColorPalette(); ++ if (colorPalette !== null) { ++ this._bodyColor = Clutter.color_from_string(colorPalette.original)[1]; ++ } ++ } + -+ this.actor._delegate = this; -+ }, ++ // Finally, use customize style if requested ++ if (this._settings.get_boolean('custom-theme-customize-running-dots')) { ++ this._borderColor = Clutter.color_from_string(this._settings.get_string('custom-theme-running-dots-border-color'))[1]; ++ this._borderWidth = this._settings.get_int('custom-theme-running-dots-border-width'); ++ this._bodyColor = Clutter.color_from_string(this._settings.get_string('custom-theme-running-dots-color'))[1]; ++ } ++ } + -+ _allocate: function(actor, box, flags) { -+ let contentBox = box; -+ let availWidth = contentBox.x2 - contentBox.x1; -+ let availHeight = contentBox.y2 - contentBox.y1; ++ // Define the radius as an arbitrary size, but keep large enough to account ++ // for the drawing of the border. ++ this._radius = Math.max(this._width/22, this._borderWidth/2); ++ this._padding = 0; // distance from the margin ++ this._spacing = this._radius + this._borderWidth; // separation between the dots ++ }, + -+ let [appIcons, showAppsButton] = actor.get_children(); -+ let [showAppsMinHeight, showAppsNatHeight] = showAppsButton.get_preferred_height(availWidth); -+ let [showAppsMinWidth, showAppsNatWidth] = showAppsButton.get_preferred_width(availHeight); ++ _updateIndicator: function() { + -+ let offset_x = this._isHorizontal?showAppsNatWidth:0; -+ let offset_y = this._isHorizontal?0:showAppsNatHeight; ++ let area = this._area; ++ let cr = this._area.get_context(); + -+ 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); ++ this._computeStyle(); ++ this._drawIndicator(cr); ++ cr.$dispose(); ++ }, + -+ 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); ++ _drawIndicator: function(cr) { ++ // Draw the required numbers of dots ++ let n = this._nWindows; + -+ childBox.x2 = contentBox.x2; -+ childBox.y2 = contentBox.y2; -+ childBox.x1 = contentBox.x2 - showAppsNatWidth; -+ childBox.y1 = contentBox.y2 - showAppsNatHeight; -+ showAppsButton.allocate(childBox, flags); ++ cr.setLineWidth(this._borderWidth); ++ Clutter.cairo_set_source_color(cr, this._borderColor); ++ ++ // draw for the bottom case: ++ cr.translate((this._width - (2*n)*this._radius - (n-1)*this._spacing)/2, this._height - this._padding); ++ for (let i = 0; i < n; i++) { ++ cr.newSubPath(); ++ cr.arc((2*i+1)*this._radius + i*this._spacing, -this._radius - this._borderWidth/2, this._radius, 0, 2*Math.PI); + } ++ ++ cr.strokePreserve(); ++ Clutter.cairo_set_source_color(cr, this._bodyColor); ++ cr.fill(); + }, + -+ _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 ++ destroy: function() { ++ this.parent(); ++ this._area.destroy(); ++ } + -+ 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); ++// Adapted from dash-to-panel by Jason DeRose ++// https://github.com/jderose9/dash-to-panel ++const RunningIndicatorCiliora = new Lang.Class({ + -+ alloc.min_size = minWidth; -+ alloc.natural_size = natWidth; ++ Name: 'DashToDock.RunningIndicatorCiliora', ++ Extends: RunningIndicatorDots, + -+ }, ++ _drawIndicator: function(cr) { ++ if (this._isRunning) { + -+ _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 size = Math.max(this._width/20, this._borderWidth); ++ let spacing = size; // separation between the dots ++ let lineLength = this._width - (size*(this._nWindows-1)) - (spacing*(this._nWindows-1)); ++ let padding = this._borderWidth; ++ // For the backlit case here we don't want the outer border visible ++ if (this._settings.get_boolean('unity-backlit-items') && !this._settings.get_boolean('custom-theme-customize-running-dots')) ++ padding = 0; ++ let yOffset = this._height - padding - size; + -+ let [, natHeight] = this.actor.layout_manager.get_preferred_height(this.actor, forWidth); ++ cr.setLineWidth(this._borderWidth); ++ Clutter.cairo_set_source_color(cr, this._borderColor); + -+ let themeNode = this.actor.get_theme_node(); -+ let [, showAppsButton] = this.actor.get_children(); -+ let [minHeight, ] = showAppsButton.get_preferred_height(forWidth); ++ cr.translate(0, yOffset); ++ cr.newSubPath(); ++ cr.rectangle(0, 0, lineLength, size); ++ for (let i = 1; i < this._nWindows; i++) { ++ cr.newSubPath(); ++ cr.rectangle(lineLength + (i*spacing) + ((i-1)*size), 0, size, size); ++ } + -+ alloc.min_size = minHeight; -+ alloc.natural_size = natHeight; ++ cr.strokePreserve(); ++ Clutter.cairo_set_source_color(cr, this._bodyColor); ++ cr.fill(); ++ } + } +}); + -+const baseIconSizes = [16, 22, 24, 32, 48, 64, 96, 128]; ++// Adapted from dash-to-panel by Jason DeRose ++// https://github.com/jderose9/dash-to-panel ++const RunningIndicatorSegmented = new Lang.Class({ ++ ++ Name: 'DashToDock.RunningIndicatorSegmented', ++ Extends: RunningIndicatorDots, ++ ++ _drawIndicator: function(cr) { ++ if (this._isRunning) { ++ let size = Math.max(this._width/20, this._borderWidth); ++ let spacing = Math.ceil(this._width/18); // separation between the dots ++ let dashLength = Math.ceil((this._width - ((this._nWindows-1)*spacing))/this._nWindows); ++ let lineLength = this._width - (size*(this._nWindows-1)) - (spacing*(this._nWindows-1)); ++ let padding = this._borderWidth; ++ // For the backlit case here we don't want the outer border visible ++ if (this._settings.get_boolean('unity-backlit-items') && !this._settings.get_boolean('custom-theme-customize-running-dots')) ++ padding = 0; ++ let yOffset = this._height - padding - size; ++ ++ cr.setLineWidth(this._borderWidth); ++ Clutter.cairo_set_source_color(cr, this._borderColor); ++ ++ cr.translate(0, yOffset); ++ for (let i = 0; i < this._nWindows; i++) { ++ cr.newSubPath(); ++ cr.rectangle(i*dashLength + i*spacing, 0, dashLength, size); ++ } + -+/** -+ * 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', ++ cr.strokePreserve(); ++ Clutter.cairo_set_source_color(cr, this._bodyColor); ++ cr.fill() ++ } ++ } ++}); + -+ _init: function(settings, monitorIndex) { -+ this._maxHeight = -1; -+ this.iconSize = 64; -+ this._availableIconSizes = baseIconSizes; -+ this._shownInitially = false; ++// Adapted from dash-to-panel by Jason DeRose ++// https://github.com/jderose9/dash-to-panel ++const RunningIndicatorSolid = new Lang.Class({ + -+ 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(); ++ Name: 'DashToDock.RunningIndicatorSolid', ++ Extends: RunningIndicatorDots, + -+ this._dragPlaceholder = null; -+ this._dragPlaceholderPos = -1; -+ this._animatingPlaceholdersCount = 0; -+ this._showLabelTimeoutId = 0; -+ this._resetHoverTimeoutId = 0; -+ this._ensureAppIconVisibilityTimeoutId = 0; -+ this._labelShowing = false; ++ _drawIndicator: function(cr) { ++ if (this._isRunning) { + -+ 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 -+ }); ++ let size = Math.max(this._width/20, this._borderWidth); ++ let padding = this._borderWidth; ++ // For the backlit case here we don't want the outer border visible ++ if (this._settings.get_boolean('unity-backlit-items') && !this._settings.get_boolean('custom-theme-customize-running-dots')) ++ padding = 0; ++ let yOffset = this._height - padding - size; + -+ this._scrollView.connect('scroll-event', Lang.bind(this, this._onScrollEvent)); ++ cr.setLineWidth(this._borderWidth); ++ Clutter.cairo_set_source_color(cr, this._borderColor); + -+ 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); ++ cr.translate(0, yOffset); ++ cr.newSubPath(); ++ cr.rectangle(0, 0, this._width, size); + -+ 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); ++ cr.strokePreserve(); ++ Clutter.cairo_set_source_color(cr, this._bodyColor); ++ cr.fill(); + -+ let appsIcon = this._showAppsIcon; -+ appsIcon.connect('menu-state-changed', Lang.bind(this, function(appsIcon, opened) { -+ this._itemMenuStateChanged(appsIcon, opened); -+ })); ++ } ++ } ++}); + -+ this.showAppsButton = this._showAppsIcon.toggleButton; ++// Adapted from dash-to-panel by Jason DeRose ++// https://github.com/jderose9/dash-to-panel ++const RunningIndicatorSquares = new Lang.Class({ + -+ this._container.add_actor(this._showAppsIcon); ++ Name: 'DashToDock.RunningIndicatorSquares', ++ Extends: RunningIndicatorDots, + -+ 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 -+ }); ++ _drawIndicator: function(cr) { ++ if (this._isRunning) { ++ let size = Math.max(this._width/11, this._borderWidth); ++ let padding = this._borderWidth; ++ let spacing = Math.ceil(this._width/18); // separation between the dots ++ let yOffset = this._height - padding - size; + -+ 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; -+ })); ++ cr.setLineWidth(this._borderWidth); ++ Clutter.cairo_set_source_color(cr, this._borderColor); ++ ++ cr.translate(Math.floor((this._width - this._nWindows*size - (this._nWindows-1)*spacing)/2), yOffset); ++ for (let i = 0; i < this._nWindows; i++) { ++ cr.newSubPath(); ++ cr.rectangle(i*size + i*spacing, 0, size, size); ++ } ++ cr.strokePreserve(); ++ Clutter.cairo_set_source_color(cr, this._bodyColor); ++ cr.fill(); + } ++ } ++}); + -+ // 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)); ++// Adapted from dash-to-panel by Jason DeRose ++// https://github.com/jderose9/dash-to-panel ++const RunningIndicatorDashes = new Lang.Class({ + -+ this._workId = Main.initializeDeferredWork(this._box, Lang.bind(this, this._redisplay)); ++ Name: 'DashToDock.RunningIndicatorDashes', ++ Extends: RunningIndicatorDots, + -+ this._settings = new Gio.Settings({ -+ schema_id: 'org.gnome.shell' -+ }); ++ _drawIndicator: function(cr) { ++ if (this._isRunning) { ++ let size = Math.max(this._width/20, this._borderWidth); ++ let padding = this._borderWidth; ++ let spacing = Math.ceil(this._width/18); // separation between the dots ++ let dashLength = Math.floor(this._width/4) - spacing; ++ let yOffset = this._height - padding - size; + -+ this._appSystem = Shell.AppSystem.get_default(); ++ cr.setLineWidth(this._borderWidth); ++ Clutter.cairo_set_source_color(cr, this._borderColor); + -+ 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) -+ ]); -+ }, ++ cr.translate(Math.floor((this._width - this._nWindows*dashLength - (this._nWindows-1)*spacing)/2), yOffset); ++ for (let i = 0; i < this._nWindows; i++) { ++ cr.newSubPath(); ++ cr.rectangle(i*dashLength + i*spacing, 0, dashLength, size); ++ } + -+ destroy: function() { -+ this._signalsHandler.destroy(); -+ }, ++ cr.strokePreserve(); ++ Clutter.cairo_set_source_color(cr, this._bodyColor); ++ cr.fill(); ++ } ++ } ++}); + -+ _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; ++// Adapted from dash-to-panel by Jason DeRose ++// https://github.com/jderose9/dash-to-panel ++const RunningIndicatorMetro = new Lang.Class({ + -+ // 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(); ++ Name: 'DashToDock.RunningIndicatorMetro', ++ Extends: RunningIndicatorDots, + -+ // 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; ++ _init: function(source, settings) { ++ this.parent(source, settings); ++ this._source.actor.add_style_class_name('metro'); ++ }, ++ ++ _drawIndicator: function(cr) { ++ if (this._isRunning) { ++ let size = Math.max(this._width/20, this._borderWidth); ++ let padding = 0; ++ // For the backlit case here we don't want the outer border visible ++ if (this._settings.get_boolean('unity-backlit-items') && !this._settings.get_boolean('custom-theme-customize-running-dots')) ++ padding = 0; ++ let yOffset = this._height - padding - size; ++ ++ let n = this._nWindows; ++ if(n <= 1) { ++ cr.translate(0, yOffset); ++ Clutter.cairo_set_source_color(cr, this._bodyColor); ++ cr.newSubPath(); ++ cr.rectangle(0, 0, this._width, size); ++ cr.fill(); ++ } else { ++ let blackenedLength = (1/48)*this._width; // need to scale with the SVG for the stacked highlight ++ let darkenedLength = this._isFocused ? (2/48)*this._width : (10/48)*this._width; ++ let blackenedColor = this._bodyColor.shade(.3); ++ let darkenedColor = this._bodyColor.shade(.7); + -+ // reset timeout to avid conflicts with the mousehover event -+ if (this._ensureAppIconVisibilityTimeoutId > 0) { -+ Mainloop.source_remove(this._ensureAppIconVisibilityTimeoutId); -+ this._ensureAppIconVisibilityTimeoutId = 0; -+ } ++ cr.translate(0, yOffset); + -+ // Skip to avoid double events mouse -+ if (event.is_pointer_emulated()) -+ return Clutter.EVENT_STOP; ++ Clutter.cairo_set_source_color(cr, this._bodyColor); ++ cr.newSubPath(); ++ cr.rectangle(0, 0, this._width - darkenedLength - blackenedLength, size); ++ cr.fill(); ++ Clutter.cairo_set_source_color(cr, blackenedColor); ++ cr.newSubPath(); ++ cr.rectangle(this._width - darkenedLength - blackenedLength, 0, 1, size); ++ cr.fill(); ++ Clutter.cairo_set_source_color(cr, darkenedColor); ++ cr.newSubPath(); ++ cr.rectangle(this._width - darkenedLength, 0, darkenedLength, size); ++ cr.fill(); ++ } ++ } ++ }, + -+ let adjustment, delta; ++ destroy: function() { ++ this.parent(); ++ this._source.actor.remove_style_class_name('metro'); ++ } ++}); + -+ if (this._isHorizontal) -+ adjustment = this._scrollView.get_hscroll_bar().get_adjustment(); -+ else -+ adjustment = this._scrollView.get_vscroll_bar().get_adjustment(); ++/* ++ * Unity like notification and progress indicators ++ */ ++const UnityIndicator = new Lang.Class({ ++ Name: 'DashToDock.UnityIndicator', ++ Extends: IndicatorBase, + -+ let increment = adjustment.step_increment; ++ _init: function(source, settings) { + -+ 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; -+ } ++ this.parent(source, settings); + -+ adjustment.set_value(adjustment.get_value() + delta); ++ this._notificationBadgeLabel = new St.Label(); ++ this._notificationBadgeBin = new St.Bin({ ++ child: this._notificationBadgeLabel, ++ x_align: St.Align.END, y_align: St.Align.START, ++ x_expand: true, y_expand: true ++ }); ++ this._notificationBadgeLabel.add_style_class_name('notification-badge'); ++ this._notificationBadgeCount = 0; ++ this._notificationBadgeBin.hide(); + -+ return Clutter.EVENT_STOP; -+ }, ++ this._source._iconContainer.add_child(this._notificationBadgeBin); ++ this._source._iconContainer.connect('allocation-changed', Lang.bind(this, this.updateNotificationBadge)); + -+ _onDragBegin: function() { -+ this._dragCancelled = false; -+ this._dragMonitor = { -+ dragMotion: Lang.bind(this, this._onDragMotion) -+ }; -+ DND.addDragMonitor(this._dragMonitor); ++ this._remoteEntries = []; ++ this._source.remoteModel.lookupById(this._source.app.id).forEach( ++ Lang.bind(this, function(entry) { ++ this.insertEntryRemote(entry); ++ }) ++ ); + -+ 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); -+ } ++ this._signalsHandler.add([ ++ this._source.remoteModel, ++ 'entry-added', ++ Lang.bind(this, this._onLauncherEntryRemoteAdded) ++ ], [ ++ this._source.remoteModel, ++ 'entry-removed', ++ Lang.bind(this, this._onLauncherEntryRemoteRemoved) ++ ]) + }, + -+ _onDragCancelled: function() { -+ this._dragCancelled = true; -+ this._endDrag(); ++ _onLauncherEntryRemoteAdded: function(remoteModel, entry) { ++ if (!entry || !entry.appId()) ++ return; ++ if (this._source && this._source.app && this._source.app.id == entry.appId()) { ++ this.insertEntryRemote(entry); ++ } + }, + -+ _onDragEnd: function() { -+ if (this._dragCancelled) ++ _onLauncherEntryRemoteRemoved: function(remoteModel, entry) { ++ if (!entry || !entry.appId()) + return; + -+ this._endDrag(); ++ if (this._source && this._source.app && this._source.app.id == entry.appId()) { ++ this.removeEntryRemote(entry); ++ } + }, + -+ _endDrag: function() { -+ this._clearDragPlaceholder(); -+ this._clearEmptyDropTarget(); -+ this._showAppsIcon.setDragApp(null); -+ DND.removeDragMonitor(this._dragMonitor); -+ }, ++ updateNotificationBadge: function() { ++ let scaleFactor = St.ThemeContext.get_for_stage(global.stage).scale_factor; ++ let [minWidth, natWidth] = this._source._iconContainer.get_preferred_width(-1); ++ let logicalNatWidth = natWidth / scaleFactor; ++ let font_size = Math.max(10, Math.round(logicalNatWidth / 5)); ++ let margin_left = Math.round(logicalNatWidth / 4); + -+ _onDragMotion: function(dragEvent) { -+ let app = Dash.getAppFromSource(dragEvent.source); -+ if (app == null) -+ return DND.DragMotionResult.CONTINUE; ++ this._notificationBadgeLabel.set_style( ++ 'font-size: ' + font_size + 'px;' + ++ 'margin-left: ' + margin_left + 'px;' ++ ); + -+ let showAppsHovered = this._showAppsIcon.contains(dragEvent.targetActor); ++ this._notificationBadgeBin.width = Math.round(logicalNatWidth - margin_left); ++ this._notificationBadgeLabel.clutter_text.ellipsize = Pango.EllipsizeMode.MIDDLE; ++ }, ++ ++ _notificationBadgeCountToText: function(count) { ++ if (count <= 9999) { ++ return count.toString(); ++ } else if (count < 1e5) { ++ let thousands = count / 1e3; ++ return thousands.toFixed(1).toString() + "k"; ++ } else if (count < 1e6) { ++ let thousands = count / 1e3; ++ return thousands.toFixed(0).toString() + "k"; ++ } else if (count < 1e8) { ++ let millions = count / 1e6; ++ return millions.toFixed(1).toString() + "M"; ++ } else if (count < 1e9) { ++ let millions = count / 1e6; ++ return millions.toFixed(0).toString() + "M"; ++ } else { ++ let billions = count / 1e9; ++ return billions.toFixed(1).toString() + "B"; ++ } ++ }, + -+ if (!this._box.contains(dragEvent.targetActor) || showAppsHovered) -+ this._clearDragPlaceholder(); ++ setNotificationBadge: function(count) { ++ this._notificationBadgeCount = count; ++ let text = this._notificationBadgeCountToText(count); ++ this._notificationBadgeLabel.set_text(text); ++ }, + -+ if (showAppsHovered) -+ this._showAppsIcon.setDragApp(app); ++ toggleNotificationBadge: function(activate) { ++ if (activate && this._notificationBadgeCount > 0) { ++ this.updateNotificationBadge(); ++ this._notificationBadgeBin.show(); ++ } + else -+ this._showAppsIcon.setDragApp(null); ++ this._notificationBadgeBin.hide(); ++ }, + -+ return DND.DragMotionResult.CONTINUE; ++ _showProgressOverlay: function() { ++ if (this._progressOverlayArea) { ++ this._updateProgressOverlay(); ++ return; ++ } ++ ++ this._progressOverlayArea = new St.DrawingArea({x_expand: true, y_expand: true}); ++ this._progressOverlayArea.connect('repaint', Lang.bind(this, function() { ++ this._drawProgressOverlay(this._progressOverlayArea); ++ })); ++ ++ this._source._iconContainer.add_child(this._progressOverlayArea); ++ this._updateProgressOverlay(); + }, + -+ _appIdListToHash: function(apps) { -+ let ids = {}; -+ for (let i = 0; i < apps.length; i++) -+ ids[apps[i].get_id()] = apps[i]; -+ return ids; ++ _hideProgressOverlay: function() { ++ if (this._progressOverlayArea) ++ this._progressOverlayArea.destroy(); ++ this._progressOverlayArea = null; + }, + -+ _queueRedisplay: function() { -+ Main.queueDeferredWork(this._workId); ++ _updateProgressOverlay: function() { ++ if (this._progressOverlayArea) ++ this._progressOverlayArea.queue_repaint(); + }, + -+ _hookUpLabel: function(item, appIcon) { -+ item.child.connect('notify::hover', Lang.bind(this, function() { -+ this._syncLabel(item, appIcon); -+ })); ++ _drawProgressOverlay: function(area) { ++ let scaleFactor = St.ThemeContext.get_for_stage(global.stage).scale_factor; ++ let [surfaceWidth, surfaceHeight] = area.get_surface_size(); ++ let cr = area.get_context(); + -+ let id = Main.overview.connect('hiding', Lang.bind(this, function() { -+ this._labelShowing = false; -+ item.hideLabel(); -+ })); -+ item.child.connect('destroy', function() { -+ Main.overview.disconnect(id); -+ }); ++ let iconSize = this._source.icon.iconSize * scaleFactor; ++ ++ let x = Math.floor((surfaceWidth - iconSize) / 2); ++ let y = Math.floor((surfaceHeight - iconSize) / 2); ++ ++ let lineWidth = Math.floor(1.0 * scaleFactor); ++ let padding = Math.floor(iconSize * 0.05); ++ let width = iconSize - 2.0*padding; ++ let height = Math.floor(Math.min(18.0*scaleFactor, 0.20*iconSize)); ++ x += padding; ++ y += iconSize - height - padding; ++ ++ cr.setLineWidth(lineWidth); ++ ++ // Draw the outer stroke ++ let stroke = new Cairo.LinearGradient(0, y, 0, y + height); ++ let fill = null; ++ stroke.addColorStopRGBA(0.5, 0.5, 0.5, 0.5, 0.1); ++ stroke.addColorStopRGBA(0.9, 0.8, 0.8, 0.8, 0.4); ++ Utils.drawRoundedLine(cr, x + lineWidth/2.0, y + lineWidth/2.0, width, height, true, true, stroke, fill); ++ ++ // Draw the background ++ x += lineWidth; ++ y += lineWidth; ++ width -= 2.0*lineWidth; ++ height -= 2.0*lineWidth; ++ ++ stroke = Cairo.SolidPattern.createRGBA(0.20, 0.20, 0.20, 0.9); ++ fill = new Cairo.LinearGradient(0, y, 0, y + height); ++ fill.addColorStopRGBA(0.4, 0.25, 0.25, 0.25, 1.0); ++ fill.addColorStopRGBA(0.9, 0.35, 0.35, 0.35, 1.0); ++ Utils.drawRoundedLine(cr, x + lineWidth/2.0, y + lineWidth/2.0, width, height, true, true, stroke, fill); ++ ++ // Draw the finished bar ++ x += lineWidth; ++ y += lineWidth; ++ width -= 2.0*lineWidth; ++ height -= 2.0*lineWidth; ++ ++ let finishedWidth = Math.ceil(this._progress * width); ++ stroke = Cairo.SolidPattern.createRGBA(0.8, 0.8, 0.8, 1.0); ++ fill = Cairo.SolidPattern.createRGBA(0.9, 0.9, 0.9, 1.0); ++ ++ if (Clutter.get_default_text_direction() == Clutter.TextDirection.RTL) ++ Utils.drawRoundedLine(cr, x + lineWidth/2.0 + width - finishedWidth, y + lineWidth/2.0, finishedWidth, height, true, true, stroke, fill); ++ else ++ Utils.drawRoundedLine(cr, x + lineWidth/2.0, y + lineWidth/2.0, finishedWidth, height, true, true, stroke, fill); + -+ if (appIcon) { -+ appIcon.connect('sync-tooltip', Lang.bind(this, function() { -+ this._syncLabel(item, appIcon); -+ })); -+ } ++ cr.$dispose(); + }, + -+ _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; -+ })); ++ setProgress: function(progress) { ++ this._progress = Math.min(Math.max(progress, 0.0), 1.0); ++ this._updateProgressOverlay(); ++ }, ++ ++ toggleProgressOverlay: function(activate) { ++ if (activate) { ++ this._showProgressOverlay(); ++ } ++ else { ++ this._hideProgressOverlay(); + } ++ }, + -+ appIcon.connect('menu-state-changed', Lang.bind(this, function(appIcon, opened) { -+ this._itemMenuStateChanged(item, opened); -+ })); ++ insertEntryRemote: function(remote) { ++ if (!remote || this._remoteEntries.indexOf(remote) !== -1) ++ return; + -+ let item = new Dash.DashItemContainer(); ++ this._remoteEntries.push(remote); ++ this._selectEntryRemote(remote); ++ }, + -+ extendDashItemContainer(item, this._dtdSettings); -+ item.setChild(appIcon.actor); ++ removeEntryRemote: function(remote) { ++ if (!remote || this._remoteEntries.indexOf(remote) == -1) ++ return; + -+ 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; -+ } -+ } -+ })); ++ this._remoteEntries.splice(this._remoteEntries.indexOf(remote), 1); + -+ appIcon.actor.connect('clicked', Lang.bind(this, function(actor) { -+ ensureActorVisibleInScrollView(this._scrollView, actor); -+ })); ++ if (this._remoteEntries.length > 0) { ++ this._selectEntryRemote(this._remoteEntries[this._remoteEntries.length-1]); ++ } else { ++ this.setNotificationBadge(0); ++ this.toggleNotificationBadge(false); ++ this.setProgress(0); ++ this.toggleProgressOverlay(false); ++ } ++ }, + -+ appIcon.actor.connect('key-focus-in', Lang.bind(this, function(actor) { -+ let [x_shift, y_shift] = ensureActorVisibleInScrollView(this._scrollView, actor); ++ _selectEntryRemote: function(remote) { ++ if (!remote) ++ return; + -+ // 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; -+ } -+ })); ++ this._signalsHandler.removeWithLabel('entry-remotes'); + -+ // Override default AppIcon label_actor, now the -+ // accessible_name is set at DashItemContainer.setLabelText -+ appIcon.actor.label_actor = null; -+ item.setLabelText(app.get_name()); ++ this._signalsHandler.addWithLabel('entry-remotes', ++ [ ++ remote, ++ 'count-changed', ++ Lang.bind(this, (remote, value) => { ++ this.setNotificationBadge(value); ++ }) ++ ], [ ++ remote, ++ 'count-visible-changed', ++ Lang.bind(this, (remote, value) => { ++ this.toggleNotificationBadge(value); ++ }) ++ ], [ ++ remote, ++ 'progress-changed', ++ Lang.bind(this, (remote, value) => { ++ this.setProgress(value); ++ }) ++ ], [ ++ remote, ++ 'progress-visible-changed', ++ Lang.bind(this, (remote, value) => { ++ this.toggleProgressOverlay(value); ++ }) ++ ]); + -+ appIcon.icon.setIconSize(this.iconSize); -+ this._hookUpLabel(item, appIcon); ++ this.setNotificationBadge(remote.count()); ++ this.toggleNotificationBadge(remote.countVisible()); ++ this.setProgress(remote.progress()); ++ this.toggleProgressOverlay(remote.progressVisible()); ++ } ++}); + -+ 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; -+ }); ++// We need an icons theme object, this is the only way I managed to get ++// pixel buffers that can be used for calculating the backlight color ++let themeLoader = null; + -+ let appIcons = iconChildren.map(function(actor) { -+ return actor.child._delegate; -+ }); ++// Global icon cache. Used for Unity7 styling. ++let iconCacheMap = new Map(); ++// Max number of items to store ++// We don't expect to ever reach this number, but let's put an hard limit to avoid ++// even the remote possibility of the cached items to grow indefinitely. ++const MAX_CACHED_ITEMS = 1000; ++// When the size exceed it, the oldest 'n' ones are deleted ++const BATCH_SIZE_TO_DELETE = 50; ++// The icon size used to extract the dominant color ++const DOMINANT_COLOR_ICON_SIZE = 64; + -+ return appIcons; -+ }, ++// Compute dominant color frim the app icon. ++// The color is cached for efficiency. ++const DominantColorExtractor = new Lang.Class({ ++ Name: 'DashToDock.DominantColorExtractor', + -+ _updateAppsIconGeometry: function() { -+ let appIcons = this._getAppIcons(); -+ appIcons.forEach(function(icon) { -+ icon.updateIconGeometry(); -+ }); ++ _init: function(app) { ++ this._app = app; + }, + -+ _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'); -+ } -+ }, ++ /** ++ * Try to get the pixel buffer for the current icon, if not fail gracefully ++ */ ++ _getIconPixBuf: function() { ++ let iconTexture = this._app.create_icon_texture(16); + -+ _syncLabel: function(item, appIcon) { -+ let shouldShow = appIcon ? appIcon.shouldShowTooltip() : item.child.get_hover(); ++ if (themeLoader === null) { ++ let ifaceSettings = new Gio.Settings({ schema: "org.gnome.desktop.interface" }); + -+ 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'); -+ } ++ themeLoader = new Gtk.IconTheme(), ++ themeLoader.set_custom_theme(ifaceSettings.get_string('icon-theme')); // Make sure the correct theme is loaded + } -+ }, + -+ _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; -+ }); ++ // Unable to load the icon texture, use fallback ++ if (iconTexture instanceof St.Icon === false) { ++ return null; ++ } + -+ iconChildren.push(this._showAppsIcon); ++ iconTexture = iconTexture.get_gicon(); + -+ if (this._maxHeight == -1) -+ return; ++ // Unable to load the icon texture, use fallback ++ if (iconTexture === null) { ++ return null; ++ } + -+ // Check if the container is present in the stage. This avoids critical -+ // errors when unlocking the screen -+ if (!this._container.get_stage()) -+ return; ++ if (iconTexture instanceof Gio.FileIcon) { ++ // Use GdkPixBuf to load the pixel buffer from the provided file path ++ return GdkPixbuf.Pixbuf.new_from_file(iconTexture.get_file().get_path()); ++ } + -+ 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; ++ // Get the pixel buffer from the icon theme ++ let icon_info = themeLoader.lookup_icon(iconTexture.get_names()[0], DOMINANT_COLOR_ICON_SIZE, 0); ++ if (icon_info !== null) ++ return icon_info.load_icon(); + else -+ availHeight = maxContent.y2 - maxContent.y1; -+ let spacing = themeNode.get_length('spacing'); ++ return null; ++ }, ++ ++ /** ++ * The backlight color choosing algorithm was mostly ported to javascript from the ++ * Unity7 C++ source of Canonicals: ++ * https://bazaar.launchpad.net/~unity-team/unity/trunk/view/head:/launcher/LauncherIcon.cpp ++ * so it more or less works the same way. ++ */ ++ _getColorPalette: function() { ++ if (iconCacheMap.get(this._app.get_id())) { ++ // We already know the answer ++ return iconCacheMap.get(this._app.get_id()); ++ } + -+ let firstButton = iconChildren[0].child; -+ let firstIcon = firstButton._delegate.icon; ++ let pixBuf = this._getIconPixBuf(); ++ if (pixBuf == null) ++ return null; + -+ let minHeight, natHeight, minWidth, natWidth; ++ let pixels = pixBuf.get_pixels(), ++ offset = 0; + -+ // 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 total = 0, ++ rTotal = 0, ++ gTotal = 0, ++ bTotal = 0; + -+ let scaleFactor = St.ThemeContext.get_for_stage(global.stage).scale_factor; -+ let iconSizes = this._availableIconSizes.map(function(s) { -+ return s * scaleFactor; -+ }); ++ let resample_y = 1, ++ resample_x = 1; + -+ // 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; ++ // Resampling of large icons ++ // We resample icons larger than twice the desired size, as the resampling ++ // to a size s ++ // DOMINANT_COLOR_ICON_SIZE < s < 2*DOMINANT_COLOR_ICON_SIZE, ++ // most of the case exactly DOMINANT_COLOR_ICON_SIZE as the icon size is tipycally ++ // a multiple of it. ++ let width = pixBuf.get_width(); ++ let height = pixBuf.get_height(); + -+ let availSize = availHeight / iconChildren.length; ++ // Resample ++ if (height >= 2* DOMINANT_COLOR_ICON_SIZE) ++ resample_y = Math.floor(height/DOMINANT_COLOR_ICON_SIZE); + ++ if (width >= 2* DOMINANT_COLOR_ICON_SIZE) ++ resample_x = Math.floor(width/DOMINANT_COLOR_ICON_SIZE); + -+ let newIconSize = this._availableIconSizes[0]; -+ for (let i = 0; i < iconSizes.length; i++) { -+ if (iconSizes[i] < availSize) -+ newIconSize = this._availableIconSizes[i]; -+ } ++ if (resample_x !==1 || resample_y !== 1) ++ pixels = this._resamplePixels(pixels, resample_x, resample_y); + -+ if (newIconSize == this.iconSize) -+ return; ++ // computing the limit outside the for (where it would be repeated at each iteration) ++ // for performance reasons ++ let limit = pixels.length; ++ for (let offset = 0; offset < limit; offset+=4) { ++ let r = pixels[offset], ++ g = pixels[offset + 1], ++ b = pixels[offset + 2], ++ a = pixels[offset + 3]; + -+ let oldIconSize = this.iconSize; -+ this.iconSize = newIconSize; -+ this.emit('icon-size-changed'); ++ let saturation = (Math.max(r,g, b) - Math.min(r,g, b)); ++ let relevance = 0.1 * 255 * 255 + 0.9 * a * saturation; + -+ let scale = oldIconSize / newIconSize; -+ for (let i = 0; i < iconChildren.length; i++) { -+ let icon = iconChildren[i].child._delegate.icon; ++ rTotal += r * relevance; ++ gTotal += g * relevance; ++ bTotal += b * relevance; + -+ // Set the new size immediately, to keep the icons' sizes -+ // in sync with this.iconSize -+ icon.setIconSize(this.iconSize); ++ total += relevance; ++ } + -+ // 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; ++ total = total * 255; + -+ let [targetWidth, targetHeight] = icon.icon.get_size(); ++ let r = rTotal / total, ++ g = gTotal / total, ++ b = bTotal / total; + -+ // 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); ++ let hsv = Utils.ColorUtils.RGBtoHSV(r * 255, g * 255, b * 255); + -+ Tweener.addTween(icon.icon, -+ { width: targetWidth, -+ height: targetHeight, -+ time: DASH_ANIMATION_TIME, -+ transition: 'easeOutQuad', -+ }); ++ if (hsv.s > 0.15) ++ hsv.s = 0.65; ++ hsv.v = 0.90; ++ ++ let rgb = Utils.ColorUtils.HSVtoRGB(hsv.h, hsv.s, hsv.v); ++ ++ // Cache the result. ++ let backgroundColor = { ++ lighter: Utils.ColorUtils.ColorLuminance(rgb.r, rgb.g, rgb.b, 0.2), ++ original: Utils.ColorUtils.ColorLuminance(rgb.r, rgb.g, rgb.b, 0), ++ darker: Utils.ColorUtils.ColorLuminance(rgb.r, rgb.g, rgb.b, -0.5) ++ }; ++ ++ if (iconCacheMap.size >= MAX_CACHED_ITEMS) { ++ //delete oldest cached values (which are in order of insertions) ++ let ctr=0; ++ for (let key of iconCacheMap.keys()) { ++ if (++ctr > BATCH_SIZE_TO_DELETE) ++ break; ++ iconCacheMap.delete(key); ++ } + } ++ ++ iconCacheMap.set(this._app.get_id(), backgroundColor); ++ ++ return backgroundColor; + }, + -+ _redisplay: function() { -+ let favorites = AppFavorites.getAppFavorites().getFavoriteMap(); ++ /** ++ * Downsample large icons before scanning for the backlight color to ++ * improve performance. ++ * ++ * @param pixBuf ++ * @param pixels ++ * @param resampleX ++ * @param resampleY ++ * ++ * @return []; ++ */ ++ _resamplePixels: function (pixels, resampleX, resampleY) { ++ let resampledPixels = []; ++ // computing the limit outside the for (where it would be repeated at each iteration) ++ // for performance reasons ++ let limit = pixels.length / (resampleX * resampleY) / 4; ++ for (let i = 0; i < limit; i++) { ++ let pixel = i * resampleX * resampleY; + -+ 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; -+ }); ++ resampledPixels.push(pixels[pixel * 4]); ++ resampledPixels.push(pixels[pixel * 4 + 1]); ++ resampledPixels.push(pixels[pixel * 4 + 2]); ++ resampledPixels.push(pixels[pixel * 4 + 3]); + } + -+ 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 = []; ++ return resampledPixels; ++ }, ++}) +diff --git a/extensions/dash-to-dock/appIcons.js b/extensions/dash-to-dock/appIcons.js +new file mode 100644 +index 0000000..ef9c6e1 +--- /dev/null ++++ b/extensions/dash-to-dock/appIcons.js +@@ -0,0 +1,1171 @@ ++// -*- mode: js; js-indent-level: 4; indent-tabs-mode: nil -*- + -+ if (this._dtdSettings.get_boolean('show-favorites')) { -+ for (let id in favorites) -+ newApps.push(favorites[id]); ++const Clutter = imports.gi.Clutter; ++const GdkPixbuf = imports.gi.GdkPixbuf ++const Gio = imports.gi.Gio; ++const GLib = imports.gi.GLib; ++const Gtk = imports.gi.Gtk; ++const Signals = imports.signals; ++const Lang = imports.lang; ++const Meta = imports.gi.Meta; ++const Shell = imports.gi.Shell; ++const St = imports.gi.St; ++const Mainloop = imports.mainloop; ++ ++// Use __ () and N__() for the extension gettext domain, and reuse ++// the shell domain with the default _() and N_() ++const Gettext = imports.gettext.domain('dashtodock'); ++const __ = Gettext.gettext; ++const N__ = function(e) { return e }; ++ ++const AppDisplay = imports.ui.appDisplay; ++const AppFavorites = imports.ui.appFavorites; ++const Dash = imports.ui.dash; ++const DND = imports.ui.dnd; ++const IconGrid = imports.ui.iconGrid; ++const Main = imports.ui.main; ++const PopupMenu = imports.ui.popupMenu; ++const Tweener = imports.ui.tweener; ++const Util = imports.misc.util; ++const Workspace = imports.ui.workspace; ++ ++const Me = imports.misc.extensionUtils.getCurrentExtension(); ++const Utils = Me.imports.utils; ++const WindowPreview = Me.imports.windowPreview; ++const AppIconIndicators = Me.imports.appIconIndicators; ++ ++let tracker = Shell.WindowTracker.get_default(); ++ ++let DASH_ITEM_LABEL_SHOW_TIME = Dash.DASH_ITEM_LABEL_SHOW_TIME; ++ ++const clickAction = { ++ SKIP: 0, ++ MINIMIZE: 1, ++ LAUNCH: 2, ++ CYCLE_WINDOWS: 3, ++ MINIMIZE_OR_OVERVIEW: 4, ++ PREVIEWS: 5, ++ MINIMIZE_OR_PREVIEWS: 6, ++ QUIT: 7 ++}; ++ ++const scrollAction = { ++ DO_NOTHING: 0, ++ CYCLE_WINDOWS: 1, ++ SWITCH_WORKSPACE: 2 ++}; ++ ++let recentlyClickedAppLoopId = 0; ++let recentlyClickedApp = null; ++let recentlyClickedAppWindows = null; ++let recentlyClickedAppIndex = 0; ++let recentlyClickedAppMonitor = -1; ++ ++/** ++ * Extend AppIcon ++ * ++ * - Pass settings to the constructor and bind settings changes ++ * - Apply a css class based on the number of windows of each application (#N); ++ * - Customized indicators for running applications in place of the default "dot" style which is hidden (#N); ++ * a class of the form "running#N" is applied to the AppWellIcon actor. ++ * like the original .running one. ++ * - Add a .focused style to the focused app ++ * - Customize click actions. ++ * - Update minimization animation target ++ * - Update menu if open on windows change ++ */ ++var MyAppIcon = new Lang.Class({ ++ Name: 'DashToDock.AppIcon', ++ Extends: AppDisplay.AppIcon, ++ ++ // settings are required inside. ++ _init: function(settings, remoteModel, app, monitorIndex, iconParams) { ++ // a prefix is required to avoid conflicting with the parent class variable ++ this._dtdSettings = settings; ++ this.monitorIndex = monitorIndex; ++ this._signalsHandler = new Utils.GlobalSignalsHandler(); ++ this.remoteModel = remoteModel; ++ this._indicator = null; ++ ++ this.parent(app, iconParams); ++ ++ this._updateIndicatorStyle(); ++ ++ // Monitor windows-changes instead of app state. ++ // Keep using the same Id and function callback (that is extended) ++ if (this._stateChangedId > 0) { ++ this.app.disconnect(this._stateChangedId); ++ this._stateChangedId = 0; + } + -+ // 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); -+ } ++ this._windowsChangedId = this.app.connect('windows-changed', ++ Lang.bind(this, ++ this.onWindowsChanged)); ++ this._focusAppChangeId = tracker.connect('notify::focus-app', ++ Lang.bind(this, ++ this._onFocusAppChanged)); ++ ++ // In Wayland sessions, this signal is needed to track the state of windows dragged ++ // from one monitor to another. As this is triggered quite often (whenever a new winow ++ // of any application opened or moved to a different desktop), ++ // we restrict this signal to the case when 'isolate-monitors' is true, ++ // and if there are at least 2 monitors. ++ if (this._dtdSettings.get_boolean('isolate-monitors') && ++ Main.layoutManager.monitors.length > 1) { ++ this._signalsHandler.removeWithLabel('isolate-monitors'); ++ this._signalsHandler.addWithLabel('isolate-monitors', [ ++ global.screen, ++ 'window-entered-monitor', ++ Lang.bind(this, this._onWindowEntered) ++ ]); + } + -+ // 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. ++ this._progressOverlayArea = null; ++ this._progress = 0; + -+ let addedItems = []; -+ let removedActors = []; ++ let keys = ['apply-custom-theme', ++ 'running-indicator-style', ++ ]; + -+ 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; -+ } ++ keys.forEach(function(key) { ++ this._signalsHandler.add([ ++ this._dtdSettings, ++ 'changed::' + key, ++ Lang.bind(this, this._updateIndicatorStyle) ++ ]); ++ }, this); ++ ++ this._dtdSettings.connect('changed::scroll-action', Lang.bind(this, function() { ++ this._optionalScrollCycleWindows(); ++ })); ++ this._optionalScrollCycleWindows(); ++ ++ this._numberOverlay(); ++ ++ this._previewMenuManager = null; ++ this._previewMenu = null; ++ }, ++ ++ _onDestroy: function() { ++ this.parent(); + -+ // App removed at oldIndex -+ if (oldApps[oldIndex] && (newApps.indexOf(oldApps[oldIndex]) == -1)) { -+ removedActors.push(children[oldIndex]); -+ oldIndex++; -+ continue; -+ } ++ // 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); + -+ // 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; -+ } ++ // Disconect global signals + -+ // 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 (this._windowsChangedId > 0) ++ this.app.disconnect(this._windowsChangedId); ++ this._windowsChangedId = 0; + -+ 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++; -+ } ++ if (this._focusAppChangeId > 0) { ++ tracker.disconnect(this._focusAppChangeId); ++ this._focusAppChangeId = 0; + } + -+ for (let i = 0; i < addedItems.length; i++) -+ this._box.insert_child_at_index(addedItems[i].item, -+ addedItems[i].pos); ++ this._signalsHandler.destroy(); + -+ for (let i = 0; i < removedActors.length; i++) { -+ let item = removedActors[i]; ++ if (this._scrollEventHandler) ++ this.actor.disconnect(this._scrollEventHandler); ++ }, + -+ // Don't animate item removal when the overview is transitioning -+ if (!Main.overview.animationInProgress) -+ item.animateOutAndDestroy(); -+ else -+ item.destroy(); ++ // TOOD Rename this function ++ _updateIndicatorStyle: function() { ++ ++ if (this._indicator !== null) { ++ this._indicator.destroy(); ++ this._indicator = null; + } ++ this._indicator = new AppIconIndicators.AppIconIndicator(this, this._dtdSettings); ++ this._indicator.update(); ++ }, + -+ this._adjustIconSize(); ++ _onWindowEntered: function(metaScreen, monitorIndex, metaWin) { ++ let app = Shell.WindowTracker.get_default().get_window_app(metaWin); ++ if (app && app.get_id() == this.app.get_id()) ++ this.onWindowsChanged(); ++ }, + -+ 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]); ++ _optionalScrollCycleWindows: function() { ++ if (this._scrollEventHandler) { ++ this.actor.disconnect(this._scrollEventHandler); ++ this._scrollEventHandler = 0; ++ } + -+ // Skip animations on first run when adding the initial set -+ // of items, to avoid all items zooming in at once ++ 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)); ++ }, + -+ let animate = this._shownInitially && -+ !Main.overview.animationInProgress; ++ onScrollEvent: function(actor, event) { + -+ if (!this._shownInitially) -+ this._shownInitially = true; ++ // 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; + -+ for (let i = 0; i < addedItems.length; i++) -+ addedItems[i].item.show(animate); ++ if (!appIsRunning) ++ return false + -+ // 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 (this._optionalScrollCycleWindowsDeadTimeId > 0) ++ return false; ++ else ++ this._optionalScrollCycleWindowsDeadTimeId = Mainloop.timeout_add(250, Lang.bind(this, function() { ++ this._optionalScrollCycleWindowsDeadTimeId = 0; ++ })); + -+ // This is required for icon reordering when the scrollview is used. -+ this._updateAppsIconGeometry(); ++ let direction = null; + -+ // This will update the size, and the corresponding number for each icon -+ this._updateNumberOverlay(); -+ }, ++ 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; ++ } + -+ _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++; -+ } ++ let focusedApp = tracker.focus_app; ++ if (!Main.overview._shown) { ++ let reversed = direction === Meta.MotionDirection.UP; ++ if (this.app == focusedApp) ++ this._cycleThroughWindows(reversed); + else { -+ // No overlay after 10 -+ icon.setNumberOverlay(-1); ++ // Activate the first window ++ let windows = this.getInterestingWindows(); ++ if (windows.length > 0) { ++ let w = windows[0]; ++ Main.activateWindow(w); ++ } + } -+ icon.updateNumberOverlay(); -+ }); -+ -+ }, -+ -+ toggleNumberOverlay: function(activate) { -+ let appIcons = this._getAppIcons(); -+ appIcons.forEach(function(icon) { -+ icon.toggleNumberOverlay(activate); -+ }); ++ } ++ else ++ this.app.activate(); ++ return true; + }, + -+ 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; ++ // 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 -+ 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 if (button && button == 1) { ++ if (modifiers & Clutter.ModifierType.SHIFT_MASK) ++ buttonAction = this._dtdSettings.get_enum('shift-click-action'); + 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]); ++ buttonAction = this._dtdSettings.get_enum('click-action'); + } + -+ // 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; ++ // 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; + -+ return DND.DragMotionResult.COPY_DROP; -+ }, ++ // Some action modes (e.g. MINIMIZE_OR_OVERVIEW) require overview to remain open ++ // This variable keeps track of this ++ let shouldHideOverview = true; + -+ /** -+ * Draggable target interface -+ */ -+ acceptDrop: function(source, actor, x, y, time) { -+ let app = Dash.getAppFromSource(source); ++ // 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; + -+ // Don't allow favoriting of transient apps -+ if (app == null || app.is_window_backed()) -+ return false; ++ 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; + -+ if (!this._settings.is_writable('favorite-apps') || !this._dtdSettings.get_boolean('show-favorites')) -+ return false; ++ 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; + -+ let id = app.get_id(); ++ case clickAction.LAUNCH: ++ this.launchNewWindow(); ++ break; + -+ let favorites = AppFavorites.getAppFavorites().getFavoriteMap(); ++ 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; + -+ let srcIsFavorite = (id in favorites); ++ case clickAction.MINIMIZE_OR_PREVIEWS: ++ // When a single window is present, toggle minimization ++ // If only one windows is present toggle minimization, but only when trigggered with the ++ // simple click action (no modifiers, no middle click). ++ if (!Main.overview._shown){ ++ if (windows.length == 1 && !modifiers && button == 1) { ++ let w = windows[0]; ++ if (this.app == focusedApp) { ++ // Window is raised, minimize it ++ this._minimizeWindow(w); ++ } else { ++ // Window is minimized, raise it ++ Main.activateWindow(w); ++ } ++ } else { ++ // Launch previews when multiple windows are present ++ this._windowPreviews(); ++ } ++ } else { ++ this.app.activate(); ++ } ++ break; + -+ 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; ++ case clickAction.QUIT: ++ this.closeAllWindows(); ++ break; + -+ let childId = children[i].child._delegate.app.get_id(); -+ if (childId == id) -+ continue; -+ if (childId in favorites) -+ favPos++; ++ case clickAction.SKIP: ++ let w = windows[0]; ++ Main.activateWindow(w); ++ break; ++ } ++ } ++ else { ++ this.launchNewWindow(); + } + -+ // 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; ++ // Hide overview except when action mode requires it ++ if(shouldHideOverview) { ++ Main.overview.hide(); ++ } + }, + -+ showShowAppsButton: function() { -+ this.showAppsButton.visible = true -+ this.showAppsButton.set_width(-1) -+ this.showAppsButton.set_height(-1) ++ shouldShowTooltip: function() { ++ return this.actor.hover && (!this._menu || !this._menu.isOpen) && ++ (!this._previewMenu || !this._previewMenu.isOpen); + }, + -+ 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(); ++ _windowPreviews: function() { ++ if (!this._previewMenu) { ++ this._previewMenuManager = new PopupMenu.PopupMenuManager(this); + -+ let [hvalue0, vvalue0] = [hvalue, vvalue]; ++ this._previewMenu = new WindowPreview.WindowPreviewMenu(this, this._dtdSettings); + -+ let voffset = 0; -+ let hoffset = 0; -+ let fade = scrollView.get_effect('fade'); -+ if (fade) { -+ voffset = fade.vfade_offset; -+ hoffset = fade.hfade_offset; -+ } ++ this._previewMenuManager.addMenu(this._previewMenu); + -+ let box = actor.get_allocation_box(); -+ let y1 = box.y1, y2 = box.y2, x1 = box.x1, x2 = box.x2; ++ 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); ++ }); + -+ 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 (this._previewMenu.isOpen) ++ this._previewMenu.close(); ++ else ++ this._previewMenu.popup(); + -+ 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); ++ return false; ++ }, + -+ 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); ++ // 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(); ++ } ++ }, + -+ if (vvalue !== vvalue0) { -+ Tweener.addTween(vadjustment, { value: vvalue, -+ time: Util.SCROLL_TIME, -+ transition: 'easeOutQuad' ++ _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 + }); -+ } -+ -+ 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; ++ this._numberOverlayLabel.add_style_class_name('number-overlay'); ++ this._numberOverlayOrder = -1; ++ this._numberOverlayBin.hide(); + -+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; ++ this._iconContainer.add_child(this._numberOverlayBin); + -+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 ++ 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;' ++ ); ++ }, + -+const State = { -+ HIDDEN: 0, -+ SHOWING: 1, -+ SHOWN: 2, -+ HIDING: 3 -+}; ++ setNumberOverlay: function(number) { ++ this._numberOverlayOrder = number; ++ this._numberOverlayLabel.set_text(number.toString()); ++ }, + -+/* 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; -+} ++ toggleNumberOverlay: function(activate) { ++ if (activate && this._numberOverlayOrder > -1) { ++ this.updateNumberOverlay(); ++ this._numberOverlayBin.show(); ++ } ++ else ++ this._numberOverlayBin.hide(); ++ }, + -+/* -+ * 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. -+*/ ++ _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; ++ } ++ } ++ }, + -+const DashSlideContainer = new Lang.Class({ -+ Name: 'DashSlideContainer', ++ // 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(); + -+ _init: function(params) { ++ // then activate all other app windows in the current workspace ++ let windows = this.getInterestingWindows(); ++ let activeWorkspace = global.screen.get_active_workspace_index(); + -+ /* Default local params */ -+ let localDefaults = { -+ side: St.Side.LEFT, -+ initialSlideValue: 1 -+ } ++ if (windows.length <= 0) ++ return; + -+ let localParams = Params.parse(params, localDefaults, true); ++ let activatedWindows = 0; + -+ 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]; ++ for (let i = windows.length - 1; i >= 0; i--) { ++ if (windows[i].get_workspace().index() == activeWorkspace) { ++ Main.activateWindow(windows[i]); ++ activatedWindows++; + } + } ++ }, + -+ 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 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()); ++ }, + -+ this.actor._delegate = this; ++ _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; + -+ this._child = null; ++ let app_windows = this.getInterestingWindows(); + -+ // slide parameter: 1 = visible, 0 = hidden. -+ this._slidex = localParams.initialSlideValue; -+ this._side = localParams.side; -+ this._slideoutSize = 0; // minimum size when slided out -+ }, ++ if (app_windows.length <1) ++ return + ++ if (recentlyClickedAppLoopId > 0) ++ Mainloop.source_remove(recentlyClickedAppLoopId); ++ recentlyClickedAppLoopId = Mainloop.timeout_add(MEMORY_TIME, this._resetRecentlyClickedApp); + -+ _allocate: function(actor, box, flags) { ++ // 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 (this._child == null) -+ return; ++ if (reversed) { ++ recentlyClickedAppIndex--; ++ if (recentlyClickedAppIndex < 0) recentlyClickedAppIndex = recentlyClickedAppWindows.length - 1; ++ } else { ++ recentlyClickedAppIndex++; ++ } ++ let index = recentlyClickedAppIndex % recentlyClickedAppWindows.length; ++ let window = recentlyClickedAppWindows[index]; + -+ let availWidth = box.x2 - box.x1; -+ let availHeight = box.y2 - box.y1; -+ let [minChildWidth, minChildHeight, natChildWidth, natChildHeight] = -+ this._child.get_preferred_size(); ++ Main.activateWindow(window); ++ }, + -+ let childWidth = natChildWidth; -+ let childHeight = natChildHeight; ++ _resetRecentlyClickedApp: function() { ++ if (recentlyClickedAppLoopId > 0) ++ Mainloop.source_remove(recentlyClickedAppLoopId); ++ recentlyClickedAppLoopId=0; ++ recentlyClickedApp =null; ++ recentlyClickedAppWindows = null; ++ recentlyClickedAppIndex = 0; ++ recentlyClickedAppMonitor = -1; + -+ let childBox = new Clutter.ActorBox(); ++ return false; ++ }, + -+ let slideoutSize = this._slideoutSize; ++ // 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, + -+ 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); -+ } ++ _init: function(source, settings) { ++ let side = Utils.getPosition(settings); + -+ this._child.allocate(childBox, flags); -+ this._child.set_clip(-childBox.x1, -childBox.y1, -+ -childBox.x1+availWidth,-childBox.y1 + availHeight); -+ }, ++ // 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); + -+ /* 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; -+ } ++ // Change the initialized side where required. ++ this._arrowSide = side; ++ this._boxPointer._arrowSide = side; ++ this._boxPointer._userArrowSide = side; + -+ alloc.min_size = minWidth; -+ alloc.natural_size = natWidth; ++ this._dtdSettings = settings; + }, + -+ /* 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; -+ }, ++ _redisplay: function() { ++ this.removeAll(); + -+ /* I was expecting it to be a virtual function... stil I don't understand -+ how things work. -+ */ -+ add_child: function(actor) { ++ 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. + -+ /* I'm supposed to have only on child */ -+ if(this._child !== null) { -+ this.actor.remove_child(actor); -+ } ++ this._allWindowsMenuItem = new PopupMenu.PopupSubMenuMenuItem(__('All Windows'), false); ++ this._allWindowsMenuItem.actor.hide(); ++ this.addMenuItem(this._allWindowsMenuItem); + -+ this._child = actor; -+ this.actor.add_child(actor); -+ }, ++ if (!this._source.app.is_window_backed()) { ++ this._appendSeparator(); + -+ set slidex(value) { -+ this._slidex = value; -+ this._child.queue_relayout(); -+ }, ++ 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(); + -+ get slidex() { -+ return this._slidex; -+ } ++ this._source.app.open_new_window(-1); ++ this.emit('activate-window', null); ++ })); ++ this._appendSeparator(); ++ } + -+}); + -+const dockedDash = new Lang.Class({ -+ Name: 'dockedDash', -+ -+ _init: function(settings) { ++ 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._rtl = Clutter.get_default_text_direction() == Clutter.TextDirection.RTL; ++ this._source.app.launch(0, -1, true); ++ this.emit('activate-window', null); ++ })); ++ } + -+ // Load settings -+ this._settings = settings; -+ this._bindSettingsChanges(); ++ 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); ++ })); ++ } + -+ this._position = getPosition(settings); -+ this._isHorizontal = ( this._position == St.Side.TOP || -+ this._position == St.Side.BOTTOM ); ++ let canFavorite = global.settings.is_writable('favorite-apps'); + -+ // 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; ++ if (canFavorite) { ++ this._appendSeparator(); + -+ // Create intellihide object to monitor windows overlapping -+ this._intellihide = new Intellihide.intellihide(this._settings); ++ let isFavorite = AppFavorites.getAppFavorites().isFavorite(this._source.app.get_id()); + -+ // initialize dock state -+ this._dockState = State.HIDDEN; ++ 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(); ++ }); ++ })); ++ } ++ } + -+ /* status variable: true when the overview is shown through the dash -+ * applications button. -+ */ -+ this.forcedOverview = false; ++ } else { ++ this.parent(); ++ } + -+ // Put dock on the primary monitor -+ this._monitor = Main.layoutManager.primaryMonitor; ++ // quit menu ++ this._appendSeparator(); ++ this._quitfromDashMenuItem = this._appendMenuItem(_("Quit")); ++ this._quitfromDashMenuItem.connect('activate', Lang.bind(this, function() { ++ this._source.closeAllWindows(); ++ })); + -+ // this store size and the position where the dash is shown; -+ // used by intellihide module to check window overlap. -+ this.staticBox = new Clutter.ActorBox(); ++ this.update(); ++ }, + -+ // Initialize pressure barrier variables -+ this._canUsePressure = false; -+ this._pressureBarrier = null; -+ this._barrier = null; -+ this._messageTrayShowing = false; -+ this._removeBarrierTimeoutId = 0; ++ // 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() { + -+ // Initialize dwelling system variables -+ this._dockDwelling = false; -+ this._dockWatch = null; -+ this._dockDwellUserTime = 0; -+ this._dockDwellTimeoutId = 0 ++ if(this._dtdSettings.get_boolean('show-windows-preview')){ + -+ // Create a new dash object -+ this.dash = new MyDash.myDash(this._settings); ++ let windows = this._source.getInterestingWindows(); + -+ // set stored icon size to the new dash -+ Main.overview.dashIconSize = this.dash.iconSize; ++ // 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")); + -+ // connect app icon into the view selector -+ this.dash.showAppsButton.connect('notify::checked', Lang.bind(this, this._onShowAppsButtonToggled)); ++ this._quitfromDashMenuItem.actor.show(); + -+ // Create the main actor and the containers for sliding in and out and -+ // centering, turn on track hover ++ } else { ++ this._quitfromDashMenuItem.actor.hide(); ++ } + -+ 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; ++ // 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; ++ }); + -+ // This is the sliding actor whose allocation is to be tracked for input regions -+ this._slider = new DashSlideContainer({side: this._position, initialSlideValue: 0}); ++ let new_windows = windows.filter(function(w) {return old_windows.indexOf(w) < 0;}); ++ if (new_windows.length > 0) { ++ this._populateAllWindowMenu(windows); + -+ // 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)); ++ // 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; + -+ // 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; -+ }) -+ ] -+ ); ++ // 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); ++ } + -+ this._injectionsHandler = new Convenience.InjectionsHandler(); -+ this._themeManager = new themeManager(this._settings, this.actor, this.dash); ++ // Update separators ++ this._getMenuItems().forEach(Lang.bind(this, this._updateSeparatorVisibility)); ++ } + -+ // 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();})); ++ _populateAllWindowMenu: function(windows) { + -+ // Restore dash accessibility -+ Main.ctrlAltTabManager.addGroup( -+ this.dash.actor, _("Dash"),'user-bookmarks-symbolic', -+ {focusCallback: Lang.bind(this, this._onAccessibilityFocus)}); ++ this._allWindowsMenuItem.menu.removeAll(); + -+ // Load optional features -+ this._optionalScrollWorkspaceSwitch(); ++ if (windows.length > 0) { + -+ // Delay operations that require the shell to be fully loaded and with -+ // user theme applied. ++ let activeWorkspace = global.screen.get_active_workspace(); ++ let separatorShown = windows[0].get_workspace() != activeWorkspace; + -+ this._paintId = this.actor.connect("paint", Lang.bind(this, this._initialize)); ++ 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; ++ } + -+ // Hide usual Dash -+ Main.overview._controls.dash.actor.hide(); ++ let item = new WindowPreview.WindowPreviewMenuItem(window); ++ this._allWindowsMenuItem.menu.addMenuItem(item); ++ item.connect('activate', Lang.bind(this, function() { ++ this.emit('activate-window', window); ++ })); + -+ // 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); ++ // 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); + -+ // 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); ++// 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; ++ }); + -+ // 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; -+ })); -+ } ++ // 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 (this._position == St.Side.LEFT) -+ Main.overview._controls._group.insert_child_at_index(this._dashSpacer, this._rtl?-1:0); // insert on first -+ else if (this._position == St.Side.RIGHT) -+ Main.overview._controls._group.insert_child_at_index(this._dashSpacer, this._rtl?0:-1); // insert on last -+ else if (this._position == St.Side.TOP) -+ Main.overview._overview.insert_child_at_index(this._dashSpacer, 0); -+ else if (this._position == St.Side.BOTTOM) -+ Main.overview._overview.insert_child_at_index(this._dashSpacer, -1); ++ if (settings.get_boolean('isolate-monitors')) ++ windows = windows.filter(function(w) { ++ return w.get_monitor() == monitorIndex; ++ }); + -+ // 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); ++ return windows; ++} + -+ // 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}); ++/** ++ * A wrapper class around the ShowAppsIcon class. ++ * ++ * - Pass settings to the constructor ++ * - set label position based on dash orientation (Note, I am reusing most machinery of the appIcon class) ++ * - implement a popupMenu based on the AppIcon code (Note, I am reusing most machinery of the appIcon class) ++ * ++ * I can't subclass the original object because of this: https://bugzilla.gnome.org/show_bug.cgi?id=688973. ++ * thus use this pattern where the real showAppsIcon object is encaptulated, and a reference to it will be properly wired upon ++ * use of this class in place of the original showAppsButton. ++ * ++ */ + -+ // Keep the dash below the modalDialogGroup -+ Main.layoutManager.uiGroup.set_child_below_sibling(this.actor,Main.layoutManager.modalDialogGroup); ++ var ShowAppsIconWrapper = new Lang.Class({ ++ Name: 'DashToDock.ShowAppsIconWrapper', + -+ if ( this._settings.get_boolean('dock-fixed') ) -+ Main.layoutManager._trackActor(this.dash.actor, {affectsStruts: true}); ++ _init: function(settings) { ++ this._dtdSettings = settings; ++ this.realShowAppsIcon = new Dash.ShowAppsIcon(); ++ ++ /* the variable equivalent to toggleButton has a different name in the appIcon class ++ (actor): duplicate reference to easily reuse appIcon methods */ ++ this.actor = this.realShowAppsIcon.toggleButton; ++ ++ // Re-use appIcon methods ++ this._removeMenuTimeout = AppDisplay.AppIcon.prototype._removeMenuTimeout; ++ this._setPopupTimeout = AppDisplay.AppIcon.prototype._setPopupTimeout; ++ this._onButtonPress = AppDisplay.AppIcon.prototype._onButtonPress; ++ this._onKeyboardPopupMenu = AppDisplay.AppIcon.prototype._onKeyboardPopupMenu; ++ this._onLeaveEvent = AppDisplay.AppIcon.prototype._onLeaveEvent; ++ this._onTouchEvent = AppDisplay.AppIcon.prototype._onTouchEvent; ++ this._onMenuPoppedDown = AppDisplay.AppIcon.prototype._onMenuPoppedDown; ++ ++ // No action on clicked (showing of the appsview is controlled elsewhere) ++ this._onClicked = Lang.bind(this, function(actor, button) { ++ this._removeMenuTimeout(); ++ }); + -+ // 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 ; ++ this.actor.connect('leave-event', Lang.bind(this, this._onLeaveEvent)); ++ this.actor.connect('button-press-event', Lang.bind(this, this._onButtonPress)); ++ this.actor.connect('touch-event', Lang.bind(this, this._onTouchEvent)); ++ this.actor.connect('clicked', Lang.bind(this, this._onClicked)); ++ this.actor.connect('popup-menu', Lang.bind(this, this._onKeyboardPopupMenu)); + -+ // Set initial position -+ this._resetPosition(); ++ this._menu = null; ++ this._menuManager = new PopupMenu.PopupMenuManager(this); ++ this._menuTimeoutId = 0; + ++ this.showLabel = itemShowLabel; + }, + -+ _initialize: function(){ ++ popupMenu: function() { ++ this._removeMenuTimeout(); ++ this.actor.fake_release(); + -+ if(this._paintId>0){ -+ this.actor.disconnect(this._paintId); -+ this._paintId=0; ++ if (!this._menu) { ++ this._menu = new MyShowAppsIconMenu(this, this._dtdSettings); ++ this._menu.connect('open-state-changed', Lang.bind(this, function(menu, isPoppedUp) { ++ if (!isPoppedUp) ++ this._onMenuPoppedDown(); ++ })); ++ let id = Main.overview.connect('hiding', Lang.bind(this, function() { ++ this._menu.close(); ++ })); ++ this._menu.actor.connect('destroy', function() { ++ Main.overview.disconnect(id); ++ }); ++ this._menuManager.addMenu(this._menu); + } + -+ this.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(); ++ //this.emit('menu-state-changed', true); + -+ // Setup pressure barrier (GS38+ only) -+ this._updatePressureBarrier(); -+ this._updateBarrier(); ++ this.actor.set_hover(true); ++ this._menu.popup(); ++ this._menuManager.ignoreRelease(); ++ this.emit('sync-tooltip'); + -+ // setup dwelling system if pressure barriers are not available -+ this._setupDockDwellIfNeeded(); ++ return false; ++ } ++}); ++Signals.addSignalMethods(ShowAppsIconWrapper.prototype); + -+ // Insensitive Message Tray -+ this._updateInsensitiveTray(); -+ }, + -+ destroy: function(){ ++/** ++ * A menu for the showAppsIcon ++ */ ++const MyShowAppsIconMenu = new Lang.Class({ ++ Name: 'DashToDock.ShowAppsIconMenu', ++ Extends: MyAppIconMenu, + -+ // Disconnect global signals -+ this._signalsHandler.destroy(); -+ // The dash and intellihide have global signals as well internally -+ this.dash.destroy(); -+ this._intellihide.destroy(); ++ _redisplay: function() { ++ this.removeAll(); + -+ this._injectionsHandler.destroy(); ++ /* 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); + -+ // Destroy main clutter actor: this should be sufficient removing it and -+ // destroying all its children -+ this.actor.destroy(); ++ item.connect('activate', function () { ++ Util.spawn(["gnome-shell-extension-prefs", Me.metadata.uuid]); ++ }); ++ } ++}); + -+ // Remove barrier timeout -+ if (this._removeBarrierTimeoutId > 0) -+ Mainloop.source_remove(this._removeBarrierTimeoutId); ++/** ++ * 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; + -+ // Remove existing barrier -+ this._removeBarrier(); ++ this.label.set_text(this._labelText); ++ this.label.opacity = 0; ++ this.label.show(); + -+ // Remove pointer watcher -+ if(this._dockWatch){ -+ PointerWatcher.getPointerWatcher()._removeWatch(this._dockWatch); -+ this._dockWatch = null; -+ } ++ let [stageX, stageY] = this.get_transformed_position(); ++ let node = this.label.get_theme_node(); + -+ // Remove the dashSpacer -+ this._dashSpacer.destroy(); ++ let itemWidth = this.allocation.x2 - this.allocation.x1; ++ let itemHeight = this.allocation.y2 - this.allocation.y1; + -+ // restore messageIndicator position -+ Main.overview._controls._indicator.actor.get_first_child().y = this._oldMessageIndicatorPosition; ++ let labelWidth = this.label.get_width(); ++ let labelHeight = this.label.get_height(); + -+ // 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; ++ let x, y, xOffset, yOffset; + -+ // reset stored icon size to the default dash -+ Main.overview.dashIconSize = Main.overview._controls.dash.iconSize; -+ // Reshow panel corners -+ this._revertPanelCorners(); -+ }, ++ let position = Utils.getPosition(this._dtdSettings); ++ this._isHorizontal = ((position == St.Side.TOP) || (position == St.Side.BOTTOM)); ++ let labelOffset = node.get_length('-x-offset'); + -+ _bindSettingsChanges: function() { ++ 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; ++ } + -+ this._settings.connect('changed::scroll-switch-workspace', Lang.bind(this, function(){ -+ this._optionalScrollWorkspaceSwitch(this._settings.get_boolean('scroll-switch-workspace')); -+ })); ++ // keep the label inside the screen border ++ // Only needed fot the x coordinate. + -+ this._settings.connect('changed::dash-max-icon-size', Lang.bind(this, function(){ -+ this.dash.setIconSize(this._settings.get_int('dash-max-icon-size')); -+ })); ++ // 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._settings.connect('changed::icon-size-fixed', Lang.bind(this, function(){ -+ this.dash.setIconSize(this._settings.get_int('dash-max-icon-size')); -+ })); ++ this.label.set_position(x, y); ++ Tweener.addTween(this.label, { ++ opacity: 255, ++ time: DASH_ITEM_LABEL_SHOW_TIME, ++ transition: 'easeOutQuad', ++ }); ++} +diff --git a/extensions/dash-to-dock/convenience.js b/extensions/dash-to-dock/convenience.js +new file mode 100644 +index 0000000..bc50419 +--- /dev/null ++++ b/extensions/dash-to-dock/convenience.js +@@ -0,0 +1,74 @@ ++/* -*- mode: js; js-basic-offset: 4; indent-tabs-mode: nil -*- */ + -+ this._settings.connect('changed::show-running', Lang.bind(this, function(){ -+ this.dash.resetAppIcons(); -+ })); ++/* ++ * Part of this file comes from gnome-shell-extensions: ++ * https://gitlab.gnome.org/GNOME/gnome-shell-extensions/ ++ */ + -+ this._settings.connect('changed::show-apps-at-top', Lang.bind(this, function(){ -+ this.dash.resetAppIcons(); -+ })); ++const Gettext = imports.gettext; ++const Gio = imports.gi.Gio; + -+ this._settings.connect('changed::dock-fixed', Lang.bind(this, function(){ ++const Config = imports.misc.config; ++const ExtensionUtils = imports.misc.extensionUtils; + -+ if(this._settings.get_boolean('dock-fixed')) { -+ Main.layoutManager._trackActor(this.dash.actor, {affectsStruts: true}); -+ } else { -+ Main.layoutManager._untrackActor(this.dash.actor); -+ } ++/** ++ * 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(); + -+ this._resetPosition(); ++ domain = domain || extension.metadata['gettext-domain']; + -+ // Add or remove barrier depending on if dock-fixed -+ this._updateBarrier(); ++ // 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); ++} + -+ this._updateVisibilityMode(); -+ })); ++/** ++ * 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(); + -+ this._settings.connect('changed::intellihide', Lang.bind(this, this._updateVisibilityMode)); ++ schema = schema || extension.metadata['settings-schema']; + -+ this._settings.connect('changed::intellihide-perapp', Lang.bind(this, function(){ -+ this._intellihide.forceUpdate(); -+ })); ++ const GioSSS = Gio.SettingsSchemaSource; + -+ 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(); -+ })); ++ // 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.'); + -+ // This is call when visibility settings change -+ _updateVisibilityMode: function() { ++ return new Gio.Settings({ ++ settings_schema: schemaObj ++ }); ++} +diff --git a/extensions/dash-to-dock/dash.js b/extensions/dash-to-dock/dash.js +new file mode 100644 +index 0000000..4cf5aa2 +--- /dev/null ++++ b/extensions/dash-to-dock/dash.js +@@ -0,0 +1,1175 @@ ++// -*- mode: js; js-indent-level: 4; indent-tabs-mode: nil -*- + -+ 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') -+ } ++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; + -+ if (this._intellihideIsEnabled) -+ this._intellihide.enable(); -+ else -+ this._intellihide.disable(); ++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; + -+ this._updateDashVisibility(); -+ }, ++const Me = imports.misc.extensionUtils.getCurrentExtension(); ++const Utils = Me.imports.utils; ++const AppIcons = Me.imports.appIcons; + -+ /* Show/hide dash based on, in order of priority: -+ * overview visibility -+ * fixed mode -+ * intellihide -+ * autohide -+ * overview visibility -+ */ -+ _updateDashVisibility: function() { ++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; + -+ if (Main.overview.visibleTarget) -+ return; ++/** ++ * 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; ++} + -+ 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(); ++/** ++ * 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', + -+ if( this._box.hover ) { -+ this._animateIn(this._settings.get_double('animation-time'), 0); -+ } else { -+ this._animateOut(this._settings.get_double('animation-time'), 0); -+ } ++ _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); + -+ } else { -+ this._animateOut(this._settings.get_double('animation-time'), 0); -+ } -+ } -+ }, ++ this._position = Utils.getPosition(settings); ++ this._isHorizontal = ((this._position == St.Side.TOP) || ++ (this._position == St.Side.BOTTOM)); + -+ _onOverviewShowing: function() { -+ this._ignoreHover = true; -+ this._intellihide.disable(); -+ this._removeAnimations(); -+ this._animateIn(this._settings.get_double('animation-time'), 0); -+ }, ++ let layout = new Clutter.BoxLayout({ ++ orientation: this._isHorizontal ? Clutter.Orientation.HORIZONTAL : Clutter.Orientation.VERTICAL ++ }); + -+ _onOverviewHiding: function() { -+ this._ignoreHover = false; -+ this._intellihide.enable(); -+ this._updateDashVisibility(); -+ }, ++ 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)); + -+ _hoverChanged: function() { ++ this.actor._delegate = this; ++ }, + -+ if (!this._ignoreHover) { ++ _allocate: function(actor, box, flags) { ++ let contentBox = box; ++ let availWidth = contentBox.x2 - contentBox.x1; ++ let availHeight = contentBox.y2 - contentBox.y1; + -+ // 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(); -+ } -+ } -+ } -+ }, ++ let [appIcons, showAppsButton] = actor.get_children(); ++ let [showAppsMinHeight, showAppsNatHeight] = showAppsButton.get_preferred_height(availWidth); ++ let [showAppsMinWidth, showAppsNatWidth] = showAppsButton.get_preferred_width(availHeight); + -+ _show: function() { ++ let offset_x = this._isHorizontal?showAppsNatWidth:0; ++ let offset_y = this._isHorizontal?0:showAppsNatHeight; + -+ if ( this._dockState == State.HIDDEN || this._dockState == State.HIDING ) { ++ 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); + -+ 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(); -+ } ++ 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); + -+ this.emit("showing"); -+ this._animateIn(this._settings.get_double('animation-time'), 0); ++ childBox.x2 = contentBox.x2; ++ childBox.y2 = contentBox.y2; ++ childBox.x1 = contentBox.x2 - showAppsNatWidth; ++ childBox.y1 = contentBox.y2 - showAppsNatHeight; ++ showAppsButton.allocate(childBox, flags); + } + }, + -+ _hide: function() { -+ -+ // If no hiding animation is running or queued -+ if ( this._dockState == State.SHOWN || this._dockState == State.SHOWING ) { ++ _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 delay; ++ let [, natWidth] = this.actor.layout_manager.get_preferred_width(this.actor, forHeight); + -+ 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'); -+ } ++ let themeNode = this.actor.get_theme_node(); ++ let [, showAppsButton] = this.actor.get_children(); ++ let [minWidth, ] = showAppsButton.get_preferred_height(forHeight); + -+ this.emit("hiding"); -+ this._animateOut(this._settings.get_double('animation-time'), delay); ++ alloc.min_size = minWidth; ++ alloc.natural_size = natWidth; + -+ } + }, + -+ _animateIn: function(time, delay) { -+ -+ this._dockState = State.SHOWING; ++ _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 + -+ 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)); -+ }) -+ }); -+ }, ++ let [, natHeight] = this.actor.layout_manager.get_preferred_height(this.actor, forWidth); + -+ _animateOut: function(time, delay){ ++ let themeNode = this.actor.get_theme_node(); ++ let [, showAppsButton] = this.actor.get_children(); ++ let [minHeight, ] = showAppsButton.get_preferred_height(forWidth); + -+ 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(); -+ }) -+ }); -+ }, ++ alloc.min_size = minHeight; ++ alloc.natural_size = natHeight; ++ } ++}); + -+ // 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; -+ } -+ }, ++const baseIconSizes = [16, 22, 24, 32, 48, 64, 96, 128]; + -+ _checkDockDwell: function(x, y) { -+ let monitor = this._monitor; ++/** ++ * This class is a fork of the upstream dash class (ui.dash.js) ++ * ++ * Summary of changes: ++ * - disconnect global signals adding a destroy method; ++ * - play animations even when not in overview mode ++ * - set a maximum icon size ++ * - show running and/or favorite applications ++ * - emit a custom signal when an app icon is added ++ * - hide showApps label when the custom menu is shown. ++ * - add scrollview ++ * ensure actor is visible on keyfocus inseid the scrollview ++ * - add 128px icon size, might be usefull for hidpi display ++ * - sync minimization application target position. ++ * - keep running apps ordered. ++ */ ++var MyDash = new Lang.Class({ ++ Name: 'DashToDock.MyDash', + -+ // Check for the dock area -+ let shouldDwell = (x >= this.staticBox.x1 && x <= this.staticBox.x2 && -+ y >= this.staticBox.y1 && y <= this.staticBox.y2); ++ _init: function(settings, remoteModel, monitorIndex) { ++ this._dtdSettings = settings; + -+ // 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; -+ } ++ // Initialize icon variables and size ++ this._maxHeight = -1; ++ this.iconSize = this._dtdSettings.get_int('dash-max-icon-size'); ++ this._availableIconSizes = baseIconSizes; ++ this._shownInitially = false; ++ this._initializeIconSize(this.iconSize); + -+ 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._remoteModel = remoteModel; ++ this._monitorIndex = monitorIndex; ++ this._position = Utils.getPosition(settings); ++ this._isHorizontal = ((this._position == St.Side.TOP) || ++ (this._position == St.Side.BOTTOM)); ++ this._signalsHandler = new Utils.GlobalSignalsHandler(); + -+ this._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; -+ } -+ }, ++ this._dragPlaceholder = null; ++ this._dragPlaceholderPos = -1; ++ this._animatingPlaceholdersCount = 0; ++ this._showLabelTimeoutId = 0; ++ this._resetHoverTimeoutId = 0; ++ this._ensureAppIconVisibilityTimeoutId = 0; ++ this._labelShowing = false; + -+ _cancelDockDwell: function() { -+ if (this._dockDwellTimeoutId != 0) { -+ Mainloop.source_remove(this._dockDwellTimeoutId); -+ this._dockDwellTimeoutId = 0; -+ } -+ }, ++ 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 ++ }); + -+ _dockDwellTimeout: function() { -+ this._dockDwellTimeoutId = 0; ++ this._scrollView.connect('scroll-event', Lang.bind(this, this._onScrollEvent)); + -+ if (this._monitor.inFullscreen) -+ return GLib.SOURCE_REMOVE; ++ 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); + -+ // 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; ++ // Create a wrapper around the real showAppsIcon in order to add a popupMenu. ++ let showAppsIconWrapper = new AppIcons.ShowAppsIconWrapper(this._dtdSettings); ++ showAppsIconWrapper.connect('menu-state-changed', Lang.bind(this, function(showAppsIconWrapper, opened) { ++ this._itemMenuStateChanged(showAppsIconWrapper, opened); ++ })); ++ // an instance of the showAppsIcon class is encapsulated in the wrapper ++ this._showAppsIcon = showAppsIconWrapper.realShowAppsIcon; + -+ // 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; ++ this._showAppsIcon.childScale = 1; ++ this._showAppsIcon.childOpacity = 255; ++ this._showAppsIcon.icon.setIconSize(this.iconSize); ++ this._hookUpLabel(this._showAppsIcon); + -+ // Reuse the pressure version function, the logic is the same -+ this._onPressureSensed(); -+ return GLib.SOURCE_REMOVE; -+ }, ++ this.showAppsButton = this._showAppsIcon.toggleButton; + -+ _updatePressureBarrier: function() { -+ this._canUsePressure = global.display.supports_extended_barriers(); -+ let pressureThreshold = this._settings.get_double('pressure-threshold'); ++ this._container.add_actor(this._showAppsIcon); + -+ // Remove existing pressure barrier -+ if (this._pressureBarrier) { -+ this._pressureBarrier.destroy(); -+ this._pressureBarrier = null; -+ } ++ 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 ++ }); + -+ // 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(); ++ 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; + })); + } -+ }, + -+ // handler for mouse pressure sensed -+ _onPressureSensed: function() { ++ // 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)); + -+ if (Main.overview.visibleTarget) -+ return; ++ this._workId = Main.initializeDeferredWork(this._box, Lang.bind(this, this._redisplay)); + -+ // 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._settings = new Gio.Settings({ ++ schema_id: 'org.gnome.shell' ++ }); + -+ this._show(); ++ 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) ++ ]); + }, + -+ _onMessageTrayShowing: function() { ++ destroy: function() { ++ this._signalsHandler.destroy(); ++ }, + -+ // 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); ++ _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; + -+ // 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(); -+ }, ++ // reset timeout to avid conflicts with the mousehover event ++ if (this._ensureAppIconVisibilityTimeoutId > 0) { ++ Mainloop.source_remove(this._ensureAppIconVisibilityTimeoutId); ++ this._ensureAppIconVisibilityTimeoutId = 0; ++ } + -+ _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; -+ }) -+ }); ++ // Skip to avoid double events mouse ++ if (event.is_pointer_emulated()) ++ return Clutter.EVENT_STOP; + -+ this._messageTrayShowing = false; -+ this._updateBarrier(); -+ }, ++ let adjustment, delta; + -+ // 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; -+ }, ++ if (this._isHorizontal) ++ adjustment = this._scrollView.get_hscroll_bar().get_adjustment(); ++ else ++ adjustment = this._scrollView.get_vscroll_bar().get_adjustment(); + -+ // Update pressure barrier size -+ _updateBarrier: function() { -+ // Remove existing barrier -+ this._removeBarrier(); ++ let increment = adjustment.step_increment; + -+ // 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; ++ 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; + } + -+ // 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; ++ adjustment.set_value(adjustment.get_value() + delta); + -+ 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; -+ } ++ return Clutter.EVENT_STOP; ++ }, + -+ 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); -+ } ++ _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(); + }, + -+ _isPrimaryMonitor: function() { -+ return (this._monitor.x == Main.layoutManager.primaryMonitor.x && -+ this._monitor.y == Main.layoutManager.primaryMonitor.y); ++ _onDragEnd: function() { ++ if (this._dragCancelled) ++ return; ++ ++ this._endDrag(); + }, + -+ _resetPosition: function() { ++ _endDrag: function() { ++ this._clearDragPlaceholder(); ++ this._clearEmptyDropTarget(); ++ this._showAppsIcon.setDragApp(null); ++ DND.removeDragMonitor(this._dragMonitor); ++ }, + -+ // Ensure variables linked to settings are updated. -+ this._updateVisibilityMode(); ++ _onDragMotion: function(dragEvent) { ++ let app = Dash.getAppFromSource(dragEvent.source); ++ if (app == null) ++ return DND.DragMotionResult.CONTINUE; + -+ this._monitor = this._getMonitor(); ++ let showAppsHovered = this._showAppsIcon.contains(dragEvent.targetActor); + -+ let unavailableTopSpace = 0; -+ let unavailableBottomSpace = 0; ++ if (!this._box.contains(dragEvent.targetActor) || showAppsHovered) ++ this._clearDragPlaceholder(); + -+ let extendHeight = this._settings.get_boolean('extend-height'); ++ if (showAppsHovered) ++ this._showAppsIcon.setDragApp(app); ++ else ++ this._showAppsIcon.setDragApp(null); + -+ // 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(); -+ } ++ return DND.DragMotionResult.CONTINUE; ++ }, + -+ let fraction = this._settings.get_double('height-fraction'); ++ _appIdListToHash: function(apps) { ++ let ids = {}; ++ for (let i = 0; i < apps.length; i++) ++ ids[apps[i].get_id()] = apps[i]; ++ return ids; ++ }, + -+ if(extendHeight) -+ fraction = 1; -+ else if(fraction<0 || fraction >1) -+ fraction = 0.95; ++ _queueRedisplay: function() { ++ Main.queueDeferredWork(this._workId); ++ }, + -+ let anchor_point; ++ _hookUpLabel: function(item, appIcon) { ++ item.child.connect('notify::hover', Lang.bind(this, function() { ++ this._syncLabel(item, appIcon); ++ })); + -+ if(this._isHorizontal){ ++ let id = Main.overview.connect('hiding', Lang.bind(this, function() { ++ this._labelShowing = false; ++ item.hideLabel(); ++ })); ++ item.child.connect('destroy', function() { ++ Main.overview.disconnect(id); ++ }); + -+ let availableWidth = this._monitor.width; -+ this.actor.width = Math.round( fraction * availableWidth); ++ if (appIcon) { ++ appIcon.connect('sync-tooltip', Lang.bind(this, function() { ++ this._syncLabel(item, appIcon); ++ })); ++ } ++ }, + -+ 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; -+ } ++ _createAppItem: function(app) { ++ let appIcon = new AppIcons.MyAppIcon(this._dtdSettings, this._remoteModel, app, this._monitorIndex, ++ { setSizeManually: true, ++ showLabel: false }); + -+ 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 (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; ++ })); ++ } + -+ 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'); -+ } ++ appIcon.connect('menu-state-changed', Lang.bind(this, function(appIcon, opened) { ++ this._itemMenuStateChanged(item, opened); ++ })); + -+ } else { ++ let item = new Dash.DashItemContainer(); + -+ let availableHeight = this._monitor.height - unavailableTopSpace - unavailableBottomSpace; -+ this.actor.height = Math.round( fraction * availableHeight); ++ extendDashItemContainer(item, this._dtdSettings); ++ item.setChild(appIcon.actor); + -+ 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; ++ 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; ++ } + } ++ })); + -+ 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); ++ appIcon.actor.connect('clicked', Lang.bind(this, function(actor) { ++ ensureActorVisibleInScrollView(this._scrollView, actor); ++ })); + -+ 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'); ++ 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; + } -+ } ++ })); + -+ this._y0 = this.actor.y; -+ this._adjustPanelCorners(); ++ // Override default AppIcon label_actor, now the ++ // accessible_name is set at DashItemContainer.setLabelText ++ appIcon.actor.label_actor = null; ++ item.setLabelText(app.get_name()); + -+ this._updateStaticBox(); -+ }, ++ appIcon.icon.setIconSize(this.iconSize); ++ this._hookUpLabel(item, appIcon); + -+ _updateStaticBox: function() { ++ return item; ++ }, + -+ 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 -+ ); ++ /** ++ * 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; ++ }); + -+ this._intellihide.updateTargetBox(this.staticBox); -+ }, ++ let appIcons = iconChildren.map(function(actor) { ++ return actor.child._delegate; ++ }); + -+ // 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(); -+ } ++ return appIcons; + }, + -+ _revertPanelCorners: function() { -+ Main.panel._leftCorner.actor.show(); -+ Main.panel._rightCorner.actor.show(); ++ _updateAppsIconGeometry: function() { ++ let appIcons = this.getAppIcons(); ++ appIcons.forEach(function(icon) { ++ icon.updateIconGeometry(); ++ }); + }, + -+ _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; ++ _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; ++ } + -+ return monitor; ++ 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'); ++ } + }, + -+ _removeAnimations: function() { -+ Tweener.removeTweens(this._slider); -+ }, ++ _syncLabel: function(item, appIcon) { ++ let shouldShow = appIcon ? appIcon.shouldShowTooltip() : item.child.get_hover(); + -+ _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); ++ 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'); ++ } ++ } + }, + -+ _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(); -+ }, ++ _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; ++ }); + -+ _pageChanged: function() { ++ iconChildren.push(this._showAppsIcon); + -+ let activePage = Main.overview.viewSelector.getActivePage(); -+ let dashVisible = (activePage == ViewSelector.ViewPage.WINDOWS || -+ activePage == ViewSelector.ViewPage.APPS); ++ if (this._maxHeight == -1) ++ return; + -+ if(dashVisible){ -+ this._animateIn(this._settings.get_double('animation-time'), 0); -+ } else { -+ this._animateOut(this._settings.get_double('animation-time'), 0); -+ } -+ }, ++ // Check if the container is present in the stage. This avoids critical ++ // errors when unlocking the screen ++ if (!this._container.get_stage()) ++ return; + -+ _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 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 activePage = Main.overview.viewSelector.getActivePage(); -+ this._dashSpacer.visible = (this._isHorizontal || activePage == ViewSelector.ViewPage.WINDOWS); -+ }, ++ let firstButton = iconChildren[0].child; ++ let firstIcon = firstButton._delegate.icon; + -+ // 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); -+ }, ++ 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); + -+ _onShowAppsButtonToggled: function() { ++ let scaleFactor = St.ThemeContext.get_for_stage(global.stage).scale_factor; ++ let iconSizes = this._availableIconSizes.map(function(s) { ++ return s * scaleFactor; ++ }); + -+ // 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. ++ // 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 selector = Main.overview.viewSelector; ++ let availSize = availHeight / iconChildren.length; + -+ 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; -+ } -+ }); ++ let newIconSize = this._availableIconSizes[0]; ++ for (let i = 0; i < iconSizes.length; i++) { ++ if (iconSizes[i] < availSize) ++ newIconSize = this._availableIconSizes[i]; ++ } + -+ if(this.dash.showAppsButton.checked){ -+ // force entering overview if needed -+ if (!Main.overview._shown) { ++ if (newIconSize == this.iconSize) ++ return; + -+ let view = Main.overview.viewSelector.appDisplay._views[visibleView].view; -+ let grid = view._grid; ++ let oldIconSize = this.iconSize; ++ this.iconSize = newIconSize; ++ this.emit('icon-size-changed'); + -+ // 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); -+ })); -+ })); ++ let scale = oldIconSize / newIconSize; ++ for (let i = 0; i < iconChildren.length; i++) { ++ let icon = iconChildren[i].child._delegate.icon; + -+ // Finally show the overview -+ Main.overview.show(); -+ this.forcedOverview = true; -+ } else { -+ selector._showAppsButton.checked = true; -+ } -+ } else { -+ if (this.forcedOverview) { -+ // force exiting overview if needed ++ // Set the new size immediately, to keep the icons' sizes ++ // in sync with this.iconSize ++ icon.setIconSize(this.iconSize); + -+ // 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; -+ })); ++ // 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; + -+ } else { -+ selector._showAppsButton.checked = false; -+ } ++ 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); + -+ // 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; ++ Tweener.addTween(icon.icon, ++ { width: targetWidth, ++ height: targetHeight, ++ time: DASH_ANIMATION_TIME, ++ transition: 'easeOutQuad', ++ }); ++ } + }, + -+ // 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; -+ }, ++ _redisplay: function() { ++ let favorites = AppFavorites.getAppFavorites().getFavoriteMap(); + -+ // Optional features enable/disable ++ 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; ++ }); ++ } + -+ // Switch workspace by scrolling over the dock -+ _optionalScrollWorkspaceSwitch: function() { ++ 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 = []; + -+ let label = 'optionalScrollWorkspaceSwitch'; ++ if (this._dtdSettings.get_boolean('show-favorites')) { ++ for (let id in favorites) ++ newApps.push(favorites[id]); ++ } + -+ 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)(); -+ })); ++ // 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); ++ } ++ } + -+ if(this._settings.get_boolean('scroll-switch-workspace')) -+ Lang.bind(this, enable)(); ++ // 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. + -+ function enable(){ ++ let addedItems = []; ++ let removedActors = []; + -+ this._signalsHandler.removeWithLabel(label); ++ 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; ++ } + -+ this._signalsHandler.addWithLabel(label, -+ [ -+ this._box, -+ 'scroll-event', -+ Lang.bind(this, onScrollEvent) -+ ] -+ ); ++ // App removed at oldIndex ++ if (oldApps[oldIndex] && (newApps.indexOf(oldApps[oldIndex]) == -1)) { ++ removedActors.push(children[oldIndex]); ++ oldIndex++; ++ continue; ++ } + -+ this._optionalScrollWorkspaceSwitchDeadTimeId=0; -+ } ++ // 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; ++ } + -+ function disable() { -+ this._signalsHandler.removeWithLabel(label); ++ // 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(this._optionalScrollWorkspaceSwitchDeadTimeId>0){ -+ Mainloop.source_remove(this._optionalScrollWorkspaceSwitchDeadTimeId); -+ this._optionalScrollWorkspaceSwitchDeadTimeId=0; ++ 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++; + } + } + -+ // 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; ++ for (let i = 0; i < addedItems.length; i++) ++ this._box.insert_child_at_index(addedItems[i].item, ++ addedItems[i].pos); + -+ let activeWs = global.screen.get_active_workspace(); -+ let direction = null; ++ for (let i = 0; i < removedActors.length; i++) { ++ let item = removedActors[i]; + -+ 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; -+ } ++ // Don't animate item removal when the overview is transitioning ++ if (!Main.overview.animationInProgress) ++ item.animateOutAndDestroy(); ++ else ++ item.destroy(); ++ } + -+ if(direction !==null ){ ++ this._adjustIconSize(); + -+ // 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. ++ 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]); + -+ // 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; -+ } -+ )); -+ } ++ // 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; + -+ let ws; ++ if (!this._shownInitially) ++ this._shownInitially = true; + -+ ws = activeWs.get_neighbor(direction) ++ for (let i = 0; i < addedItems.length; i++) ++ addedItems[i].item.show(animate); + -+ 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; -+ }); ++ // Workaround for https://bugzilla.gnome.org/show_bug.cgi?id=692744 ++ // Without it, StBoxLayout may use a stale size cache ++ this._box.queue_relayout(); + -+ // Do not show wokspaceSwithcer in overview -+ if(!Main.overview.visible) -+ Main.wm._workspaceSwitcherPopup.display(direction, ws.index()); -+ Main.wm.actionMoveWorkspace(ws); ++ // This is required for icon reordering when the scrollview is used. ++ this._updateAppsIconGeometry(); + -+ return true; ++ // This will update the size, and the corresponding number for each icon ++ this._updateNumberOverlay(); ++ }, + -+ } else { -+ return false; ++ _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(); ++ }); + + }, + -+ // Makes the message not being triggered by mouse. SOURCE: insensitive-tray extension. -+ _updateInsensitiveTray: function() { ++ toggleNumberOverlay: function(activate) { ++ let appIcons = this.getAppIcons(); ++ appIcons.forEach(function(icon) { ++ icon.toggleNumberOverlay(activate); ++ }); ++ }, + -+ let insensitive = this._settings.get_boolean('insensitive-message-tray'); ++ _initializeIconSize: function(max_size) { ++ let max_allowed = baseIconSizes[baseIconSizes.length-1]; ++ max_size = Math.min(max_size, max_allowed); + -+ 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') -+ } ++ 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 + -+ let themeNode = this._dash._container.get_theme_node(); -+ this._dash._container.set_style(oldStyle); ++ // Take into account childredn position in rtl ++ if (this._isHorizontal && (Clutter.get_default_text_direction() == Clutter.TextDirection.RTL)) ++ pos = numChildren - pos; + -+ this._defaultBackgroundColor = themeNode.get_background_color(); -+ }, ++ if ((pos != this._dragPlaceholderPos) && (pos <= numFavorites) && (this._animatingPlaceholdersCount == 0)) { ++ this._dragPlaceholderPos = pos; + -+ _updateCustomStyleClasses: function(){ ++ // Don't allow positioning before or after self ++ if ((favPos != -1) && (pos == favPos || pos == favPos + 1)) { ++ this._clearDragPlaceholder(); ++ return DND.DragMotionResult.CONTINUE; ++ } + -+ if (this._settings.get_boolean('apply-custom-theme')) -+ this._actor.add_style_class_name('dashtodock'); -+ else { -+ this._actor.remove_style_class_name('dashtodock'); -+ } ++ // 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; + -+ if (this._settings.get_boolean('custom-theme-shrink')) -+ this._actor.add_style_class_name('shrink'); -+ else { -+ this._actor.remove_style_class_name('shrink'); ++ 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]); + } + -+ 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'); -+ } ++ // 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; + -+ updateCustomTheme: function() { -+ this._updateCustomStyleClasses(); -+ this._getBackgroundColor(); -+ this._updateBackgroundOpacity(); -+ this._adjustTheme(); -+ this._dash._redisplay(); ++ let srcIsFavorite = (favPos != -1); ++ ++ if (srcIsFavorite) ++ return DND.DragMotionResult.MOVE_DROP; ++ ++ return DND.DragMotionResult.COPY_DROP; + }, + -+ /* 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; -+ } ++ /** ++ * Draggable target interface ++ */ ++ acceptDrop: function(source, actor, x, y, time) { ++ let app = Dash.getAppFromSource(source); + -+ // Remove prior style edits -+ this._dash._container.set_style(null); ++ // Don't allow favoriting of transient apps ++ if (app == null || app.is_window_backed()) ++ return false; + -+ /* If built-in theme is enabled do nothing else */ -+ if( this._settings.get_boolean('apply-custom-theme') ) -+ return; ++ if (!this._settings.is_writable('favorite-apps') || !this._dtdSettings.get_boolean('show-favorites')) ++ return false; + -+ let newStyle = ''; -+ let position = getPosition(this._settings); ++ 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; + -+ if ( ! this._settings.get_boolean('custom-theme-shrink') ) { ++ 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; ++ })); + -+ // 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); ++ return true; ++ }, + -+ /* 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 = ''; ++ showShowAppsButton: function() { ++ this.showAppsButton.visible = true ++ this.showAppsButton.set_width(-1) ++ this.showAppsButton.set_height(-1) ++ }, + -+ 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() + ';'; -+ } ++ hideShowAppsButton: function() { ++ this.showAppsButton.hide() ++ this.showAppsButton.set_width(0) ++ this.showAppsButton.set_height(0) ++ } ++}); + -+ 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; -+ } ++Signals.addSignalMethods(MyDash.prototype); + -+ newStyle = borderInner + ': none;' + -+ 'border-radius: ' + borderRadiusValue + -+ borderMissingStyle ; ++/** ++ * 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; + -+ /* 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); -+ } ++ 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(); + -+ /* 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); -+ } -+ }, ++ let [hvalue0, vvalue0] = [hvalue, vvalue]; + -+ _bindSettingsChanges: function() { ++ let voffset = 0; ++ let hoffset = 0; ++ let fade = scrollView.get_effect('fade'); ++ if (fade) { ++ voffset = fade.vfade_offset; ++ hoffset = fade.hfade_offset; ++ } + -+ let keys = ['opaque-background', -+ 'background-opacity', -+ 'apply-custom-theme', -+ 'custom-theme-shrink', -+ 'custom-theme-running-dots', -+ 'extend-height']; ++ let box = actor.get_allocation_box(); ++ let y1 = box.y1, y2 = box.y2, x1 = box.x1, x2 = box.x2; + -+ keys.forEach(function(key){ -+ this._settings.connect('changed::'+key, -+ Lang.bind(this, this.updateCustomTheme) -+ ); -+ }, this ); ++ 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); + -+/* -+ * 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(); ++ 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); + -+ let [x, y] = actor.get_transformed_position(); -+ let [width, height] =actor.get_transformed_size(); ++ if (vvalue !== vvalue0) { ++ Tweener.addTween(vadjustment, { value: vvalue, ++ time: Util.SCROLL_TIME, ++ transition: 'easeOutQuad' ++ }); ++ } + -+ let test = (pointerX < x + width) && -+ (pointerX > x) && -+ (pointerY < y + height) && -+ (pointerY > y); ++ if (hvalue !== hvalue0) { ++ Tweener.addTween(hadjustment, ++ { value: hvalue, ++ time: Util.SCROLL_TIME, ++ transition: 'easeOutQuad' }); ++ } + -+ return test; ++ return [hvalue- hvalue0, vvalue - vvalue0]; +} diff --git a/extensions/dash-to-dock/docking.js b/extensions/dash-to-dock/docking.js new file mode 100644 -index 0000000..08707b4 +index 0000000..11810a1 --- /dev/null +++ b/extensions/dash-to-dock/docking.js -@@ -0,0 +1,1909 @@ +@@ -0,0 +1,1925 @@ +// -*- mode: js; js-indent-level: 4; indent-tabs-mode: nil -*- + +const Clutter = imports.gi.Clutter; @@ -7671,10 +7286,11 @@ index 0000000..08707b4 +const Intellihide = Me.imports.intellihide; +const Theming = Me.imports.theming; +const MyDash = Me.imports.dash; ++const LauncherAPI = Me.imports.launcherAPI; + +const DOCK_DWELL_CHECK_INTERVAL = 100; + -+const State = { ++var State = { + HIDDEN: 0, + SHOWING: 1, + SHOWN: 2, @@ -7833,11 +7449,12 @@ index 0000000..08707b4 +const DockedDash = new Lang.Class({ + Name: 'DashToDock.DockedDash', + -+ _init: function(settings, monitorIndex) { ++ _init: function(settings, remoteModel, monitorIndex) { + this._rtl = (Clutter.get_default_text_direction() == Clutter.TextDirection.RTL); + + // Load settings + this._settings = settings; ++ this._remoteModel = remoteModel; + this._monitorIndex = monitorIndex; + // Connect global signals + this._signalsHandler = new Utils.GlobalSignalsHandler(); @@ -7882,7 +7499,7 @@ index 0000000..08707b4 + this._dockDwellTimeoutId = 0 + + // Create a new dash object -+ this.dash = new MyDash.MyDash(this._settings, this._monitorIndex); ++ this.dash = new MyDash.MyDash(this._settings, this._remoteModel, this._monitorIndex); + + if (!this._settings.get_boolean('show-show-apps-button')) + this.dash.hideShowAppsButton(); @@ -7992,7 +7609,7 @@ index 0000000..08707b4 + ]); + + this._injectionsHandler = new Utils.InjectionsHandler(); -+ this._themeManager = new Theming.ThemeManager(this._settings, this.actor, this.dash); ++ this._themeManager = new Theming.ThemeManager(this._settings, this); + + // Since the actor is not a topLevel child and its parent is now not added to the Chrome, + // the allocation change of the parent container (slide in and slideout) doesn't trigger @@ -8062,8 +7679,6 @@ index 0000000..08707b4 + 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(); + @@ -8338,6 +7953,10 @@ index 0000000..08707b4 + } + }, + ++ getDockState: function() { ++ return this._dockState; ++ }, ++ + _show: function() { + if ((this._dockState == State.HIDDEN) || (this._dockState == State.HIDING)) { + if (this._dockState == State.HIDING) @@ -8397,6 +8016,9 @@ index 0000000..08707b4 + transition: 'easeOutQuad', + onComplete: Lang.bind(this, function() { + this._dockState = State.HIDDEN; ++ // Remove queued barried removal if any ++ if (this._removeBarrierTimeoutId > 0) ++ Mainloop.source_remove(this._removeBarrierTimeoutId); + this._updateBarrier(); + }) + }); @@ -8754,6 +8376,11 @@ index 0000000..08707b4 + }, + + _adjustLegacyTray: function() { ++ // The legacyTray has been removed in GNOME Shell 3.26. ++ // Once we drop support for previous releases this fuction can be dropped too. ++ if (!Main.legacyTray) ++ return; ++ + let use_work_area = true; + + if (this._fixedIsEnabled && !this._settings.get_boolean('extend-height') @@ -8770,6 +8397,10 @@ index 0000000..08707b4 + }, + + _resetLegacyTray: function() { ++ // The legacyTray has been removed in GNOME Shell 3.26. ++ // Once we drop support for previous releases this fuction can be dropped too. ++ if (!Main.legacyTray) ++ return; + Main.legacyTray.actor.clear_constraints(); + let constraint = new Layout.MonitorConstraint({ + primary: true, @@ -9042,8 +8673,7 @@ index 0000000..08707b4 + 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 ++ let keys = ['app-hotkey-', 'app-shift-hotkey-', 'app-ctrl-hotkey-']; + keys.forEach( function(key) { + for (let i = 0; i < this._numHotkeys; i++) { + let appNum = i; @@ -9064,8 +8694,7 @@ index 0000000..08707b4 + 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 ++ let keys = ['app-hotkey-', 'app-shift-hotkey-', 'app-ctrl-hotkey-']; + keys.forEach( function(key) { + for (let i = 0; i < this._numHotkeys; i++) + Main.wm.removeKeybinding(key + (i + 1)); @@ -9267,10 +8896,11 @@ index 0000000..08707b4 +}); + + -+const DockManager = new Lang.Class({ ++var DockManager = new Lang.Class({ + Name: 'DashToDock.DockManager', + + _init: function() { ++ this._remoteModel = new LauncherAPI.LauncherEntryRemoteModel(); + this._settings = Convenience.getSettings('org.gnome.shell.extensions.dash-to-dock'); + this._oldDash = Main.overview._dash; + /* Array of all the docks created */ @@ -9341,7 +8971,7 @@ index 0000000..08707b4 + } + + // First we create the main Dock, to get the extra features to bind to this one -+ let dock = new DockedDash(this._settings, this._preferredMonitorIndex); ++ let dock = new DockedDash(this._settings, this._remoteModel, this._preferredMonitorIndex); + this._mainShowAppsButton = dock.dash.showAppsButton; + this._allDocks.push(dock); + @@ -9359,7 +8989,7 @@ index 0000000..08707b4 + for (let iMon = 0; iMon < nMon; iMon++) { + if (iMon == this._preferredMonitorIndex) + continue; -+ let dock = new DockedDash(this._settings, iMon); ++ let dock = new DockedDash(this._settings, this._remoteModel, iMon); + this._allDocks.push(dock); + // connect app icon into the view selector + dock.dash.showAppsButton.connect('notify::checked', Lang.bind(this, this._onShowAppsButtonToggled)); @@ -9522,6 +9152,7 @@ index 0000000..08707b4 + this._deleteDocks(); + this._revertPanelCorners(); + this._restoreDash(); ++ this._remoteModel.destroy(); + }, + + /** @@ -9551,17 +9182,19 @@ index 0000000..08707b4 +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 +index 0000000..97c1dbb --- /dev/null +++ b/extensions/dash-to-dock/extension.js -@@ -0,0 +1,21 @@ +@@ -0,0 +1,23 @@ +// -*- mode: js; js-indent-level: 4; indent-tabs-mode: nil -*- + +const Me = imports.misc.extensionUtils.getCurrentExtension(); +const Docking = Me.imports.docking; +const Convenience = Me.imports.convenience; + -+let dockManager; ++// We declare this with var so it can be accessed by other extensions in ++// GNOME Shell 3.26+ (mozjs52+). ++var dockManager; + +function init() { + Convenience.initTranslations('dashtodock'); @@ -9578,7 +9211,7 @@ index 0000000..86ef700 +} diff --git a/extensions/dash-to-dock/intellihide.js b/extensions/dash-to-dock/intellihide.js new file mode 100644 -index 0000000..0d9fabd +index 0000000..1fd2699 --- /dev/null +++ b/extensions/dash-to-dock/intellihide.js @@ -0,0 +1,323 @@ @@ -9629,7 +9262,7 @@ index 0000000..0d9fabd + * Intallihide object: emit 'status-changed' signal when the overlap of windows + * with the provided targetBoxClutter.ActorBox changes; + */ -+const Intellihide = new Lang.Class({ ++var Intellihide = new Lang.Class({ + Name: 'DashToDock.Intellihide', + + _init: function(settings, monitorIndex) { @@ -9905,6 +9538,401 @@ index 0000000..0d9fabd +}); + +Signals.addSignalMethods(Intellihide.prototype); +diff --git a/extensions/dash-to-dock/launcherAPI.js b/extensions/dash-to-dock/launcherAPI.js +new file mode 100644 +index 0000000..d051a70 +--- /dev/null ++++ b/extensions/dash-to-dock/launcherAPI.js +@@ -0,0 +1,244 @@ ++// -*- mode: js; js-indent-level: 4; indent-tabs-mode: nil -*- ++ ++const Gio = imports.gi.Gio; ++const Lang = imports.lang; ++const Signals = imports.signals; ++ ++var LauncherEntryRemoteModel = new Lang.Class({ ++ Name: 'DashToDock.LauncherEntryRemoteModel', ++ ++ _init: function () { ++ this._entriesByDBusName = {}; ++ ++ this._launcher_entry_dbus_signal_id = ++ Gio.DBus.session.signal_subscribe(null, // sender ++ 'com.canonical.Unity.LauncherEntry', // iface ++ null, // member ++ null, // path ++ null, // arg0 ++ Gio.DBusSignalFlags.NONE, ++ Lang.bind(this, this._onEntrySignalReceived)); ++ ++ this._dbus_name_owner_changed_signal_id = ++ Gio.DBus.session.signal_subscribe('org.freedesktop.DBus', // sender ++ 'org.freedesktop.DBus', // interface ++ 'NameOwnerChanged', // member ++ '/org/freedesktop/DBus', // path ++ null, // arg0 ++ Gio.DBusSignalFlags.NONE, ++ Lang.bind(this, this._onDBusNameOwnerChanged)); ++ ++ this._acquireUnityDBus(); ++ }, ++ ++ destroy: function () { ++ if (this._launcher_entry_dbus_signal_id) { ++ Gio.DBus.session.signal_unsubscribe(this._launcher_entry_dbus_signal_id); ++ } ++ ++ if (this._dbus_name_owner_changed_signal_id) { ++ Gio.DBus.session.signal_unsubscribe(this._dbus_name_owner_changed_signal_id); ++ } ++ ++ this._releaseUnityDBus(); ++ }, ++ ++ size: function () { ++ return Object.keys(this._entriesByDBusName).length; ++ }, ++ ++ lookupByDBusName: function (dbusName) { ++ return this._entriesByDBusName.hasOwnProperty(dbusName) ? this._entriesByDBusName[dbusName] : null; ++ }, ++ ++ lookupById: function (appId) { ++ let ret = []; ++ for (let dbusName in this._entriesByDBusName) { ++ let entry = this._entriesByDBusName[dbusName]; ++ if (entry && entry.appId() == appId) { ++ ret.push(entry); ++ } ++ } ++ ++ return ret; ++ }, ++ ++ addEntry: function (entry) { ++ let existingEntry = this.lookupByDBusName(entry.dbusName()); ++ if (existingEntry) { ++ existingEntry.update(entry); ++ } else { ++ this._entriesByDBusName[entry.dbusName()] = entry; ++ this.emit('entry-added', entry); ++ } ++ }, ++ ++ removeEntry: function (entry) { ++ delete this._entriesByDBusName[entry.dbusName()] ++ this.emit('entry-removed', entry); ++ }, ++ ++ _acquireUnityDBus: function () { ++ if (!this._unity_bus_id) { ++ Gio.DBus.session.own_name('com.canonical.Unity', ++ Gio.BusNameOwnerFlags.ALLOW_REPLACEMENT, null, null); ++ } ++ }, ++ ++ _releaseUnityDBus: function () { ++ if (this._unity_bus_id) { ++ Gio.DBus.session.unown_name(this._unity_bus_id); ++ this._unity_bus_id = 0; ++ } ++ }, ++ ++ _onEntrySignalReceived: function (connection, sender_name, object_path, ++ interface_name, signal_name, parameters, user_data) { ++ if (!parameters || !signal_name) ++ return; ++ ++ if (signal_name == 'Update') { ++ if (!sender_name) { ++ return; ++ } ++ ++ this._handleUpdateRequest(sender_name, parameters); ++ } ++ }, ++ ++ _onDBusNameOwnerChanged: function (connection, sender_name, object_path, ++ interface_name, signal_name, parameters, user_data) { ++ if (!parameters || !this.size()) ++ return; ++ ++ let [name, before, after] = parameters.deep_unpack(); ++ ++ if (!after) { ++ if (this._entriesByDBusName.hasOwnProperty(before)) { ++ this.removeEntry(this._entriesByDBusName[before]); ++ } ++ } ++ }, ++ ++ _handleUpdateRequest: function (senderName, parameters) { ++ if (!senderName || !parameters) { ++ return; ++ } ++ ++ let [appUri, properties] = parameters.deep_unpack(); ++ let appId = appUri.replace(/(^\w+:|^)\/\//, ''); ++ let entry = this.lookupByDBusName(senderName); ++ ++ if (entry) { ++ entry.setDBusName(senderName); ++ entry.update(properties); ++ } else { ++ let entry = new LauncherEntryRemote(senderName, appId, properties); ++ this.addEntry(entry); ++ } ++ }, ++}); ++ ++Signals.addSignalMethods(LauncherEntryRemoteModel.prototype); ++ ++var LauncherEntryRemote = new Lang.Class({ ++ Name: 'DashToDock.LauncherEntryRemote', ++ ++ _init: function (dbusName, appId, properties) { ++ this._dbusName = dbusName; ++ this._appId = appId; ++ this._count = 0; ++ this._countVisible = false; ++ this._progress = 0.0; ++ this._progressVisible = false; ++ this.update(properties); ++ }, ++ ++ appId: function () { ++ return this._appId; ++ }, ++ ++ dbusName: function () { ++ return this._dbusName; ++ }, ++ ++ count: function () { ++ return this._count; ++ }, ++ ++ setCount: function (count) { ++ if (this._count != count) { ++ this._count = count; ++ this.emit('count-changed', this._count); ++ } ++ }, ++ ++ countVisible: function () { ++ return this._countVisible; ++ }, ++ ++ setCountVisible: function (countVisible) { ++ if (this._countVisible != countVisible) { ++ this._countVisible = countVisible; ++ this.emit('count-visible-changed', this._countVisible); ++ } ++ }, ++ ++ progress: function () { ++ return this._progress; ++ }, ++ ++ setProgress: function (progress) { ++ if (this._progress != progress) { ++ this._progress = progress; ++ this.emit('progress-changed', this._progress); ++ } ++ }, ++ ++ progressVisible: function () { ++ return this._progressVisible; ++ }, ++ ++ setProgressVisible: function (progressVisible) { ++ if (this._progressVisible != progressVisible) { ++ this._progressVisible = progressVisible; ++ this.emit('progress-visible-changed', this._progressVisible); ++ } ++ }, ++ ++ setDBusName: function (dbusName) { ++ if (this._dbusName != dbusName) { ++ let oldName = this._dbusName; ++ this._dbusName = dbusName; ++ this.emit('dbus-name-changed', oldName); ++ } ++ }, ++ ++ update: function (other) { ++ if (other instanceof LauncherEntryRemote) { ++ this.setDBusName(other.dbusName()) ++ this.setCount(other.count()); ++ this.setCountVisible(other.countVisible()); ++ this.setProgress(other.progress()); ++ this.setProgressVisible(other.progressVisible()) ++ } else { ++ for (let property in other) { ++ if (other.hasOwnProperty(property)) { ++ if (property == 'count') { ++ this.setCount(other[property].get_int64()); ++ } else if (property == 'count-visible') { ++ this.setCountVisible(other[property].get_boolean()); ++ } if (property == 'progress') { ++ this.setProgress(other[property].get_double()); ++ } else if (property == 'progress-visible') { ++ this.setProgressVisible(other[property].get_boolean()); ++ } else { ++ // Not implemented yet ++ } ++ } ++ } ++ } ++ }, ++}); ++ ++Signals.addSignalMethods(LauncherEntryRemote.prototype); +diff --git a/extensions/dash-to-dock/media/glossy.svg b/extensions/dash-to-dock/media/glossy.svg +new file mode 100644 +index 0000000..55b71ba +--- /dev/null ++++ b/extensions/dash-to-dock/media/glossy.svg +@@ -0,0 +1,139 @@ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ image/svg+xml ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ diff --git a/extensions/dash-to-dock/media/logo.svg b/extensions/dash-to-dock/media/logo.svg new file mode 100644 index 0000000..eebd0b1 @@ -10439,9 +10467,38 @@ index 0000000..eebd0b1 + ry="1.0583334" /> + + +diff --git a/extensions/dash-to-dock/meson.build b/extensions/dash-to-dock/meson.build +new file mode 100644 +index 0000000..e0906fa +--- /dev/null ++++ b/extensions/dash-to-dock/meson.build +@@ -0,0 +1,23 @@ ++extension_data += configure_file( ++ input: metadata_name + '.in', ++ output: metadata_name, ++ configuration: metadata_conf ++) ++ ++extension_sources += files( ++ 'appIconIndicators.js', ++ 'appIcons.js', ++ 'convenience.js', ++ 'dash.js', ++ 'docking.js', ++ 'intellihide.js', ++ 'launcherAPI.js', ++ 'prefs.js', ++ 'Settings.ui', ++ 'theming.js', ++ 'utils.js', ++ 'windowPreview.js' ++) ++extension_schemas += files(metadata_conf.get('gschemaname') + '.gschema.xml') ++ ++install_data(['media/logo.svg', 'media/glossy.svg'], install_dir: join_paths(extensiondir, uuid, 'media')) diff --git a/extensions/dash-to-dock/metadata.json.in b/extensions/dash-to-dock/metadata.json.in new file mode 100644 -index 0000000..a090272 +index 0000000..90eddb5 --- /dev/null +++ b/extensions/dash-to-dock/metadata.json.in @@ -0,0 +1,12 @@ @@ -10450,19 +10507,19 @@ index 0000000..a090272 +"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.", ++"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 +index 0000000..3e4f68a --- /dev/null +++ b/extensions/dash-to-dock/org.gnome.shell.extensions.dash-to-dock.gschema.xml -@@ -0,0 +1,713 @@ +@@ -0,0 +1,540 @@ + + + @@ -10472,7 +10529,8 @@ index 0000000..0f43bd7 + + + -+ ++ ++ + + + @@ -10492,6 +10550,22 @@ index 0000000..0f43bd7 + + + ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ + + + 'LEFT' @@ -10523,10 +10597,35 @@ index 0000000..0f43bd7 + Dash background color. + Customize the background color of the dash. + -+ ++ ++ 'DEFAULT' ++ Transparency mode for the dock ++ FIXED: constant transparency. ADAPTIVE: lock state with the top panel when not hidden. DYNAMIC: dock takes the opaque style only when windows are close to it. ++ ++ ++ 'DEFAULT' ++ ... ++ DEFAULT: .... DOTS: .... ++ ++ + false -+ Dash background is opaque -+ Makes the background of the dash opaque improving readability when in autohide mode. ++ Use application icon dominant color for the indicator color ++ ++ ++ ++ false ++ Manually set the min and max opacity ++ For Adaptive and Dynamic modes, the min/max opacity values will be given by 'min-alpha' and 'max-alpha'. ++ ++ ++ 0.2 ++ Opacity of the dash background when free-floating ++ Sets the opacity of the dash background when no windows are close. ++ ++ ++ 0.8 ++ Opacity of the dash background when windows are close. ++ Sets the opacity of the dash background when windows are close. + + + 0.8 @@ -10593,11 +10692,6 @@ index 0000000..0f43bd7 + TODO + TODO + -+ -+ false -+ TODO -+ TODO -+ + + false + Customize the style of the running application indicators. @@ -10681,11 +10775,6 @@ index 0000000..0f43bd7 + 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 @@ -10959,229 +11048,24 @@ index 0000000..0f43bd7 + 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 + ++ ++ false ++ Enable unity7 like glossy backlit items ++ Emulate the unity7 backlit glossy items behaviour ++ + + diff --git a/extensions/dash-to-dock/prefs.js b/extensions/dash-to-dock/prefs.js new file mode 100644 -index 0000000..5f1f378 +index 0000000..d8d8b94 --- /dev/null +++ b/extensions/dash-to-dock/prefs.js -@@ -0,0 +1,705 @@ +@@ -0,0 +1,868 @@ +// -*- mode: js; js-indent-level: 4; indent-tabs-mode: nil -*- + +const Gio = imports.gi.Gio; @@ -11205,6 +11089,24 @@ index 0000000..5f1f378 +const SCALE_UPDATE_TIMEOUT = 500; +const DEFAULT_ICONS_SIZES = [ 128, 96, 64, 48, 32, 24, 16 ]; + ++const TransparencyMode = { ++ DEFAULT: 0, ++ FIXED: 1, ++ ADAPTIVE: 2, ++ DYNAMIC: 3 ++}; ++ ++const RunningIndicatorStyle = { ++ DEFAULT: 0, ++ DOTS: 1, ++ SQUARES: 2, ++ DASHES: 3, ++ SEGMENTED: 4, ++ SOLID: 5, ++ CILIORA: 6, ++ METRO: 7 ++}; ++ +/** + * This function was copied from the activities-config extension + * https://github.com/nls1729/acme-code/tree/master/activities-config @@ -11262,7 +11164,16 @@ index 0000000..5f1f378 + 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'); ++ this.widget = new Gtk.ScrolledWindow({ hscrollbar_policy: Gtk.PolicyType.NEVER }); ++ this._notebook = this._builder.get_object('settings_notebook'); ++ this.widget.add(this._notebook); ++ ++ // Set a reasonable initial window height ++ this.widget.connect('realize', Lang.bind(this, function() { ++ let window = this.widget.get_toplevel(); ++ let [default_width, default_height] = window.get_default_size(); ++ window.resize(default_width, 650); ++ })); + + // Timeout to delay the update of the settings + this._dock_size_timeout = 0; @@ -11687,17 +11598,29 @@ index 0000000..5f1f378 + 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); ++ // Running indicators ++ this._builder.get_object('running_indicators_combo').set_active( ++ this._settings.get_enum('running-indicator-style') ++ ); ++ this._builder.get_object('running_indicators_combo').connect( ++ 'changed', ++ Lang.bind (this, function(widget) { ++ this._settings.set_enum('running-indicator-style', widget.get_active()); ++ }) ++ ); ++ ++ if (this._settings.get_enum('running-indicator-style') == RunningIndicatorStyle.DEFAULT) ++ this._builder.get_object('running_indicators_advance_settings_button').set_sensitive(false); ++ ++ this._settings.connect('changed::running-indicator-style', Lang.bind(this, function() { ++ if (this._settings.get_enum('running-indicator-style') == RunningIndicatorStyle.DEFAULT) ++ this._builder.get_object('running_indicators_advance_settings_button').set_sensitive(false); ++ else ++ this._builder.get_object('running_indicators_advance_settings_button').set_sensitive(true); ++ })); + -+ // Create dialog for running dots advanced settings -+ this._builder.get_object('running_dots_advance_settings_button').connect('clicked', Lang.bind(this, function() { ++ // Create dialog for running indicators advanced settings ++ this._builder.get_object('running_indicators_advance_settings_button').connect('clicked', Lang.bind(this, function() { + + let dialog = new Gtk.Dialog({ title: __('Customize running indicators'), + transient_for: this.widget.get_toplevel(), @@ -11707,6 +11630,11 @@ index 0000000..5f1f378 + let box = this._builder.get_object('running_dots_advance_settings_box'); + dialog.get_content_area().add(box); + ++ this._settings.bind('running-indicator-dominant-color', ++ this._builder.get_object('dominant_color_switch'), ++ 'active', ++ Gio.SettingsBindFlags.DEFAULT); ++ + this._settings.bind('custom-theme-customize-running-dots', + this._builder.get_object('dot_style_switch'), + 'active', @@ -11726,21 +11654,121 @@ index 0000000..5f1f378 + 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); ++ rgba.parse(this._settings.get_string('custom-theme-running-dots-border-color')); ++ this._builder.get_object('dot_border_color_colorbutton').set_rgba(rgba); ++ ++ this._builder.get_object('dot_border_color_colorbutton').connect('notify::color', Lang.bind(this, function(button) { ++ let rgba = button.get_rgba(); ++ let css = rgba.to_string(); ++ let hexString = cssHexString(css); ++ this._settings.set_string('custom-theme-running-dots-border-color', hexString); ++ })); ++ ++ this._settings.bind('custom-theme-running-dots-border-width', ++ this._builder.get_object('dot_border_width_spin_button'), ++ 'value', ++ Gio.SettingsBindFlags.DEFAULT); ++ ++ ++ dialog.connect('response', Lang.bind(this, function(dialog, id) { ++ // remove the settings box so it doesn't get destroyed; ++ dialog.get_content_area().remove(box); ++ dialog.destroy(); ++ return; ++ })); ++ ++ dialog.show_all(); ++ ++ })); ++ ++ this._settings.bind('custom-background-color', this._builder.get_object('custom_background_color_switch'), 'active', Gio.SettingsBindFlags.DEFAULT); ++ this._settings.bind('custom-background-color', this._builder.get_object('custom_background_color'), 'sensitive', Gio.SettingsBindFlags.DEFAULT); ++ ++ let rgba = new Gdk.RGBA(); ++ rgba.parse(this._settings.get_string('background-color')); ++ this._builder.get_object('custom_background_color').set_rgba(rgba); ++ ++ this._builder.get_object('custom_background_color').connect('notify::color', Lang.bind(this, function(button) { ++ let rgba = button.get_rgba(); ++ let css = rgba.to_string(); ++ let hexString = cssHexString(css); ++ this._settings.set_string('background-color', hexString); ++ })); ++ ++ // Opacity ++ this._builder.get_object('customize_opacity_combo').set_active( ++ this._settings.get_enum('transparency-mode') ++ ); ++ this._builder.get_object('customize_opacity_combo').connect( ++ 'changed', ++ Lang.bind (this, function(widget) { ++ this._settings.set_enum('transparency-mode', widget.get_active()); ++ }) ++ ); ++ ++ this._builder.get_object('custom_opacity_scale').set_value(this._settings.get_double('background-opacity')); ++ ++ if (this._settings.get_enum('transparency-mode') !== TransparencyMode.FIXED) ++ this._builder.get_object('custom_opacity_scale').set_sensitive(false); ++ ++ this._settings.connect('changed::transparency-mode', Lang.bind(this, function() { ++ if (this._settings.get_enum('transparency-mode') !== TransparencyMode.FIXED) ++ this._builder.get_object('custom_opacity_scale').set_sensitive(false); ++ else ++ this._builder.get_object('custom_opacity_scale').set_sensitive(true); ++ })); ++ ++ if (this._settings.get_enum('transparency-mode') !== TransparencyMode.ADAPTIVE && ++ this._settings.get_enum('transparency-mode') !== TransparencyMode.DYNAMIC) { ++ this._builder.get_object('dynamic_opacity_button').set_sensitive(false); ++ } ++ ++ this._settings.connect('changed::transparency-mode', Lang.bind(this, function() { ++ if (this._settings.get_enum('transparency-mode') !== TransparencyMode.ADAPTIVE && ++ this._settings.get_enum('transparency-mode') !== TransparencyMode.DYNAMIC) { ++ this._builder.get_object('dynamic_opacity_button').set_sensitive(false); ++ } ++ else { ++ this._builder.get_object('dynamic_opacity_button').set_sensitive(true); ++ } ++ })); ++ ++ // Create dialog for transparency advanced settings ++ this._builder.get_object('dynamic_opacity_button').connect('clicked', Lang.bind(this, function() { + -+ 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); -+ })); ++ let dialog = new Gtk.Dialog({ title: __('Cutomize opacity'), ++ transient_for: this.widget.get_toplevel(), ++ use_header_bar: true, ++ modal: true }); + -+ this._settings.bind('custom-theme-running-dots-border-width', -+ this._builder.get_object('dot_border_width_spin_button'), -+ 'value', -+ Gio.SettingsBindFlags.DEFAULT); ++ let box = this._builder.get_object('advanced_transparency_dialog'); ++ dialog.get_content_area().add(box); ++ ++ this._settings.bind( ++ 'customize-alphas', ++ this._builder.get_object('customize_alphas_switch'), ++ 'active', ++ Gio.SettingsBindFlags.DEFAULT ++ ); ++ this._settings.bind( ++ 'customize-alphas', ++ this._builder.get_object('min_alpha_scale'), ++ 'sensitive', ++ Gio.SettingsBindFlags.DEFAULT ++ ); ++ this._settings.bind( ++ 'customize-alphas', ++ this._builder.get_object('max_alpha_scale'), ++ 'sensitive', ++ Gio.SettingsBindFlags.DEFAULT ++ ); + ++ this._builder.get_object('min_alpha_scale').set_value( ++ this._settings.get_double('min-alpha') ++ ); ++ this._builder.get_object('max_alpha_scale').set_value( ++ this._settings.get_double('max-alpha') ++ ); + + dialog.connect('response', Lang.bind(this, function(dialog, id) { + // remove the settings box so it doesn't get destroyed; @@ -11750,26 +11778,13 @@ index 0000000..5f1f378 + })); + + 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('unity-backlit-items', ++ this._builder.get_object('unity_backlit_items_switch'), ++ 'active', Gio.SettingsBindFlags.DEFAULT ++ ); + + this._settings.bind('force-straight-corner', + this._builder.get_object('force_straight_corner_switch'), @@ -11856,10 +11871,42 @@ index 0000000..5f1f378 + })); + }, + ++ min_opacity_scale_value_changed_cb: function(scale) { ++ // Avoid settings the opacity consinuosly as it's change is animated ++ if (this._opacity_timeout > 0) ++ Mainloop.source_remove(this._opacity_timeout); ++ ++ this._opacity_timeout = Mainloop.timeout_add(SCALE_UPDATE_TIMEOUT, Lang.bind(this, function() { ++ this._settings.set_double('min-alpha', scale.get_value()); ++ this._opacity_timeout = 0; ++ return GLib.SOURCE_REMOVE; ++ })); ++ }, ++ ++ max_opacity_scale_value_changed_cb: function(scale) { ++ // Avoid settings the opacity consinuosly as it's change is animated ++ if (this._opacity_timeout > 0) ++ Mainloop.source_remove(this._opacity_timeout); ++ ++ this._opacity_timeout = Mainloop.timeout_add(SCALE_UPDATE_TIMEOUT, Lang.bind(this, function() { ++ this._settings.set_double('max-alpha', scale.get_value()); ++ this._opacity_timeout = 0; ++ return GLib.SOURCE_REMOVE; ++ })); ++ }, ++ + custom_opacity_scale_format_value_cb: function(scale, value) { + return Math.round(value*100) + ' %'; + }, + ++ min_opacity_scale_format_value_cb: function(scale, value) { ++ return Math.round(value*100) + ' %'; ++ }, ++ ++ max_opacity_scale_format_value_cb: function(scale, value) { ++ return Math.round(value*100) + ' %'; ++ }, ++ + all_windows_radio_button_toggled_cb: function(button) { + if (button.get_active()) + this._settings.set_enum('intellihide-mode', 0); @@ -12004,10 +12051,10 @@ index 0000000..6e9bf38 +} diff --git a/extensions/dash-to-dock/theming.js b/extensions/dash-to-dock/theming.js new file mode 100644 -index 0000000..0a306b4 +index 0000000..4b18d1a --- /dev/null +++ b/extensions/dash-to-dock/theming.js -@@ -0,0 +1,293 @@ +@@ -0,0 +1,672 @@ +// -*- mode: js; js-indent-level: 4; indent-tabs-mode: nil -*- + +const Clutter = imports.gi.Clutter; @@ -12033,24 +12080,42 @@ index 0000000..0a306b4 +const Workspace = imports.ui.workspace; + +const Me = imports.misc.extensionUtils.getCurrentExtension(); ++const Dock = Me.imports.docking; +const Utils = Me.imports.utils; + ++/* ++ * DEFAULT: transparency given by theme ++ * FIXED: constant transparency chosen by user ++ * ADAPTIVE: apply 'transparent' style to dock AND panel when ++ * no windows are close to the dock OR panel. ++ * When dock is hidden, the dock 'transparent' style only ++ * apply to itself. ++ * DYNAMIC: apply 'transparent' style when no windows are close to the dock ++ * */ ++const TransparencyMode = { ++ DEFAULT: 0, ++ FIXED: 1, ++ ADAPTIVE: 2, ++ DYNAMIC: 3 ++}; ++ +/** + * Manage theme customization and custom theme support + */ -+const ThemeManager = new Lang.Class({ ++var ThemeManager = new Lang.Class({ + Name: 'DashToDock.ThemeManager', + -+ _init: function(settings, actor, dash) { ++ _init: function(settings, dock) { + this._settings = settings; + this._signalsHandler = new Utils.GlobalSignalsHandler(); + this._bindSettingsChanges(); -+ this._actor = actor; -+ this._dash = dash; ++ this._actor = dock.actor; ++ this._dash = dock.dash; + + // initialize colors with generic values + this._customizedBackground = {red: 0, green: 0, blue: 0, alpha: 0}; + this._customizedBorder = {red: 0, green: 0, blue: 0, alpha: 0}; ++ this._transparency = new Transparency(this._settings, dock); + + this._signalsHandler.add([ + // When theme changes re-obtain default background color @@ -12078,6 +12143,7 @@ index 0000000..0a306b4 + + destroy: function() { + this._signalsHandler.destroy(); ++ this._transparency.destroy(); + }, + + _onOverviewShowing: function() { @@ -12149,25 +12215,32 @@ index 0000000..0a306b4 + }, + + _updateDashColor: function() { -+ if (this._settings.get_boolean('custom-background-color')) { -+ let [backgroundColor, borderColor] = this._getDefaultColors(); ++ // Retrieve the color. If needed we will adjust it before passing it to ++ // this._transparency. ++ let [backgroundColor, borderColor] = this._getDefaultColors(); + -+ if (backgroundColor==null) -+ return; ++ if (backgroundColor==null) ++ return; + ++ if (this._settings.get_boolean('custom-background-color')) { ++ // When applying a custom color, we need to check the alpha value, ++ // if not the opacity will always be overridden by the color below. ++ // Note that if using 'adaptive' or 'dynamic' transparency modes, ++ // the opacity will be set by the opaque/transparent styles anyway. + let newAlpha = Math.round(backgroundColor.alpha/2.55)/100; -+ if (this._settings.get_boolean('opaque-background')) ++ if (this._settings.get_enum('transparency-mode') == TransparencyMode.FIXED) + newAlpha = this._settings.get_double('background-opacity'); + -+ let newColor = Clutter.color_from_string(this._settings.get_string('background-color'))[1]; ++ backgroundColor = Clutter.color_from_string(this._settings.get_string('background-color'))[1]; + this._customizedBackground = 'rgba(' + -+ newColor.red + ',' + -+ newColor.green + ',' + -+ newColor.blue + ',' + ++ backgroundColor.red + ',' + ++ backgroundColor.green + ',' + ++ backgroundColor.blue + ',' + + newAlpha + ')'; + + this._customizedBorder = this._customizedBackground; + } ++ this._transparency.setColor(backgroundColor); + }, + + _updateCustomStyleClasses: function() { @@ -12181,7 +12254,7 @@ index 0000000..0a306b4 + else + this._actor.remove_style_class_name('shrink'); + -+ if (this._settings.get_boolean('custom-theme-running-dots')) ++ if (this._settings.get_enum('running-indicator-style') !== 0) + this._actor.add_style_class_name('running-dots'); + else + this._actor.remove_style_class_name('running-dots'); @@ -12190,7 +12263,7 @@ index 0000000..0a306b4 + 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 ++ else + this._actor.remove_style_class_name('straight-corner'); + } else { + this._actor.remove_style_class_name('straight-corner'); @@ -12216,6 +12289,7 @@ index 0000000..0a306b4 + + // Remove prior style edits + this._dash._container.set_style(null); ++ this._transparency.disable(); + + // If built-in theme is enabled do nothing else + if (this._settings.get_boolean('apply-custom-theme')) @@ -12273,7 +12347,12 @@ index 0000000..0a306b4 + } + + // Customize background -+ if (this._settings.get_boolean('opaque-background') || this._settings.get_boolean('custom-background-color')) { ++ let fixedTransparency = this._settings.get_enum('transparency-mode') == TransparencyMode.FIXED; ++ let defaultTransparency = this._settings.get_enum('transparency-mode') == TransparencyMode.DEFAULT; ++ if (!defaultTransparency && !fixedTransparency) { ++ this._transparency.enable(); ++ } ++ else if (!defaultTransparency || this._settings.get_boolean('custom-background-color')) { + newStyle = newStyle + 'background-color:'+ this._customizedBackground + '; ' + + 'border-color:'+ this._customizedBorder + '; ' + + 'transition-delay: 0s; transition-duration: 0.250s;'; @@ -12282,7 +12361,10 @@ index 0000000..0a306b4 + }, + + _bindSettingsChanges: function() { -+ let keys = ['opaque-background', ++ let keys = ['transparency-mode', ++ 'customize-alphas', ++ 'min-alpha', ++ 'max-alpha', + 'background-opacity', + 'custom-background-color', + 'background-color', @@ -12301,12 +12383,356 @@ index 0000000..0a306b4 + }, this); + } +}); ++ ++/** ++ * The following class is based on the following upstream commit: ++ * https://git.gnome.org/browse/gnome-shell/commit/?id=447bf55e45b00426ed908b1b1035f472c2466956 ++ * Transparency when free-floating ++ */ ++const Transparency = new Lang.Class({ ++ Name: 'DashToDock.Transparency', ++ ++ _init: function(settings, dock) { ++ this._settings = settings; ++ this._dash = dock.dash; ++ this._actor = this._dash._container; ++ this._dockActor = dock.actor; ++ this._dock = dock; ++ this._panel = Main.panel; ++ this._position = Utils.getPosition(this._settings); ++ ++ this._backgroundColor = '0,0,0'; ++ this._transparentAlpha = '0.2'; ++ this._opaqueAlpha = '1'; ++ this._transparentAlphaBorder = '0.1'; ++ this._opaqueAlphaBorder = '0.5'; ++ this._transparentTransition = '0ms'; ++ this._opaqueTransition = '0ms'; ++ ++ this._updateStyles(); ++ ++ this._signalsHandler = new Utils.GlobalSignalsHandler(); ++ this._injectionsHandler = new Utils.InjectionsHandler(); ++ this._trackedWindows = new Map(); ++ }, ++ ++ enable: function() { ++ // ensure I never double-register/inject ++ // although it should never happen ++ this.disable(); ++ ++ this._signalsHandler.addWithLabel('transparency', [ ++ global.window_group, ++ 'actor-added', ++ Lang.bind(this, this._onWindowActorAdded) ++ ], [ ++ global.window_group, ++ 'actor-removed', ++ Lang.bind(this, this._onWindowActorRemoved) ++ ], [ ++ global.window_manager, ++ 'switch-workspace', ++ Lang.bind(this, this._updateSolidStyle) ++ ], [ ++ Main.overview, ++ 'hiding', ++ Lang.bind(this, this._updateSolidStyle) ++ ], [ ++ Main.overview, ++ 'showing', ++ Lang.bind(this, this._updateSolidStyle) ++ ]); ++ ++ // Window signals ++ global.get_window_actors().forEach(function(win) { ++ // An irrelevant window actor ('Gnome-shell') produces an error when the signals are ++ // disconnected, therefore do not add signals to it. ++ if (win.get_meta_window().get_wm_class() !== 'Gnome-shell') ++ this._onWindowActorAdded(null, win); ++ }, this); ++ ++ if (this._settings.get_enum('transparency-mode') === TransparencyMode.ADAPTIVE) ++ this._enableAdaptive(); ++ ++ if (this._actor.get_stage()) ++ this._updateSolidStyle(); ++ ++ this.emit('transparency-enabled'); ++ }, ++ ++ disable: function() { ++ this._disableAdaptive(); ++ ++ // ensure I never double-register/inject ++ // although it should never happen ++ this._signalsHandler.removeWithLabel('transparency'); ++ ++ for (let key of this._trackedWindows.keys()) ++ this._trackedWindows.get(key).forEach(id => { ++ key.disconnect(id); ++ }); ++ this._trackedWindows.clear(); ++ ++ this.emit('transparency-disabled'); ++ }, ++ ++ destroy: function() { ++ this.disable(); ++ this._signalsHandler.destroy(); ++ this._injectionsHandler.destroy(); ++ }, ++ ++ _onWindowActorAdded: function(container, metaWindowActor) { ++ let signalIds = []; ++ ['allocation-changed', 'notify::visible'].forEach(s => { ++ signalIds.push(metaWindowActor.connect(s, Lang.bind(this, this._updateSolidStyle))); ++ }); ++ this._trackedWindows.set(metaWindowActor, signalIds); ++ }, ++ ++ _onWindowActorRemoved: function(container, metaWindowActor) { ++ if (!this._trackedWindows.get(metaWindowActor)) ++ return; ++ ++ this._trackedWindows.get(metaWindowActor).forEach(id => { ++ metaWindowActor.disconnect(id); ++ }); ++ this._trackedWindows.delete(metaWindowActor); ++ this._updateSolidStyle(); ++ }, ++ ++ _updateSolidStyle: function() { ++ let isNear = this._dockIsNear() || this._panelIsNear(); ++ if (isNear) { ++ this._actor.set_style(this._opaque_style); ++ if (this._panel._updateSolidStyle && this._adaptiveEnabled) { ++ if (this._settings.get_boolean('dock-fixed') || this._panelIsNear()) ++ this._panel._addStyleClassName('solid'); ++ else ++ this._panel._removeStyleClassName('solid'); ++ } ++ } ++ else { ++ this._actor.set_style(this._transparent_style); ++ if (this._panel._updateSolidStyle && this._adaptiveEnabled) ++ this._panel._removeStyleClassName('solid'); ++ } ++ ++ this.emit('solid-style-updated', isNear); ++ }, ++ ++ _dockIsNear: function() { ++ if (this._dockActor.has_style_pseudo_class('overview')) ++ return false; ++ /* Get all the windows in the active workspace that are in the primary monitor and visible */ ++ let activeWorkspace = global.screen.get_active_workspace(); ++ let dash = this._dash; ++ let windows = activeWorkspace.list_windows().filter(function(metaWindow) { ++ return metaWindow.get_monitor() === dash._monitorIndex && ++ metaWindow.showing_on_its_workspace() && ++ metaWindow.get_window_type() != Meta.WindowType.DESKTOP; ++ }); ++ ++ /* Check if at least one window is near enough to the panel. ++ * If the dock is hidden, we need to account for the space it would take ++ * up when it slides out. This is avoid an ugly transition. ++ * */ ++ let factor = 0; ++ if (!this._settings.get_boolean('dock-fixed') && ++ this._dock.getDockState() == Dock.State.HIDDEN) ++ factor = 1; ++ let [leftCoord, topCoord] = this._actor.get_transformed_position(); ++ let threshold; ++ if (this._position === St.Side.LEFT) ++ threshold = leftCoord + this._actor.get_width() * (factor + 1); ++ else if (this._position === St.Side.RIGHT) ++ threshold = leftCoord - this._actor.get_width() * factor; ++ else if (this._position === St.Side.TOP) ++ threshold = topCoord + this._actor.get_height() * (factor + 1); ++ else ++ threshold = topCoord - this._actor.get_height() * factor; ++ ++ let scale = St.ThemeContext.get_for_stage(global.stage).scale_factor; ++ let isNearEnough = windows.some(Lang.bind(this, function(metaWindow) { ++ let coord; ++ if (this._position === St.Side.LEFT) { ++ coord = metaWindow.get_frame_rect().x; ++ return coord < threshold + 5 * scale; ++ } ++ else if (this._position === St.Side.RIGHT) { ++ coord = metaWindow.get_frame_rect().x + metaWindow.get_frame_rect().width; ++ return coord > threshold - 5 * scale; ++ } ++ else if (this._position === St.Side.TOP) { ++ coord = metaWindow.get_frame_rect().y; ++ return coord < threshold + 5 * scale; ++ } ++ else { ++ coord = metaWindow.get_frame_rect().y + metaWindow.get_frame_rect().height; ++ return coord > threshold - 5 * scale; ++ } ++ })); ++ ++ return isNearEnough; ++ }, ++ ++ _panelIsNear: function() { ++ if (!this._panel._updateSolidStyle || ++ this._settings.get_enum('transparency-mode') !== TransparencyMode.ADAPTIVE) ++ return false; ++ ++ if (this._panel.actor.has_style_pseudo_class('overview') || !Main.sessionMode.hasWindows) { ++ this._panel._removeStyleClassName('solid'); ++ return false; ++ } ++ ++ /* Get all the windows in the active workspace that are in the ++ * primary monitor and visible */ ++ let activeWorkspace = global.screen.get_active_workspace(); ++ let windows = activeWorkspace.list_windows().filter(function(metaWindow) { ++ return metaWindow.is_on_primary_monitor() && ++ metaWindow.showing_on_its_workspace() && ++ metaWindow.get_window_type() != Meta.WindowType.DESKTOP; ++ }); ++ ++ /* Check if at least one window is near enough to the panel */ ++ let [, panelTop] = this._panel.actor.get_transformed_position(); ++ let panelBottom = panelTop + this._panel.actor.get_height(); ++ let scale = St.ThemeContext.get_for_stage(global.stage).scale_factor; ++ let isNearEnough = windows.some(Lang.bind(this._panel, function(metaWindow) { ++ let verticalPosition = metaWindow.get_frame_rect().y; ++ return verticalPosition < panelBottom + 5 * scale; ++ })); ++ ++ return isNearEnough; ++ }, ++ ++ _updateStyles: function() { ++ this._getAlphas(); ++ ++ this._transparent_style = ++ 'background-color: rgba(' + ++ this._backgroundColor + ', ' + this._transparentAlpha + ');' + ++ 'border-color: rgba(' + ++ this._backgroundColor + ', ' + this._transparentAlphaBorder + ');' + ++ 'transition-duration: ' + this._transparentTransition + 'ms;'; ++ ++ this._opaque_style = ++ 'background-color: rgba(' + ++ this._backgroundColor + ', ' + this._opaqueAlpha + ');' + ++ 'border-color: rgba(' + ++ this._backgroundColor + ',' + this._opaqueAlphaBorder + ');' + ++ 'transition-duration: ' + this._opaqueTransition + 'ms;'; ++ ++ this.emit('styles-updated'); ++ }, ++ ++ setColor: function(color) { ++ this._backgroundColor = color.red + ',' + color.green + ',' + color.blue; ++ this._updateStyles(); ++ }, ++ ++ _getAlphas: function() { ++ // Create dummy object and add to the uiGroup to get it to the stage ++ let dummyObject = new St.Bin({ ++ name: 'dashtodockContainer', ++ }); ++ Main.uiGroup.add_child(dummyObject); ++ ++ dummyObject.add_style_class_name('opaque'); ++ let themeNode = dummyObject.get_theme_node(); ++ this._opaqueAlpha = themeNode.get_background_color().alpha / 255; ++ this._opaqueAlphaBorder = themeNode.get_border_color(0).alpha / 255; ++ this._opaqueTransition = themeNode.get_transition_duration(); ++ ++ dummyObject.add_style_class_name('transparent'); ++ themeNode = dummyObject.get_theme_node(); ++ this._transparentAlpha = themeNode.get_background_color().alpha / 255; ++ this._transparentAlphaBorder = themeNode.get_border_color(0).alpha / 255; ++ this._transparentTransition = themeNode.get_transition_duration(); ++ ++ Main.uiGroup.remove_child(dummyObject); ++ ++ if (this._settings.get_boolean('customize-alphas')) { ++ this._opaqueAlpha = this._settings.get_double('max-alpha'); ++ this._opaqueAlphaBorder = this._opaqueAlpha / 2; ++ this._transparentAlpha = this._settings.get_double('min-alpha'); ++ this._transparentAlphaBorder = this._transparentAlpha / 2; ++ } ++ ++ if (this._settings.get_enum('transparency-mode') === TransparencyMode.ADAPTIVE && ++ this._panel._updateSolidStyle) { ++ themeNode = this._panel.actor.get_theme_node(); ++ if (this._panel.actor.has_style_class_name('solid')) { ++ this._opaqueTransition = themeNode.get_transition_duration(); ++ this._panel._removeStyleClassName('solid'); ++ themeNode = this._panel.actor.get_theme_node(); ++ this._transparentTransition = themeNode.get_transition_duration(); ++ this._panel._addStyleClassName('solid'); ++ } ++ else { ++ this._transparentTransition = themeNode.get_transition_duration(); ++ this._panel._addStyleClassName('solid'); ++ themeNode = this._panel.actor.get_theme_node(); ++ this._opaqueTransition = themeNode.get_transition_duration(); ++ this._panel._removeStyleClassName('solid'); ++ } ++ } ++ }, ++ ++ _enableAdaptive: function() { ++ if (!this._panel._updateSolidStyle || ++ this._dash._monitorIndex !== Main.layoutManager.primaryIndex) ++ return; ++ ++ this._adaptiveEnabled = true; ++ ++ function UpdateSolidStyle() { ++ return; ++ } ++ ++ this._injectionsHandler.addWithLabel('adaptive', [ ++ this._panel, ++ '_updateSolidStyle', ++ UpdateSolidStyle ++ ]); ++ ++ // Once we injected the new function, we need to disconnect and ++ // reconnect all window signals. ++ for (let key of this._panel._trackedWindows.keys()) ++ this._panel._trackedWindows.get(key).forEach(id => { ++ key.disconnect(id); ++ }); ++ ++ for (let win of this._panel._trackedWindows.keys()) ++ this._panel._onWindowActorAdded(null, win); ++ }, ++ ++ _disableAdaptive: function() { ++ if (!this._adaptiveEnabled) ++ return; ++ ++ this._injectionsHandler.removeWithLabel('adaptive'); ++ this._adaptiveEnabled = false; ++ ++ // Once we removed the injection, we need to disconnect and ++ // reconnect all window signals. ++ for (let key of this._panel._trackedWindows.keys()) ++ this._panel._trackedWindows.get(key).forEach(id => { ++ key.disconnect(id); ++ }); ++ ++ for (let win of this._panel._trackedWindows.keys()) ++ this._panel._onWindowActorAdded(null, win); ++ } ++}); ++Signals.addSignalMethods(Transparency.prototype); diff --git a/extensions/dash-to-dock/utils.js b/extensions/dash-to-dock/utils.js new file mode 100644 -index 0000000..b98fe45 +index 0000000..6514649 --- /dev/null +++ b/extensions/dash-to-dock/utils.js -@@ -0,0 +1,123 @@ +@@ -0,0 +1,255 @@ +const Clutter = imports.gi.Clutter; +const Lang = imports.lang; +const St = imports.gi.St; @@ -12340,7 +12766,8 @@ index 0000000..b98fe45 + + // Skip first element of the arguments + for (let i = 1; i < arguments.length; i++) { -+ this._storage[label].push( this._create(arguments[i])); ++ let item = this._storage[label]; ++ item.push(this._create(arguments[i])); + } + }, + @@ -12373,7 +12800,7 @@ index 0000000..b98fe45 +/** + * Manage global signals + */ -+const GlobalSignalsHandler = new Lang.Class({ ++var GlobalSignalsHandler = new Lang.Class({ + Name: 'DashToDock.GlobalSignalHandler', + Extends: BasicHandler, + @@ -12392,10 +12819,108 @@ index 0000000..b98fe45 +}); + +/** ++ * Color manipulation utilities ++ */ ++var ColorUtils = { ++ ++ // Darken or brigthen color by a fraction dlum ++ // Each rgb value is modified by the same fraction. ++ // Return "#rrggbb" string ++ ColorLuminance: function(r, g, b, dlum) { ++ let rgbString = '#'; ++ ++ rgbString += Math.round(Math.min(Math.max(r*(1+dlum), 0), 255)).toString(16); ++ rgbString += Math.round(Math.min(Math.max(g*(1+dlum), 0), 255)).toString(16); ++ rgbString += Math.round(Math.min(Math.max(b*(1+dlum), 0), 255)).toString(16); ++ ++ return rgbString; ++ }, ++ ++ // Convert hsv ([0-1, 0-1, 0-1]) to rgb ([0-255, 0-255, 0-255]). ++ // Following algorithm in https://en.wikipedia.org/wiki/HSL_and_HSV ++ // here with h = [0,1] instead of [0, 360] ++ // Accept either (h,s,v) independently or {h:h, s:s, v:v} object. ++ // Return {r:r, g:g, b:b} object. ++ HSVtoRGB: function(h, s, v) { ++ if (arguments.length === 1) { ++ s = h.s; ++ v = h.v; ++ h = h.h; ++ } ++ ++ let r,g,b; ++ let c = v*s; ++ let h1 = h*6; ++ let x = c*(1 - Math.abs(h1 % 2 - 1)); ++ let m = v - c; ++ ++ if (h1 <=1) ++ r = c + m, g = x + m, b = m; ++ else if (h1 <=2) ++ r = x + m, g = c + m, b = m; ++ else if (h1 <=3) ++ r = m, g = c + m, b = x + m; ++ else if (h1 <=4) ++ r = m, g = x + m, b = c + m; ++ else if (h1 <=5) ++ r = x + m, g = m, b = c + m; ++ else ++ r = c + m, g = m, b = x + m; ++ ++ return { ++ r: Math.round(r * 255), ++ g: Math.round(g * 255), ++ b: Math.round(b * 255) ++ }; ++ }, ++ ++ // Convert rgb ([0-255, 0-255, 0-255]) to hsv ([0-1, 0-1, 0-1]). ++ // Following algorithm in https://en.wikipedia.org/wiki/HSL_and_HSV ++ // here with h = [0,1] instead of [0, 360] ++ // Accept either (r,g,b) independently or {r:r, g:g, b:b} object. ++ // Return {h:h, s:s, v:v} object. ++ RGBtoHSV: function (r, g, b) { ++ if (arguments.length === 1) { ++ r = r.r; ++ g = r.g; ++ b = r.b; ++ } ++ ++ let h,s,v; ++ ++ let M = Math.max(r, g, b); ++ let m = Math.min(r, g, b); ++ let c = M - m; ++ ++ if (c == 0) ++ h = 0; ++ else if (M == r) ++ h = ((g-b)/c) % 6; ++ else if (M == g) ++ h = (b-r)/c + 2; ++ else ++ h = (r-g)/c + 4; ++ ++ h = h/6; ++ v = M/255; ++ if (M !== 0) ++ s = c/M; ++ else ++ s = 0; ++ ++ return { ++ h: h, ++ s: s, ++ v: v ++ }; ++ } ++}; ++ ++/** + * Manage function injection: both instances and prototype can be overridden + * and restored + */ -+const InjectionsHandler = new Lang.Class({ ++var InjectionsHandler = new Lang.Class({ + Name: 'DashToDock.InjectionsHandler', + Extends: BasicHandler, + @@ -12430,12 +12955,45 @@ index 0000000..b98fe45 + } + return position; +} ++ ++function drawRoundedLine(cr, x, y, width, height, isRoundLeft, isRoundRight, stroke, fill) { ++ if (height > width) { ++ y += Math.floor((height - width) / 2.0); ++ height = width; ++ } ++ ++ height = 2.0 * Math.floor(height / 2.0); ++ ++ var leftRadius = isRoundLeft ? height / 2.0 : 0.0; ++ var rightRadius = isRoundRight ? height / 2.0 : 0.0; ++ ++ cr.moveTo(x + width - rightRadius, y); ++ cr.lineTo(x + leftRadius, y); ++ if (isRoundLeft) ++ cr.arcNegative(x + leftRadius, y + leftRadius, leftRadius, -Math.PI/2, Math.PI/2); ++ else ++ cr.lineTo(x, y + height); ++ cr.lineTo(x + width - rightRadius, y + height); ++ if (isRoundRight) ++ cr.arcNegative(x + width - rightRadius, y + rightRadius, rightRadius, Math.PI/2, -Math.PI/2); ++ else ++ cr.lineTo(x + width, y); ++ cr.closePath(); ++ ++ if (fill != null) { ++ cr.setSource(fill); ++ cr.fillPreserve(); ++ } ++ if (stroke != null) ++ cr.setSource(stroke); ++ cr.stroke(); ++} diff --git a/extensions/dash-to-dock/windowPreview.js b/extensions/dash-to-dock/windowPreview.js new file mode 100644 -index 0000000..4f84b4d +index 0000000..4b99aa8 --- /dev/null +++ b/extensions/dash-to-dock/windowPreview.js -@@ -0,0 +1,595 @@ +@@ -0,0 +1,630 @@ +/* + * Credits: + * This file is based on code from the Dash to Panel extension by Jason DeRose @@ -12539,8 +13097,8 @@ index 0000000..4f84b4d + this.parent(); + + this.actor = new St.ScrollView({ name: 'dashtodockWindowScrollview', -+ hscrollbar_policy: Gtk.PolicyType.AUTOMATIC, -+ vscrollbar_policy: Gtk.PolicyType.AUTOMATIC, ++ hscrollbar_policy: Gtk.PolicyType.NEVER, ++ vscrollbar_policy: Gtk.PolicyType.NEVER, + enable_mouse_scrolling: true }); + + this.actor.connect('scroll-event', Lang.bind(this, this._onScrollEvent )); @@ -12745,6 +13303,39 @@ index 0000000..4f84b4d + + if (newWin.length < 1) + this._getTopMenu().close(~0); ++ ++ // As for upstream: ++ // St.ScrollView always requests space horizontally for a possible vertical ++ // scrollbar if in AUTOMATIC mode. Doing better would require implementation ++ // of width-for-height in St.BoxLayout and St.ScrollView. This looks bad ++ // when we *don't* need it, so turn off the scrollbar when that's true. ++ // Dynamic changes in whether we need it aren't handled properly. ++ let needsScrollbar = this._needsScrollbar(); ++ let scrollbar_policy = needsScrollbar ? Gtk.PolicyType.AUTOMATIC : Gtk.PolicyType.NEVER; ++ if (this.isHorizontal) ++ this.actor.hscrollbar_policy = scrollbar_policy; ++ else ++ this.actor.vscrollbar_policy = scrollbar_policy; ++ ++ if (needsScrollbar) ++ this.actor.add_style_pseudo_class('scrolled'); ++ else ++ this.actor.remove_style_pseudo_class('scrolled'); ++ }, ++ ++ _needsScrollbar: function() { ++ let topMenu = this._getTopMenu(); ++ let topThemeNode = topMenu.actor.get_theme_node(); ++ if (this.isHorizontal) { ++ let [topMinWidth, topNaturalWidth] = topMenu.actor.get_preferred_width(-1); ++ let topMaxWidth = topThemeNode.get_max_width(); ++ return topMaxWidth >= 0 && topNaturalWidth >= topMaxWidth; ++ } else { ++ let [topMinHeight, topNaturalHeight] = topMenu.actor.get_preferred_height(-1); ++ let topMaxHeight = topThemeNode.get_max_height(); ++ return topMaxHeight >= 0 && topNaturalHeight >= topMaxHeight; ++ } ++ + }, + + isAnimatingOut: function() { @@ -12774,15 +13365,18 @@ index 0000000..4f84b4d + this._window = window; + this._destroyId = 0; + this._windowAddedId = 0; -+ params = Params.parse(params, { style_class: 'app-well-preview-menu-item' }); + this.parent(params); + ++ // We don't want this: it adds spacing on the left of the item. ++ this.actor.remove_child(this._ornamentLabel); ++ this.actor.add_style_class_name('dashtodock-app-well-preview-menu-item'); ++ + this._cloneBin = new St.Bin(); + this._cloneBin.set_size(PREVIEW_MAX_WIDTH, PREVIEW_MAX_HEIGHT); + + // TODO: improve the way the closebutton is layout. Just use some padding + // for the moment. -+ this._cloneBin.set_style('padding: 5px'); ++ this._cloneBin.set_style('padding-bottom: 0.5em'); + + this.closeButton = new St.Button({ style_class: 'window-close', + x_expand: true, @@ -13030,67 +13624,39 @@ index 0000000..4f84b4d + } + +}); -+ +diff --git a/meson.build b/meson.build +index c16bde1..f9b56cf 100644 +--- a/meson.build ++++ b/meson.build +@@ -52,6 +52,7 @@ default_extensions += [ + all_extensions = default_extensions + all_extensions += [ + 'auto-move-windows', ++ 'dash-to-dock', + 'example', + 'native-window-placement', + 'top-icons', -- -2.14.2 +2.17.1 -From 9f6e5b7f81fe3796d04ee8585a8319bb6aa17b74 Mon Sep 17 00:00:00 2001 +From 671a9cb9728d7de3bd617fec40045b0397bf0cab 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 + extensions/panel-favorites/extension.js | 267 ++++++++++++++++++++ + extensions/panel-favorites/meson.build | 5 + + extensions/panel-favorites/metadata.json.in | 10 + + extensions/panel-favorites/stylesheet.css | 14 + + meson.build | 1 + + 5 files changed, 297 insertions(+) create mode 100644 extensions/panel-favorites/extension.js + create mode 100644 extensions/panel-favorites/meson.build create mode 100644 extensions/panel-favorites/metadata.json.in create mode 100644 extensions/panel-favorites/stylesheet.css -diff --git a/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 @@ -13364,6 +13930,17 @@ index 0000000..b817dbb + return indicator; + }; +} +diff --git a/extensions/panel-favorites/meson.build b/extensions/panel-favorites/meson.build +new file mode 100644 +index 0000000..48504f6 +--- /dev/null ++++ b/extensions/panel-favorites/meson.build +@@ -0,0 +1,5 @@ ++extension_data += configure_file( ++ input: metadata_name + '.in', ++ output: metadata_name, ++ configuration: metadata_conf ++) diff --git a/extensions/panel-favorites/metadata.json.in b/extensions/panel-favorites/metadata.json.in new file mode 100644 index 0000000..037f281 @@ -13400,71 +13977,42 @@ index 0000000..120adac + font-weight: bold; + -y-offset: 6px; +} +diff --git a/meson.build b/meson.build +index f9b56cf..3451585 100644 +--- a/meson.build ++++ b/meson.build +@@ -55,6 +55,7 @@ all_extensions += [ + 'dash-to-dock', + 'example', + 'native-window-placement', ++ 'panel-favorites', + 'top-icons', + 'user-theme' + ] -- -2.14.2 +2.17.1 -From baec74c44a6d57b4525466403d164a5ab0112259 Mon Sep 17 00:00:00 2001 +From d518dca768e895a00e7eae89b72cab8474826314 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 + extensions/updates-dialog/extension.js | 490 ++++++++++++++++++ + extensions/updates-dialog/meson.build | 7 + + extensions/updates-dialog/metadata.json.in | 10 + + ...hell.extensions.updates-dialog.gschema.xml | 30 ++ + extensions/updates-dialog/stylesheet.css | 1 + + meson.build | 1 + + po/POTFILES.in | 2 + + 7 files changed, 541 insertions(+) create mode 100644 extensions/updates-dialog/extension.js + create mode 100644 extensions/updates-dialog/meson.build create mode 100644 extensions/updates-dialog/metadata.json.in create mode 100644 extensions/updates-dialog/org.gnome.shell.extensions.updates-dialog.gschema.xml create mode 100644 extensions/updates-dialog/stylesheet.css -diff --git a/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 @@ -13961,6 +14509,19 @@ index 0000000..2fa62a5 + pkProxy = null; + } +} +diff --git a/extensions/updates-dialog/meson.build b/extensions/updates-dialog/meson.build +new file mode 100644 +index 0000000..585c02d +--- /dev/null ++++ b/extensions/updates-dialog/meson.build +@@ -0,0 +1,7 @@ ++extension_data += configure_file( ++ input: metadata_name + '.in', ++ output: metadata_name, ++ configuration: metadata_conf ++) ++ ++extension_schemas += files(metadata_conf.get('gschemaname') + '.gschema.xml') diff --git a/extensions/updates-dialog/metadata.json.in b/extensions/updates-dialog/metadata.json.in new file mode 100644 index 0000000..9946abb @@ -14020,6 +14581,18 @@ index 0000000..25134b6 +++ b/extensions/updates-dialog/stylesheet.css @@ -0,0 +1 @@ +/* This extensions requires no special styling */ +diff --git a/meson.build b/meson.build +index 3451585..08a243e 100644 +--- a/meson.build ++++ b/meson.build +@@ -57,6 +57,7 @@ all_extensions += [ + 'native-window-placement', + 'panel-favorites', + 'top-icons', ++ 'updates-dialog', + 'user-theme' + ] + diff --git a/po/POTFILES.in b/po/POTFILES.in index d98ca1b..43a817f 100644 --- a/po/POTFILES.in @@ -14034,65 +14607,26 @@ index d98ca1b..43a817f 100644 extensions/user-theme/org.gnome.shell.extensions.user-theme.gschema.xml extensions/window-list/extension.js -- -2.14.2 +2.17.1 -From d640b8f483903cf13c0a1c09fbc4b064dea3b920 Mon Sep 17 00:00:00 2001 +From e5f70e34dc8bb90fe1df024c5964bb0b206ea6b5 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/extension.js | 31 +++++++++++++++++++++++ + extensions/no-hot-corner/meson.build | 5 ++++ + extensions/no-hot-corner/metadata.json.in | 9 +++++++ extensions/no-hot-corner/stylesheet.css | 1 + - 5 files changed, 47 insertions(+), 2 deletions(-) - create mode 100644 extensions/no-hot-corner/Makefile.am + meson.build | 1 + + 5 files changed, 47 insertions(+) create mode 100644 extensions/no-hot-corner/extension.js + create mode 100644 extensions/no-hot-corner/meson.build create mode 100644 extensions/no-hot-corner/metadata.json.in create mode 100644 extensions/no-hot-corner/stylesheet.css -diff --git a/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 @@ -14130,6 +14664,17 @@ index 0000000..e7a0d63 + Main.layoutManager.disconnect(_id); + Main.layoutManager._updateHotCorners(); +} +diff --git a/extensions/no-hot-corner/meson.build b/extensions/no-hot-corner/meson.build +new file mode 100644 +index 0000000..48504f6 +--- /dev/null ++++ b/extensions/no-hot-corner/meson.build +@@ -0,0 +1,5 @@ ++extension_data += configure_file( ++ input: metadata_name + '.in', ++ output: metadata_name, ++ configuration: metadata_conf ++) diff --git a/extensions/no-hot-corner/metadata.json.in b/extensions/no-hot-corner/metadata.json.in new file mode 100644 index 0000000..406d83b @@ -14152,6 +14697,18 @@ index 0000000..25134b6 +++ b/extensions/no-hot-corner/stylesheet.css @@ -0,0 +1 @@ +/* This extensions requires no special styling */ +diff --git a/meson.build b/meson.build +index 08a243e..201c484 100644 +--- a/meson.build ++++ b/meson.build +@@ -55,6 +55,7 @@ all_extensions += [ + 'dash-to-dock', + 'example', + 'native-window-placement', ++ 'no-hot-corner', + 'panel-favorites', + 'top-icons', + 'updates-dialog', -- -2.14.2 +2.17.1 diff --git a/SOURCES/apps-menu-follow-sort-order.patch b/SOURCES/apps-menu-follow-sort-order.patch deleted file mode 100644 index d6238ea..0000000 --- a/SOURCES/apps-menu-follow-sort-order.patch +++ /dev/null @@ -1,107 +0,0 @@ -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 deleted file mode 100644 index fdd7dbb..0000000 --- a/SOURCES/apps-menu-support-separators.patch +++ /dev/null @@ -1,100 +0,0 @@ -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 deleted file mode 100644 index 47d21e7..0000000 --- a/SOURCES/classic-style-fixes.patch +++ /dev/null @@ -1,89 +0,0 @@ -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 index 57865cf..7e9c8d2 100644 --- a/SOURCES/resurrect-system-monitor.patch +++ b/SOURCES/resurrect-system-monitor.patch @@ -1,4 +1,4 @@ -From 5b4f81b1b510cc254221cac762dd282408c18a8c Mon Sep 17 00:00:00 2001 +From 57bb099db30703a474a023122f1106e199ff79ed Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Florian=20M=C3=BCllner?= Date: Wed, 17 May 2017 19:13:50 +0200 Subject: [PATCH 1/4] extensions: Resurrect systemMonitor extension @@ -13,78 +13,20 @@ 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/extension.js | 376 ++++++++++++++++++++++ + extensions/systemMonitor/meson.build | 5 + 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 + extensions/systemMonitor/stylesheet.css | 35 ++ + meson.build | 1 + + 5 files changed, 428 insertions(+) create mode 100644 extensions/systemMonitor/extension.js + create mode 100644 extensions/systemMonitor/meson.build create mode 100644 extensions/systemMonitor/metadata.json.in create mode 100644 extensions/systemMonitor/stylesheet.css -diff --git a/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 +index 0000000..7b09df0 --- /dev/null +++ b/extensions/systemMonitor/extension.js @@ -0,0 +1,376 @@ @@ -220,7 +162,7 @@ index 0000000..58a2f57 + cr.setLineWidth(1); + cr.setDash([4,1], 0); + cr.stroke(); -+ ++ + //draw the foreground + + function makePath(values, reverse, nudge) { @@ -241,7 +183,7 @@ index 0000000..58a2f57 + + } + } -+ ++ + let renderStats = this.renderStats; + + // Make sure we don't have more sample points than pixels @@ -258,10 +200,10 @@ index 0000000..58a2f57 + 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, ++ // 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); @@ -274,7 +216,7 @@ index 0000000..58a2f57 + 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); @@ -294,7 +236,7 @@ index 0000000..58a2f57 + + 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) { @@ -308,11 +250,11 @@ index 0000000..58a2f57 + this._prev = new GTop.glibtop_cpu; + GTop.glibtop_get_cpu(this._prev); + -+ this.stats = { ++ 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: []} ++ 'cpu-total': {color: '-cpu-total-color', values: []} + }; + }, + @@ -333,7 +275,7 @@ index 0000000..58a2f57 + t += user / total; + this.stats['cpu-user'].values.push(t); + this.stats['cpu-total'].values.push(1 - idle / total); -+ ++ + this._prev = cpu; + } +}); @@ -341,13 +283,13 @@ index 0000000..58a2f57 +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) { @@ -464,6 +406,17 @@ index 0000000..58a2f57 +function init() { + return new Extension(); +} +diff --git a/extensions/systemMonitor/meson.build b/extensions/systemMonitor/meson.build +new file mode 100644 +index 0000000..48504f6 +--- /dev/null ++++ b/extensions/systemMonitor/meson.build +@@ -0,0 +1,5 @@ ++extension_data += configure_file( ++ input: metadata_name + '.in', ++ output: metadata_name, ++ configuration: metadata_conf ++) diff --git a/extensions/systemMonitor/metadata.json.in b/extensions/systemMonitor/metadata.json.in new file mode 100644 index 0000000..fa75007 @@ -522,11 +475,77 @@ index 0000000..13f95ec + font-size: 9pt; + font-weight: bold; +} +diff --git a/meson.build b/meson.build +index 201c484..cde2d34 100644 +--- a/meson.build ++++ b/meson.build +@@ -30,60 +30,61 @@ if ver_arr[1].to_int().is_even() + else + shell_version = '.'.join(ver_arr) + endif + + uuid_suffix = '@gnome-shell-extensions.gcampax.github.com' + + classic_extensions = [ + 'alternate-tab', + 'apps-menu', + 'places-menu', + 'launch-new-instance', + 'window-list' + ] + + default_extensions = classic_extensions + default_extensions += [ + 'drive-menu', + 'screenshot-window-sizer', + 'windowsNavigator', + 'workspace-indicator' + ] + + all_extensions = default_extensions + all_extensions += [ + 'auto-move-windows', + 'dash-to-dock', + 'example', + 'native-window-placement', + 'no-hot-corner', + 'panel-favorites', ++ 'systemMonitor', + 'top-icons', + 'updates-dialog', + 'user-theme' + ] + + enabled_extensions = get_option('enable_extensions') + + if enabled_extensions.length() == 0 + set = get_option('extension_set') + + if set == 'classic' + enabled_extensions += classic_extensions + elif set == 'default' + enabled_extensions += default_extensions + elif set == 'all' + enabled_extensions += all_extensions + endif + endif + + classic_mode_enabled = get_option('classic_mode') + + if classic_mode_enabled + # Sanity check: Make sure all classic extensions are enabled + foreach e : classic_extensions + if not enabled_extensions.contains(e) + error('Classic mode is enabled, ' + + 'but the required extension @0@ is not.'.format(e)) + endif + endforeach + endif -- -2.14.2 +2.17.1 -From 529c0c1da0259953130a0e098820854336b1c87e Mon Sep 17 00:00:00 2001 +From 8ffea72d040e165c73b1b2eba82e6c4e106aee7f 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 @@ -537,15 +556,18 @@ 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 --------- + 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 +index 7b09df0..1388a1f 100644 --- a/extensions/systemMonitor/extension.js +++ b/extensions/systemMonitor/extension.js -@@ -4,10 +4,12 @@ const Clutter = imports.gi.Clutter; +@@ -1,132 +1,146 @@ + /* -*- mode: js2; js2-basic-offset: 4; indent-tabs-mode: nil -*- */ + + const Clutter = imports.gi.Clutter; const GTop = imports.gi.GTop; const Lang = imports.lang; const Mainloop = imports.mainloop; @@ -558,7 +580,21 @@ index 58a2f57..01146fa 100644 const Tweener = imports.ui.tweener; const Gettext = imports.gettext.domain('gnome-shell-extensions'); -@@ -29,18 +31,21 @@ const Indicator = new Lang.Class({ + 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(); @@ -588,7 +624,32 @@ index 58a2f57..01146fa 100644 this._timeout = Mainloop.timeout_add(INDICATOR_UPDATE_INTERVAL, Lang.bind(this, function () { this._updateValues(); -@@ -73,6 +78,7 @@ const Indicator = new Lang.Class({ + 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); @@ -596,7 +657,27 @@ index 58a2f57..01146fa 100644 Tweener.addTween(this.label, { opacity: 255, time: ITEM_LABEL_SHOW_TIME, -@@ -100,6 +106,14 @@ const Indicator = new Lang.Class({ + 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(); + }) }); }, @@ -611,7 +692,61 @@ index 58a2f57..01146fa 100644 destroy: function() { Mainloop.source_remove(this._timeout); -@@ -194,6 +208,7 @@ const Indicator = new Lang.Class({ + this.actor.destroy(); + if (this.label) + this.label.destroy(); + }, + + _initValues: function() { + }, + + _updateValues: function() { + }, + + _draw: function(area) { + let [width, height] = area.get_surface_size(); + let themeNode = this.actor.get_theme_node(); + let cr = area.get_context(); + + //draw the background grid + let color = themeNode.get_color(this.gridColor); + let gridOffset = Math.floor(height / (INDICATOR_NUM_GRID_LINES + 1)); + for (let i = 1; i <= INDICATOR_NUM_GRID_LINES; ++i) { + cr.moveTo(0, i * gridOffset + .5); + cr.lineTo(width, i * gridOffset + .5); + } + Clutter.cairo_set_source_color(cr, color); + cr.setLineWidth(1); + cr.setDash([4,1], 0); + cr.stroke(); +@@ -167,60 +181,61 @@ const Indicator = new Lang.Class({ + // We outline at full opacity and fill with 40% opacity + let outlineColor = themeNode.get_color(stat.color); + let color = new Clutter.Color(outlineColor); + color.alpha = color.alpha * .4; + + // Render the background between us and the next level + makePath(stat.values, false); + // If there is a process below us, render the cpu between us and it, otherwise, + // render to the bottom of the chart + if (i == renderStats.length - 1) { + cr.lineTo(stat.values.length - 1, height); + cr.lineTo(0, height); + cr.closePath(); + } else { + let nextStat = this.stats[renderStats[i+1]]; + makePath(nextStat.values, true); + } + cr.closePath() + Clutter.cairo_set_source_color(cr, color); + cr.fill(); + + // Render the outline of this level + makePath(stat.values, false, .5); + Clutter.cairo_set_source_color(cr, outlineColor); + cr.setLineWidth(1.0); + cr.setDash([], 0); + cr.stroke(); } } }); @@ -619,7 +754,61 @@ index 58a2f57..01146fa 100644 const CpuIndicator = new Lang.Class({ Name: 'SystemMonitor.CpuIndicator', -@@ -302,9 +317,7 @@ const Extension = new Lang.Class({ + Extends: Indicator, + + _init: function() { + this.parent(); + + this.gridColor = '-grid-color'; + this.renderStats = [ 'cpu-user', 'cpu-sys', 'cpu-iowait' ]; + + // Make sure renderStats is sorted as necessary for rendering + let renderStatOrder = {'cpu-total': 0, 'cpu-user': 1, 'cpu-sys': 2, 'cpu-iowait': 3}; + this.renderStats = this.renderStats.sort(function(a,b) { + return renderStatOrder[a] - renderStatOrder[b]; + }); + + this.setLabelText(_("CPU")); + }, + + _initValues: function() { + this._prev = new GTop.glibtop_cpu; + GTop.glibtop_get_cpu(this._prev); + + this.stats = { + 'cpu-user': {color: '-cpu-user-color', values: []}, + 'cpu-sys': {color: '-cpu-sys-color', values: []}, + 'cpu-iowait': {color: '-cpu-iowait-color', values: []}, + 'cpu-total': {color: '-cpu-total-color', values: []} + }; +@@ -275,96 +290,81 @@ const MemoryIndicator = new Lang.Class({ + 'mem-cached': { color: "-mem-cached-color", values: [] } + }; + }, + + _updateValues: function() { + GTop.glibtop_get_mem(this.mem); + + let t = this.mem.user / this.mem.total; + this.stats['mem-user'].values.push(t); + t += (this.mem.used - this.mem.user - this.mem.cached) / this.mem.total; + this.stats['mem-other'].values.push(t); + t += this.mem.cached / this.mem.total; + this.stats['mem-cached'].values.push(t); + } + }); + + const INDICATORS = [CpuIndicator, MemoryIndicator]; + + const Extension = new Lang.Class({ + Name: 'SystemMonitor.Extension', + + _init: function() { + Convenience.initTranslations(); + + this._showLabelTimeoutId = 0; + this._resetHoverTimeoutId = 0; + this._labelShowing = false; }, enable: function() { @@ -630,7 +819,8 @@ index 58a2f57..01146fa 100644 this._indicators = [ ]; for (let i = 0; i < INDICATORS.length; i++) { -@@ -313,31 +326,18 @@ const Extension = new Lang.Class({ + let indicator = new (INDICATORS[i])(); + indicator.actor.connect('notify::hover', Lang.bind(this, function() { this._onHover(indicator); })); @@ -666,11 +856,38 @@ index 58a2f57..01146fa 100644 }, _onHover: function (item) { + if (item.actor.get_hover()) { + if (this._showLabelTimeoutId == 0) { + let timeout = this._labelShowing ? 0 : ITEM_HOVER_TIMEOUT; + this._showLabelTimeoutId = Mainloop.timeout_add(timeout, + Lang.bind(this, function() { + this._labelShowing = true; + item.showLabel(); + return false; + })); + if (this._resetHoverTimeoutId > 0) { + Mainloop.source_remove(this._resetHoverTimeoutId); + this._resetHoverTimeoutId = 0; + } + } + } else { + if (this._showLabelTimeoutId > 0) + Mainloop.source_remove(this._showLabelTimeoutId); + this._showLabelTimeoutId = 0; + item.hideLabel(); + if (this._labelShowing) { + this._resetHoverTimeoutId = Mainloop.timeout_add(ITEM_HOVER_TIMEOUT, + Lang.bind(this, function() { + this._labelShowing = false; + return false; + })); + } + } diff --git a/extensions/systemMonitor/stylesheet.css b/extensions/systemMonitor/stylesheet.css index 13f95ec..978ac12 100644 --- a/extensions/systemMonitor/stylesheet.css +++ b/extensions/systemMonitor/stylesheet.css -@@ -1,17 +1,4 @@ +@@ -1,35 +1,21 @@ -.extension-systemMonitor-container { - spacing: 5px; - padding-left: 5px; @@ -688,7 +905,9 @@ index 13f95ec..978ac12 100644 height: 50px; -grid-color: #575757; -cpu-total-color: rgb(0,154,62); -@@ -21,7 +8,6 @@ + -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); @@ -696,11 +915,19 @@ index 13f95ec..978ac12 100644 } .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 +2.17.1 -From 77669f312fcd4cdc823c74fecb12480fb5e9769f Mon Sep 17 00:00:00 2001 +From e1133a8a92c49a90e02f8d2f1e66c7aae9d19519 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Florian=20M=C3=BCllner?= Date: Thu, 18 May 2017 16:20:07 +0200 Subject: [PATCH 3/4] systemMonitor: Handle clicks on section title @@ -716,10 +943,37 @@ Fixes: #3 1 file changed, 19 insertions(+), 1 deletion(-) diff --git a/extensions/systemMonitor/extension.js b/extensions/systemMonitor/extension.js -index 01146fa..cc49cbb 100644 +index 1388a1f..9c010d8 100644 --- a/extensions/systemMonitor/extension.js +++ b/extensions/systemMonitor/extension.js -@@ -303,6 +303,24 @@ const MemoryIndicator = new Lang.Class({ +@@ -276,75 +276,93 @@ const MemoryIndicator = new Lang.Class({ + // Make sure renderStats is sorted as necessary for rendering + let renderStatOrder = { 'mem-cached': 0, 'mem-other': 1, 'mem-user': 2 }; + this.renderStats = this.renderStats.sort(function(a,b) { + return renderStatOrder[a] - renderStatOrder[b]; + }); + + this.setLabelText(_("Memory")); + }, + + _initValues: function() { + this.mem = new GTop.glibtop_mem; + this.stats = { + 'mem-user': { color: "-mem-user-color", values: [] }, + 'mem-other': { color: "-mem-other-color", values: [] }, + 'mem-cached': { color: "-mem-cached-color", values: [] } + }; + }, + + _updateValues: function() { + GTop.glibtop_get_mem(this.mem); + + let t = this.mem.user / this.mem.total; + this.stats['mem-user'].values.push(t); + t += (this.mem.used - this.mem.user - this.mem.cached) / this.mem.total; + this.stats['mem-other'].values.push(t); + t += this.mem.cached / this.mem.total; + this.stats['mem-cached'].values.push(t); } }); @@ -744,7 +998,14 @@ index 01146fa..cc49cbb 100644 const INDICATORS = [CpuIndicator, MemoryIndicator]; const Extension = new Lang.Class({ -@@ -317,7 +335,7 @@ const Extension = new Lang.Class({ + Name: 'SystemMonitor.Extension', + + _init: function() { + Convenience.initTranslations(); + + this._showLabelTimeoutId = 0; + this._resetHoverTimeoutId = 0; + this._labelShowing = false; }, enable: function() { @@ -753,11 +1014,38 @@ index 01146fa..cc49cbb 100644 this._indicators = [ ]; for (let i = 0; i < INDICATORS.length; i++) { + let indicator = new (INDICATORS[i])(); + + indicator.actor.connect('notify::hover', Lang.bind(this, function() { + this._onHover(indicator); + })); + this._section.addMessage(indicator, false); + this._indicators.push(indicator); + } + + Main.panel.statusArea.dateMenu._messageList._addSection(this._section); + this._section.actor.get_parent().set_child_at_index(this._section.actor, 0); + }, + + disable: function() { + this._indicators.forEach(function(i) { i.destroy(); }); + + Main.panel.statusArea.dateMenu._messageList._removeSection(this._section); + }, + + _onHover: function (item) { + if (item.actor.get_hover()) { + if (this._showLabelTimeoutId == 0) { + let timeout = this._labelShowing ? 0 : ITEM_HOVER_TIMEOUT; + this._showLabelTimeoutId = Mainloop.timeout_add(timeout, + Lang.bind(this, function() { + this._labelShowing = true; + item.showLabel(); -- -2.14.2 +2.17.1 -From 7ba627fcc4646366c851fb0e4a4aa0ddf521b6b7 Mon Sep 17 00:00:00 2001 +From d2a0c7bfdb3fedf56021b6fd64628e4cda1aa294 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Florian=20M=C3=BCllner?= Date: Thu, 18 May 2017 18:00:17 +0200 Subject: [PATCH 4/4] systemMonitor: Provide classic styling @@ -768,25 +1056,11 @@ style. Fixes: #4 --- - extensions/systemMonitor/Makefile.am | 6 ++++++ extensions/systemMonitor/classic.css | 6 ++++++ - 2 files changed, 12 insertions(+) + extensions/systemMonitor/meson.build | 4 ++++ + 2 files changed, 10 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 @@ -799,6 +1073,20 @@ index 0000000..946863d + background-color: rgba(237,237,237,0.9); + border: 1px solid #a1a1a1; +} +diff --git a/extensions/systemMonitor/meson.build b/extensions/systemMonitor/meson.build +index 48504f6..b6548b1 100644 +--- a/extensions/systemMonitor/meson.build ++++ b/extensions/systemMonitor/meson.build +@@ -1,5 +1,9 @@ + extension_data += configure_file( + input: metadata_name + '.in', + output: metadata_name, + configuration: metadata_conf + ) ++ ++if classic_mode_enabled ++ extension_data += files('classic.css') ++endif -- -2.14.2 +2.17.1 diff --git a/SPECS/gnome-shell-extensions.spec b/SPECS/gnome-shell-extensions.spec index 3bd14ba..2a6b532 100644 --- a/SPECS/gnome-shell-extensions.spec +++ b/SPECS/gnome-shell-extensions.spec @@ -5,8 +5,8 @@ %global pkg_prefix gnome-shell-extension Name: gnome-shell-extensions -Version: 3.26.2 -Release: 3%{?dist} +Version: 3.28.1 +Release: 5%{?dist} Summary: Modify and extend GNOME Shell functionality and behavior Group: User Interface/Desktops @@ -14,8 +14,11 @@ Group: User Interface/Desktops License: GPLv2+ and BSD URL: http://wiki.gnome.org/Projects/GnomeShell/Extensions Source0: http://ftp.gnome.org/pub/GNOME/sources/%{name}/%{major_version}/%{name}-%{version}.tar.xz +Source2: https://github.com/sass/sassc/archive/3.4.1.tar.gz +Source3: https://github.com/sass/libsass/archive/3.4.5.tar.gz # BuildRequires: gnome-common -BuildRequires: autoconf automake +BuildRequires: meson +BuildRequires: ruby BuildRequires: gettext >= 0.19.6 BuildRequires: git BuildRequires: pkgconfig(gnome-desktop-3.0) @@ -24,17 +27,16 @@ Requires: gnome-shell >= %{min_gs_version} BuildArch: noarch Patch1: 0001-Update-style.patch -Patch2: 0001-classic-shade-panel-in-overview.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 +Patch7: 0001-data-drop-app-icon-styling.patch Patch11: 0001-Include-top-icons-in-classic-session.patch +Patch99: 0001-Revert-data-Remove-nautilus-classic.patch + %description GNOME Shell Extensions is a collection of extensions providing additional and optional functionality to GNOME Shell. @@ -299,19 +301,27 @@ workspaces. %prep +%setup -q -n libsass-3.4.5 -b3 -T +%setup -q -n sassc-3.4.1 -b2 -T %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} - +(cd ../libsass-3.4.5; + export LIBSASS_VERSION=3.4.5 + make %{?_smp_mflags}) +(cd ../sassc-3.4.1; + %make_build LDFLAGS="$RPM_OPT_FLAGS $PWD/../libsass-3.4.5/lib/libsass.a" \ + CFLAGS="$RPM_OPT_FLAGS -I$PWD/../libsass-3.4.5/include" \ + CXXFLAGS="$RPM_OPT_FLAGS" \ + SASS_LIBSASS_PATH=$PWD/../libsass-3.4.5) +export PATH=$PWD/../sassc-3.4.1/bin:$PATH + +%meson -Dextension_set="all" -Dclassic_mode=true +%meson_build %install -%make_install +%meson_install # Drop useless example extension rm -r $RPM_BUILD_ROOT%{_datadir}/gnome-shell/extensions/example*/ @@ -321,7 +331,7 @@ rm $RPM_BUILD_ROOT%{_datadir}/glib-2.0/schemas/org.gnome.shell.extensions.exampl %files -n %{pkg_prefix}-common -f %{name}.lang -%doc COPYING NEWS README +%doc COPYING NEWS README.md %files -n gnome-classic-session @@ -480,7 +490,29 @@ fi %changelog -* Fri Feb 23 2018 Florian Müllner - 3.26.2-3 +* Tue Sep 04 2018 Ray Strode - 3.28.1-5 +- Get rid of weird drop shadow next to app menu + Resolves: #1599841 + +* Wed Aug 01 2018 Ray Strode - 3.28.1-4 +- Make icons on desktop default in classic session again + Resolves: #1610477 + +* Fri Jun 22 2018 Florian Müllner - 3.28.1-3 +- Fix a couple of regressions from the rebase: + - add back classic overview style + - update dash-to-dock to a compatible version + Related: #1569717 + +* Mon Jun 11 2018 Ray Strode - 3.28.1-2 +- Import updated styles from gnome-shell + Related: #1569717 + +* Fri Jun 08 2018 Ray Strode - 3.28.1-1 +- Rebase to 3.28.1 + Resolves: #1569717 + +* Fri Feb 23 2018 Florian Müllner - 3.26.2-3 - Enable top-icons extension in classic mode Resolves: #1548446