diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..6b31b44 --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +SOURCES/gnome-shell-extensions-3.28.1.tar.xz diff --git a/.gnome-shell-extensions.metadata b/.gnome-shell-extensions.metadata new file mode 100644 index 0000000..8fb3d9e --- /dev/null +++ b/.gnome-shell-extensions.metadata @@ -0,0 +1 @@ +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 new file mode 100644 index 0000000..4f7dae5 --- /dev/null +++ b/SOURCES/0001-Include-top-icons-in-classic-session.patch @@ -0,0 +1,32 @@ +From 12f264be954474200864c9acad33c11292f34e14 Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Florian=20M=C3=BCllner?= +Date: Fri, 23 Feb 2018 16:56:46 +0100 +Subject: [PATCH] Include top-icons in classic session + +--- + meson.build | 2 +- + 1 file changed, 1 insertion(+), 1 deletion(-) + +diff --git a/meson.build b/meson.build +index da5e791..ca9f60a 100644 +--- a/meson.build ++++ b/meson.build +@@ -39,6 +39,7 @@ classic_extensions = [ + 'desktop-icons', + 'places-menu', + 'launch-new-instance', ++ 'top-icons', + 'window-list' + ] + +@@ -59,7 +60,6 @@ all_extensions += [ + 'no-hot-corner', + 'panel-favorites', + 'systemMonitor', +- 'top-icons', + 'updates-dialog', + 'user-theme' + ] +-- +2.20.1 + diff --git a/SOURCES/0001-Update-style.patch b/SOURCES/0001-Update-style.patch new file mode 100644 index 0000000..091f008 --- /dev/null +++ b/SOURCES/0001-Update-style.patch @@ -0,0 +1,65 @@ +From bddab939dedf770220f59394b4d4d5534063f0f1 Mon Sep 17 00:00:00 2001 +From: rpm-build +Date: Mon, 11 Jun 2018 16:40:34 -0400 +Subject: [PATCH] Update style + +--- + data/gnome-shell-sass/_common.scss | 19 +++++++++++++++++-- + 1 file changed, 17 insertions(+), 2 deletions(-) + +diff --git a/data/gnome-shell-sass/_common.scss b/data/gnome-shell-sass/_common.scss +index 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; ++ } ++ + .system-status-icon, + .app-menu-icon > StIcon, + .popup-menu-arrow { +@@ -1397,6 +1402,14 @@ StScrollBar { + + } + ++ .app-well-hover-text { ++ text-align: center; ++ color: $osd_fg_color; ++ background-color: $osd_bg_color; ++ border-radius: 5px; ++ padding: 3px; ++ } ++ + .app-well-app-running-dot { //running apps indicator + width: 10px; height: 3px; + background-color: $selected_bg_color; +@@ -1769,7 +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 new file mode 100644 index 0000000..d917c3d --- /dev/null +++ b/SOURCES/0001-apps-menu-Explicitly-set-label_actor.patch @@ -0,0 +1,40 @@ +From 07d409f6bf4e5bffa4dbda8d7cdefaf71742b85f Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Florian=20M=C3=BCllner?= +Date: Thu, 17 Mar 2016 17:15:38 +0100 +Subject: [PATCH] apps-menu: Explicitly set label_actor + +For some reason orca fails to pick up the label of category items, +so set the label_actor explicitly as workaround. +--- + extensions/apps-menu/extension.js | 8 ++++++-- + 1 file changed, 6 insertions(+), 2 deletions(-) + +diff --git a/extensions/apps-menu/extension.js b/extensions/apps-menu/extension.js +index 5067b63..49a05c7 100644 +--- a/extensions/apps-menu/extension.js ++++ b/extensions/apps-menu/extension.js +@@ -34,7 +34,9 @@ class ActivitiesMenuItem extends PopupMenu.PopupBaseMenuItem { + constructor(button) { + super(); + this._button = button; +- this.actor.add_child(new St.Label({ text: _("Activities Overview") })); ++ let label = new St.Label({ text: _("Activities Overview") }); ++ this.actor.add_child(label); ++ this.actor.label_actor = label; + } + + activate(event) { +@@ -129,7 +131,9 @@ class CategoryMenuItem extends PopupMenu.PopupBaseMenuItem { + else + name = _("Favorites"); + +- this.actor.add_child(new St.Label({ text: name })); ++ let label = new St.Label({ text: name }); ++ this.actor.add_child(label); ++ this.actor.label_actor = label; + this.actor.connect('motion-event', this._onMotionEvent.bind(this)); + } + +-- +2.20.1 + diff --git a/SOURCES/0001-apps-menu-add-logo-icon-to-Applications-menu.patch b/SOURCES/0001-apps-menu-add-logo-icon-to-Applications-menu.patch new file mode 100644 index 0000000..2f79690 --- /dev/null +++ b/SOURCES/0001-apps-menu-add-logo-icon-to-Applications-menu.patch @@ -0,0 +1,29 @@ +From fe6695b8d45fe7d1d9aea8c41c9aa54048a9704d Mon Sep 17 00:00:00 2001 +From: Ray Strode +Date: Tue, 21 Jan 2014 16:48:17 -0500 +Subject: [PATCH] apps-menu: add logo icon to Applications menu + +Brand requested it. +--- + extensions/apps-menu/extension.js | 5 +++++ + 1 file changed, 5 insertions(+) + +diff --git a/extensions/apps-menu/extension.js b/extensions/apps-menu/extension.js +index 2f4002a..41d1faf 100644 +--- a/extensions/apps-menu/extension.js ++++ b/extensions/apps-menu/extension.js +@@ -433,6 +433,11 @@ const ApplicationsButton = new Lang.Class({ + + let hbox = new St.BoxLayout({ style_class: 'panel-status-menu-box' }); + ++ let iconFile = Gio.File.new_for_path('/usr/share/icons/hicolor/scalable/apps/start-here.svg'); ++ this._icon = new St.Icon({ gicon: new Gio.FileIcon({ file: iconFile }), ++ style_class: 'panel-logo-icon' }); ++ hbox.add_actor(this._icon); ++ + this._label = new St.Label({ text: _("Applications"), + y_expand: true, + y_align: Clutter.ActorAlign.CENTER }); +-- +2.14.2 + diff --git a/SOURCES/0001-classic-Shade-panel-in-overview.patch b/SOURCES/0001-classic-Shade-panel-in-overview.patch new file mode 100644 index 0000000..5894023 --- /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.20.1 + diff --git a/SOURCES/0001-common-get-rid-of-weird-drop-shadow-nex-to-app-menu.patch b/SOURCES/0001-common-get-rid-of-weird-drop-shadow-nex-to-app-menu.patch new file mode 100644 index 0000000..c8ac779 --- /dev/null +++ b/SOURCES/0001-common-get-rid-of-weird-drop-shadow-nex-to-app-menu.patch @@ -0,0 +1,159 @@ +From 707ca4122da0a638f3df3b92178acc04eea264ec Mon Sep 17 00:00:00 2001 +From: rpm-build +Date: Tue, 4 Sep 2018 15:33:26 -0400 +Subject: [PATCH] common: get rid of weird drop shadow nex to app menu + +Resolves: #1620241 + +--- + 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 2f5c887..9883c78 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/add-extra-extensions.patch b/SOURCES/add-extra-extensions.patch new file mode 100644 index 0000000..f5d284f --- /dev/null +++ b/SOURCES/add-extra-extensions.patch @@ -0,0 +1,21963 @@ +From 3ce61087bd6777c450690f5f10e7d5a689f8d08c 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/6] Add top-icons extension + +--- + extensions/top-icons/extension.js | 225 ++++++++++++++++++++++++++ + extensions/top-icons/meson.build | 5 + + extensions/top-icons/metadata.json.in | 10 ++ + extensions/top-icons/stylesheet.css | 1 + + meson.build | 1 + + 5 files changed, 242 insertions(+) + create mode 100644 extensions/top-icons/extension.js + create mode 100644 extensions/top-icons/meson.build + create mode 100644 extensions/top-icons/metadata.json.in + create mode 100644 extensions/top-icons/stylesheet.css + +diff --git a/extensions/top-icons/extension.js b/extensions/top-icons/extension.js +new file mode 100644 +index 0000000..7312a26 +--- /dev/null ++++ b/extensions/top-icons/extension.js +@@ -0,0 +1,225 @@ ++// -*- mode: js; js-indent-level: 4; indent-tabs-mode: nil -*- ++ ++const Clutter = imports.gi.Clutter; ++const Shell = imports.gi.Shell; ++const St = imports.gi.St; ++const Main = imports.ui.main; ++const GLib = imports.gi.GLib; ++const Lang = imports.lang; ++const Panel = imports.ui.panel; ++const PanelMenu = imports.ui.panelMenu; ++const Meta = imports.gi.Meta; ++const Mainloop = imports.mainloop; ++const NotificationDaemon = imports.ui.notificationDaemon; ++const System = imports.system; ++ ++let trayAddedId = 0; ++let trayRemovedId = 0; ++let getSource = null; ++let icons = []; ++let notificationDaemon = null; ++let sysTray = null; ++ ++const PANEL_ICON_SIZE = 24; ++ ++function init() { ++ if (Main.legacyTray) { ++ notificationDaemon = Main.legacyTray; ++ NotificationDaemon.STANDARD_TRAY_ICON_IMPLEMENTATIONS = imports.ui.legacyTray.STANDARD_TRAY_ICON_IMPLEMENTATIONS; ++ } ++ else if (Main.notificationDaemon._fdoNotificationDaemon && ++ Main.notificationDaemon._fdoNotificationDaemon._trayManager) { ++ notificationDaemon = Main.notificationDaemon._fdoNotificationDaemon; ++ getSource = Lang.bind(notificationDaemon, NotificationDaemon.FdoNotificationDaemon.prototype._getSource); ++ } ++ else if (Main.notificationDaemon._trayManager) { ++ notificationDaemon = Main.notificationDaemon; ++ getSource = Lang.bind(notificationDaemon, NotificationDaemon.NotificationDaemon.prototype._getSource); ++ } ++ else { ++ NotificationDaemon.STANDARD_TRAY_ICON_IMPLEMENTATIONS = { ++ 'bluetooth-applet': 1, 'gnome-sound-applet': 1, 'nm-applet': 1, ++ 'gnome-power-manager': 1, 'keyboard': 1, 'a11y-keyboard': 1, ++ 'kbd-scrolllock': 1, 'kbd-numlock': 1, 'kbd-capslock': 1, 'ibus-ui-gtk': 1 ++ }; ++ } ++} ++ ++function enable() { ++ if (notificationDaemon) ++ GLib.idle_add(GLib.PRIORITY_LOW, moveToTop); ++ else ++ createTray(); ++} ++ ++function createSource (title, pid, ndata, sender, trayIcon) { ++ if (trayIcon) { ++ onTrayIconAdded(this, trayIcon, title); ++ return null; ++ } ++ ++ return getSource(title, pid, ndata, sender, trayIcon); ++}; ++ ++function onTrayIconAdded(o, icon, role) { ++ let wmClass = icon.wm_class ? icon.wm_class.toLowerCase() : ''; ++ if (NotificationDaemon.STANDARD_TRAY_ICON_IMPLEMENTATIONS[wmClass] !== undefined) ++ return; ++ ++ let buttonBox = new PanelMenu.ButtonBox(); ++ let box = buttonBox.actor; ++ let parent = box.get_parent(); ++ ++ let scaleFactor = St.ThemeContext.get_for_stage(global.stage).scale_factor; ++ let iconSize = PANEL_ICON_SIZE * scaleFactor; ++ ++ icon.set_size(iconSize, iconSize); ++ box.add_actor(icon); ++ ++ icon.reactive = true; ++ ++ if (parent) ++ parent.remove_actor(box); ++ ++ icons.push(icon); ++ Main.panel._rightBox.insert_child_at_index(box, 0); ++ ++ let clickProxy = new St.Bin({ width: iconSize, height: iconSize }); ++ clickProxy.reactive = true; ++ Main.uiGroup.add_actor(clickProxy); ++ ++ icon._proxyAlloc = Main.panel._rightBox.connect('allocation-changed', function() { ++ Meta.later_add(Meta.LaterType.BEFORE_REDRAW, function() { ++ let [x, y] = icon.get_transformed_position(); ++ clickProxy.set_position(x, y); ++ }); ++ }); ++ ++ icon.connect("destroy", function() { ++ Main.panel._rightBox.disconnect(icon._proxyAlloc); ++ clickProxy.destroy(); ++ }); ++ ++ clickProxy.connect('button-release-event', function(actor, event) { ++ icon.click(event); ++ }); ++ ++ icon._clickProxy = clickProxy; ++ ++ /* Fixme: HACK */ ++ Meta.later_add(Meta.LaterType.BEFORE_REDRAW, function() { ++ let [x, y] = icon.get_transformed_position(); ++ clickProxy.set_position(x, y); ++ return false; ++ }); ++ let timerId = 0; ++ let i = 0; ++ timerId = Mainloop.timeout_add(500, function() { ++ icon.set_size(icon.width == iconSize ? iconSize - 1 : iconSize, ++ icon.width == iconSize ? iconSize - 1 : iconSize); ++ i++; ++ if (i == 2) ++ Mainloop.source_remove(timerId); ++ }); ++} ++ ++function onTrayIconRemoved(o, icon) { ++ let parent = icon.get_parent(); ++ parent.destroy(); ++ icon.destroy(); ++ icons.splice(icons.indexOf(icon), 1); ++} ++ ++function createTray() { ++ sysTray = new Shell.TrayManager(); ++ sysTray.connect('tray-icon-added', onTrayIconAdded); ++ sysTray.connect('tray-icon-removed', onTrayIconRemoved); ++ sysTray.manage_screen(global.screen, Main.panel.actor); ++} ++ ++function destroyTray() { ++ icons.forEach(icon => { icon.get_parent().destroy(); }); ++ icons = []; ++ ++ sysTray = null; ++ System.gc(); // force finalizing tray to unmanage screen ++} ++ ++function moveToTop() { ++ notificationDaemon._trayManager.disconnect(notificationDaemon._trayIconAddedId); ++ notificationDaemon._trayManager.disconnect(notificationDaemon._trayIconRemovedId); ++ trayAddedId = notificationDaemon._trayManager.connect('tray-icon-added', onTrayIconAdded); ++ trayRemovedId = notificationDaemon._trayManager.connect('tray-icon-removed', onTrayIconRemoved); ++ ++ notificationDaemon._getSource = createSource; ++ ++ let toDestroy = []; ++ if (notificationDaemon._sources) { ++ for (let i = 0; i < notificationDaemon._sources.length; i++) { ++ let source = notificationDaemon._sources[i]; ++ if (!source.trayIcon) ++ continue; ++ let parent = source.trayIcon.get_parent(); ++ parent.remove_actor(source.trayIcon); ++ onTrayIconAdded(this, source.trayIcon, source.initialTitle); ++ toDestroy.push(source); ++ } ++ } ++ else { ++ for (let i = 0; i < notificationDaemon._iconBox.get_n_children(); i++) { ++ let button = notificationDaemon._iconBox.get_child_at_index(i); ++ let icon = button.child; ++ button.remove_actor(icon); ++ onTrayIconAdded(this, icon, ''); ++ toDestroy.push(button); ++ } ++ } ++ ++ for (let i = 0; i < toDestroy.length; i++) { ++ toDestroy[i].destroy(); ++ } ++} ++ ++function moveToTray() { ++ if (trayAddedId != 0) { ++ notificationDaemon._trayManager.disconnect(trayAddedId); ++ trayAddedId = 0; ++ } ++ ++ if (trayRemovedId != 0) { ++ notificationDaemon._trayManager.disconnect(trayRemovedId); ++ trayRemovedId = 0; ++ } ++ ++ notificationDaemon._trayIconAddedId = notificationDaemon._trayManager.connect('tray-icon-added', ++ Lang.bind(notificationDaemon, notificationDaemon._onTrayIconAdded)); ++ notificationDaemon._trayIconRemovedId = notificationDaemon._trayManager.connect('tray-icon-removed', ++ Lang.bind(notificationDaemon, notificationDaemon._onTrayIconRemoved)); ++ ++ notificationDaemon._getSource = getSource; ++ ++ for (let i = 0; i < icons.length; i++) { ++ let icon = icons[i]; ++ let parent = icon.get_parent(); ++ if (icon._clicked) { ++ icon.disconnect(icon._clicked); ++ } ++ icon._clicked = undefined; ++ if (icon._proxyAlloc) { ++ Main.panel._rightBox.disconnect(icon._proxyAlloc); ++ } ++ icon._clickProxy.destroy(); ++ parent.remove_actor(icon); ++ parent.destroy(); ++ notificationDaemon._onTrayIconAdded(notificationDaemon, icon); ++ } ++ ++ icons = []; ++} ++ ++function disable() { ++ if (notificationDaemon) ++ moveToTray(); ++ else ++ destroyTray(); ++} +diff --git a/extensions/top-icons/meson.build b/extensions/top-icons/meson.build +new file mode 100644 +index 0000000..48504f6 +--- /dev/null ++++ b/extensions/top-icons/meson.build +@@ -0,0 +1,5 @@ ++extension_data += configure_file( ++ input: metadata_name + '.in', ++ output: metadata_name, ++ configuration: metadata_conf ++) +diff --git a/extensions/top-icons/metadata.json.in b/extensions/top-icons/metadata.json.in +new file mode 100644 +index 0000000..f1e2436 +--- /dev/null ++++ b/extensions/top-icons/metadata.json.in +@@ -0,0 +1,10 @@ ++{ ++"extension-id": "@extension_id@", ++"uuid": "@uuid@", ++"settings-schema": "@gschemaname@", ++"gettext-domain": "@gettext_domain@", ++"name": "Top Icons", ++"description": "Shows legacy tray icons on top", ++"shell-version": [ "@shell_current@" ], ++"url": "http://94.247.144.115/repo/topicons/" ++} +diff --git a/extensions/top-icons/stylesheet.css b/extensions/top-icons/stylesheet.css +new file mode 100644 +index 0000000..25134b6 +--- /dev/null ++++ b/extensions/top-icons/stylesheet.css +@@ -0,0 +1 @@ ++/* This extensions requires no special styling */ +diff --git a/meson.build b/meson.build +index 40a8275..c16bde1 100644 +--- a/meson.build ++++ b/meson.build +@@ -54,6 +54,7 @@ all_extensions += [ + 'auto-move-windows', + 'example', + 'native-window-placement', ++ 'top-icons', + 'user-theme' + ] + +-- +2.20.1 + + +From 6bbc576ed2c697b9da688fe19febff9c6ac7163f 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/6] Add dash-to-dock extension + +--- + extensions/dash-to-dock/Settings.ui | 3332 +++++++++++++++++ + extensions/dash-to-dock/appIconIndicators.js | 1124 ++++++ + extensions/dash-to-dock/appIcons.js | 1171 ++++++ + extensions/dash-to-dock/convenience.js | 74 + + extensions/dash-to-dock/dash.js | 1175 ++++++ + extensions/dash-to-dock/docking.js | 1925 ++++++++++ + extensions/dash-to-dock/extension.js | 23 + + extensions/dash-to-dock/intellihide.js | 323 ++ + extensions/dash-to-dock/launcherAPI.js | 244 ++ + extensions/dash-to-dock/media/glossy.svg | 139 + + extensions/dash-to-dock/media/logo.svg | 528 +++ + extensions/dash-to-dock/meson.build | 23 + + extensions/dash-to-dock/metadata.json.in | 12 + + ....shell.extensions.dash-to-dock.gschema.xml | 540 +++ + extensions/dash-to-dock/prefs.js | 868 +++++ + extensions/dash-to-dock/stylesheet.css | 109 + + extensions/dash-to-dock/theming.js | 672 ++++ + extensions/dash-to-dock/utils.js | 255 ++ + extensions/dash-to-dock/windowPreview.js | 630 ++++ + meson.build | 1 + + 20 files changed, 13168 insertions(+) + create mode 100644 extensions/dash-to-dock/Settings.ui + create mode 100644 extensions/dash-to-dock/appIconIndicators.js + create mode 100644 extensions/dash-to-dock/appIcons.js + create mode 100644 extensions/dash-to-dock/convenience.js + create mode 100644 extensions/dash-to-dock/dash.js + create mode 100644 extensions/dash-to-dock/docking.js + create mode 100644 extensions/dash-to-dock/extension.js + create mode 100644 extensions/dash-to-dock/intellihide.js + create mode 100644 extensions/dash-to-dock/launcherAPI.js + create mode 100644 extensions/dash-to-dock/media/glossy.svg + create mode 100644 extensions/dash-to-dock/media/logo.svg + create mode 100644 extensions/dash-to-dock/meson.build + create mode 100644 extensions/dash-to-dock/metadata.json.in + create mode 100644 extensions/dash-to-dock/org.gnome.shell.extensions.dash-to-dock.gschema.xml + create mode 100644 extensions/dash-to-dock/prefs.js + create mode 100644 extensions/dash-to-dock/stylesheet.css + create mode 100644 extensions/dash-to-dock/theming.js + create mode 100644 extensions/dash-to-dock/utils.js + create mode 100644 extensions/dash-to-dock/windowPreview.js + +diff --git a/extensions/dash-to-dock/Settings.ui b/extensions/dash-to-dock/Settings.ui +new file mode 100644 +index 0000000..2b164a8 +--- /dev/null ++++ b/extensions/dash-to-dock/Settings.ui +@@ -0,0 +1,3332 @@ ++ ++ ++ ++ ++ ++ 1 ++ 0.050000000000000003 ++ 0.25 ++ ++ ++ True ++ False ++ 12 ++ 12 ++ 12 ++ 12 ++ vertical ++ ++ ++ True ++ False ++ 0 ++ in ++ ++ ++ True ++ False ++ none ++ ++ ++ 100 ++ 80 ++ True ++ True ++ ++ ++ True ++ False ++ 12 ++ 12 ++ 12 ++ 12 ++ 32 ++ ++ ++ True ++ False ++ True ++ When set to minimize, double clicking minimizes all the windows of the application. ++ True ++ 40 ++ 0 ++ ++ ++ ++ 0 ++ 1 ++ ++ ++ ++ ++ True ++ False ++ True ++ Shift+Click action ++ 0 ++ ++ ++ 0 ++ 0 ++ ++ ++ ++ ++ True ++ False ++ center ++ ++ Raise window ++ Minimize window ++ Launch new instance ++ Cycle through windows ++ Minimize or overview ++ Show window previews ++ Minimize or show previews ++ Quit ++ ++ ++ ++ 1 ++ 0 ++ 2 ++ ++ ++ ++ ++ ++ ++ ++ ++ 100 ++ 80 ++ True ++ True ++ ++ ++ True ++ False ++ 12 ++ 12 ++ 12 ++ 12 ++ 32 ++ ++ ++ True ++ False ++ True ++ Behavior for Middle-Click. ++ True ++ 40 ++ 0 ++ ++ ++ ++ 0 ++ 1 ++ ++ ++ ++ ++ True ++ False ++ True ++ Middle-Click action ++ 0 ++ ++ ++ 0 ++ 0 ++ ++ ++ ++ ++ True ++ False ++ center ++ ++ Raise window ++ Minimize window ++ Launch new instance ++ Cycle through windows ++ Minimize or overview ++ Show window previews ++ Minimize or show previews ++ Quit ++ ++ ++ ++ 1 ++ 0 ++ 2 ++ ++ ++ ++ ++ ++ ++ ++ ++ 100 ++ 80 ++ True ++ True ++ ++ ++ True ++ False ++ 12 ++ 12 ++ 12 ++ 12 ++ 32 ++ ++ ++ True ++ False ++ True ++ Behavior for Shift+Middle-Click. ++ True ++ 40 ++ 0 ++ ++ ++ ++ 0 ++ 1 ++ ++ ++ ++ ++ True ++ False ++ True ++ Shift+Middle-Click action ++ 0 ++ ++ ++ 0 ++ 0 ++ ++ ++ ++ ++ True ++ False ++ center ++ ++ Raise window ++ Minimize window ++ Launch new instance ++ Cycle through windows ++ Minimize or overview ++ Show window previews ++ Minimize or show previews ++ Quit ++ ++ ++ ++ 1 ++ 0 ++ 2 ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ False ++ True ++ 0 ++ ++ ++ ++ ++ 1 ++ 0.01 ++ 0.10000000000000001 ++ ++ ++ 0.33000000000000002 ++ 1 ++ 0.01 ++ 0.10000000000000001 ++ ++ ++ 10 ++ 1 ++ 5 ++ ++ ++ True ++ False ++ 12 ++ 12 ++ 12 ++ 12 ++ vertical ++ ++ ++ True ++ False ++ 0 ++ in ++ ++ ++ True ++ False ++ none ++ ++ ++ 100 ++ 80 ++ True ++ True ++ ++ ++ True ++ False ++ 12 ++ 12 ++ 12 ++ 12 ++ vertical ++ 12 ++ ++ ++ True ++ False ++ 32 ++ ++ ++ True ++ False ++ center ++ Enable Unity7 like glossy backlit items ++ 0 ++ ++ ++ True ++ True ++ 0 ++ ++ ++ ++ ++ True ++ True ++ center ++ ++ ++ False ++ True ++ 1 ++ ++ ++ ++ ++ False ++ True ++ 0 ++ ++ ++ ++ ++ True ++ False ++ ++ ++ True ++ False ++ start ++ Use dominant color ++ ++ ++ True ++ True ++ 0 ++ ++ ++ ++ ++ True ++ True ++ ++ ++ False ++ True ++ 1 ++ ++ ++ ++ ++ False ++ True ++ 1 ++ ++ ++ ++ ++ True ++ False ++ 32 ++ ++ ++ True ++ True ++ ++ ++ 1 ++ 0 ++ ++ ++ ++ ++ True ++ False ++ True ++ Customize indicator style ++ fill ++ 0 ++ ++ ++ 0 ++ 0 ++ ++ ++ ++ ++ False ++ True ++ 2 ++ ++ ++ ++ ++ True ++ False ++ 1 ++ vertical ++ 12 ++ ++ ++ True ++ False ++ 32 ++ ++ ++ True ++ False ++ Color ++ 0 ++ ++ ++ True ++ True ++ 0 ++ ++ ++ ++ ++ True ++ True ++ True ++ ++ ++ False ++ True ++ 1 ++ ++ ++ ++ ++ False ++ True ++ 0 ++ ++ ++ ++ ++ True ++ False ++ 32 ++ ++ ++ True ++ False ++ Border color ++ 0 ++ ++ ++ True ++ True ++ 0 ++ ++ ++ ++ ++ True ++ True ++ True ++ ++ ++ False ++ True ++ 1 ++ ++ ++ ++ ++ False ++ True ++ 1 ++ ++ ++ ++ ++ True ++ False ++ 32 ++ ++ ++ True ++ False ++ Border width ++ 0 ++ ++ ++ True ++ True ++ 0 ++ ++ ++ ++ ++ True ++ True ++ dot_border_width_adjustment ++ ++ ++ False ++ True ++ 1 ++ ++ ++ ++ ++ False ++ True ++ 2 ++ ++ ++ ++ ++ False ++ True ++ 3 ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ False ++ True ++ 0 ++ ++ ++ ++ ++ 1 ++ 0.050000000000000003 ++ 0.25 ++ ++ ++ 16 ++ 128 ++ 1 ++ 10 ++ ++ ++ True ++ True ++ 6 ++ 6 ++ 6 ++ 6 ++ ++ ++ True ++ False ++ 24 ++ 24 ++ 24 ++ 24 ++ vertical ++ 24 ++ ++ ++ True ++ False ++ 0 ++ in ++ ++ ++ True ++ False ++ none ++ ++ ++ True ++ True ++ ++ ++ True ++ False ++ 12 ++ 12 ++ 12 ++ 12 ++ 32 ++ ++ ++ True ++ False ++ True ++ Show the dock on ++ 0 ++ ++ ++ 0 ++ 0 ++ ++ ++ ++ ++ True ++ False ++ center ++ ++ ++ ++ 1 ++ 0 ++ ++ ++ ++ ++ Show on all monitors. ++ True ++ True ++ False ++ 12 ++ 0 ++ True ++ ++ ++ 0 ++ 2 ++ 2 ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ 100 ++ True ++ True ++ ++ ++ True ++ False ++ 12 ++ 12 ++ 12 ++ 12 ++ 32 ++ ++ ++ True ++ False ++ True ++ Position on screen ++ 0 ++ ++ ++ False ++ True ++ 0 ++ ++ ++ ++ ++ True ++ False ++ 32 ++ ++ ++ Left ++ True ++ True ++ False ++ end ++ center ++ 0 ++ True ++ ++ ++ ++ False ++ True ++ 0 ++ ++ ++ ++ ++ Bottom ++ True ++ True ++ False ++ center ++ 0 ++ True ++ position_left_button ++ ++ ++ ++ False ++ True ++ 1 ++ ++ ++ ++ ++ Top ++ True ++ True ++ False ++ center ++ 0 ++ bottom ++ True ++ position_left_button ++ ++ ++ ++ False ++ True ++ 2 ++ ++ ++ ++ ++ Right ++ True ++ True ++ False ++ center ++ 0 ++ True ++ position_left_button ++ ++ ++ ++ False ++ True ++ 3 ++ ++ ++ ++ ++ False ++ True ++ 1 ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ False ++ True ++ 0 ++ ++ ++ ++ ++ True ++ False ++ 0 ++ in ++ ++ ++ True ++ False ++ none ++ ++ ++ 100 ++ 80 ++ True ++ True ++ ++ ++ True ++ False ++ 12 ++ 12 ++ 12 ++ 12 ++ 32 ++ ++ ++ True ++ False ++ True ++ Hide the dock when it obstructs a window of the current application. More refined settings are available. ++ True ++ 0 ++ ++ ++ ++ 0 ++ 1 ++ ++ ++ ++ ++ True ++ False ++ True ++ Intelligent autohide ++ 0 ++ ++ ++ 0 ++ 0 ++ ++ ++ ++ ++ True ++ False ++ 6 ++ ++ ++ True ++ True ++ True ++ center ++ center ++ 0.46000000834465027 ++ ++ ++ True ++ False ++ emblem-system-symbolic ++ ++ ++ ++ ++ ++ False ++ True ++ 0 ++ ++ ++ ++ ++ True ++ True ++ end ++ center ++ ++ ++ False ++ True ++ 1 ++ ++ ++ ++ ++ 1 ++ 0 ++ 2 ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ False ++ True ++ 1 ++ ++ ++ ++ ++ True ++ False ++ 0 ++ in ++ ++ ++ True ++ False ++ none ++ ++ ++ 100 ++ 80 ++ True ++ True ++ ++ ++ True ++ False ++ 12 ++ 12 ++ 12 ++ 12 ++ 32 ++ ++ ++ True ++ False ++ Dock size limit ++ 0 ++ ++ ++ 0 ++ 0 ++ ++ ++ ++ ++ True ++ True ++ baseline ++ True ++ dock_size_adjustment ++ 0 ++ 2 ++ right ++ ++ ++ ++ ++ 1 ++ 0 ++ ++ ++ ++ ++ Panel mode: extend to the screen edge ++ True ++ True ++ False ++ 12 ++ 0 ++ True ++ ++ ++ 0 ++ 1 ++ 2 ++ ++ ++ ++ ++ ++ ++ ++ ++ True ++ True ++ ++ ++ True ++ False ++ 12 ++ 12 ++ 12 ++ 12 ++ 32 ++ ++ ++ True ++ False ++ Icon size limit ++ 0 ++ ++ ++ 0 ++ 0 ++ ++ ++ ++ ++ True ++ True ++ baseline ++ True ++ icon_size_adjustment ++ 1 ++ 0 ++ right ++ ++ ++ ++ ++ 1 ++ 0 ++ ++ ++ ++ ++ Fixed icon size: scroll to reveal other icons ++ True ++ True ++ False ++ 12 ++ 0 ++ True ++ ++ ++ 0 ++ 1 ++ 2 ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ False ++ True ++ 2 ++ ++ ++ ++ ++ ++ ++ True ++ False ++ Position and size ++ ++ ++ False ++ ++ ++ ++ ++ True ++ False ++ 24 ++ 24 ++ 24 ++ 24 ++ vertical ++ 24 ++ ++ ++ True ++ False ++ 0 ++ in ++ ++ ++ True ++ False ++ none ++ ++ ++ True ++ True ++ ++ ++ True ++ False ++ 12 ++ 12 ++ 12 ++ 12 ++ 32 ++ ++ ++ True ++ True ++ end ++ center ++ ++ ++ 1 ++ 0 ++ ++ ++ ++ ++ True ++ False ++ True ++ Show favorite applications ++ 0 ++ ++ ++ 0 ++ 0 ++ ++ ++ ++ ++ ++ ++ ++ ++ True ++ True ++ ++ ++ True ++ False ++ 12 ++ 12 ++ 12 ++ 12 ++ 32 ++ ++ ++ True ++ True ++ end ++ center ++ ++ ++ 1 ++ 0 ++ ++ ++ ++ ++ True ++ False ++ True ++ Show running applications ++ 0 ++ ++ ++ 0 ++ 0 ++ ++ ++ ++ ++ Isolate workspaces. ++ True ++ True ++ False ++ 12 ++ 0 ++ True ++ ++ ++ 0 ++ 2 ++ 2 ++ ++ ++ ++ ++ Isolate monitors. ++ True ++ True ++ False ++ 12 ++ 0 ++ True ++ ++ ++ 0 ++ 3 ++ 2 ++ ++ ++ ++ ++ True ++ True ++ False ++ 3 ++ 0 ++ True ++ ++ ++ True ++ False ++ Show open windows previews. ++ True ++ ++ ++ ++ ++ 0 ++ 1 ++ 2 ++ ++ ++ ++ ++ ++ ++ ++ ++ True ++ True ++ ++ ++ True ++ False ++ 12 ++ 12 ++ 12 ++ 12 ++ 32 ++ ++ ++ True ++ True ++ end ++ center ++ ++ ++ 1 ++ 0 ++ 2 ++ ++ ++ ++ ++ True ++ False ++ True ++ If disabled, these settings are accessible from gnome-tweak-tool or the extension website. ++ True ++ 0 ++ ++ ++ ++ 0 ++ 1 ++ ++ ++ ++ ++ True ++ False ++ True ++ Show <i>Applications</i> icon ++ True ++ 0 ++ ++ ++ 0 ++ 0 ++ ++ ++ ++ ++ Move the applications button at the beginning of the dock. ++ True ++ True ++ False ++ 12 ++ 0 ++ True ++ ++ ++ 0 ++ 2 ++ 2 ++ ++ ++ ++ ++ True ++ True ++ False ++ 3 ++ 0 ++ 0.43000000715255737 ++ True ++ ++ ++ True ++ False ++ Animate <i>Show Applications</i>. ++ True ++ ++ ++ ++ ++ 0 ++ 3 ++ 2 ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ False ++ True ++ 0 ++ ++ ++ ++ ++ 1 ++ ++ ++ ++ ++ True ++ False ++ Launchers ++ ++ ++ 1 ++ False ++ ++ ++ ++ ++ True ++ False ++ 24 ++ 24 ++ 24 ++ 24 ++ vertical ++ 24 ++ ++ ++ True ++ False ++ 0 ++ in ++ ++ ++ True ++ False ++ none ++ ++ ++ True ++ True ++ ++ ++ True ++ False ++ 12 ++ 12 ++ 12 ++ 12 ++ 32 ++ ++ ++ True ++ False ++ True ++ Enable Super+(0-9) as shortcuts to activate apps. It can also be used together with Shift and Ctrl. ++ True ++ True ++ 0 ++ ++ ++ ++ 0 ++ 1 ++ ++ ++ ++ ++ True ++ False ++ True ++ Use keyboard shortcuts to activate apps ++ 0 ++ ++ ++ 0 ++ 0 ++ ++ ++ ++ ++ True ++ False ++ 6 ++ ++ ++ True ++ True ++ True ++ center ++ center ++ 0.46000000834465027 ++ ++ ++ True ++ False ++ emblem-system-symbolic ++ ++ ++ ++ ++ ++ False ++ True ++ 0 ++ ++ ++ ++ ++ True ++ True ++ end ++ center ++ ++ ++ False ++ True ++ 1 ++ ++ ++ ++ ++ 1 ++ 0 ++ 2 ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ False ++ True ++ 1 ++ ++ ++ ++ ++ True ++ False ++ 0 ++ in ++ ++ ++ True ++ False ++ none ++ ++ ++ True ++ True ++ ++ ++ True ++ False ++ 12 ++ 12 ++ 12 ++ 12 ++ 32 ++ ++ ++ True ++ False ++ True ++ Behaviour when clicking on the icon of a running application. ++ True ++ 0 ++ ++ ++ ++ 0 ++ 1 ++ ++ ++ ++ ++ True ++ False ++ True ++ Click action ++ 0 ++ ++ ++ 0 ++ 0 ++ ++ ++ ++ ++ True ++ False ++ 6 ++ ++ ++ True ++ True ++ True ++ center ++ ++ ++ True ++ False ++ emblem-system-symbolic ++ ++ ++ ++ ++ ++ False ++ True ++ 0 ++ ++ ++ ++ ++ True ++ False ++ center ++ ++ Raise window ++ Minimize ++ Launch new instance ++ Cycle through windows ++ Minimize or overview ++ Show window previews ++ Minimize or show previews ++ ++ ++ ++ False ++ True ++ 1 ++ ++ ++ ++ ++ 1 ++ 0 ++ 2 ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ False ++ True ++ 2 ++ ++ ++ ++ ++ True ++ False ++ 0 ++ in ++ ++ ++ True ++ False ++ none ++ ++ ++ True ++ True ++ ++ ++ True ++ False ++ 12 ++ 12 ++ 12 ++ 12 ++ 32 ++ ++ ++ True ++ False ++ True ++ Behaviour when scrolling on the icon of an application. ++ True ++ 0 ++ ++ ++ ++ 0 ++ 1 ++ ++ ++ ++ ++ True ++ False ++ True ++ Scroll action ++ 0 ++ ++ ++ 0 ++ 0 ++ ++ ++ ++ ++ True ++ False ++ 6 ++ ++ ++ True ++ False ++ center ++ ++ Do nothing ++ Cycle through windows ++ Switch workspace ++ ++ ++ ++ False ++ True ++ 1 ++ ++ ++ ++ ++ 1 ++ 0 ++ 2 ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ False ++ True ++ 3 ++ ++ ++ ++ ++ 2 ++ ++ ++ ++ ++ True ++ False ++ Behavior ++ ++ ++ 2 ++ False ++ ++ ++ ++ ++ True ++ False ++ 24 ++ 24 ++ 24 ++ 24 ++ vertical ++ 24 ++ ++ ++ True ++ False ++ 0 ++ in ++ ++ ++ True ++ True ++ none ++ ++ ++ True ++ True ++ ++ ++ True ++ False ++ 12 ++ 12 ++ 12 ++ 12 ++ 32 ++ ++ ++ True ++ False ++ True ++ Few customizations meant to integrate the dock with the default GNOME theme. Alternatively, specific options can be enabled below. ++ True ++ 0 ++ ++ ++ ++ 0 ++ 1 ++ ++ ++ ++ ++ True ++ False ++ True ++ Use built-in theme ++ 0 ++ ++ ++ 0 ++ 0 ++ ++ ++ ++ ++ True ++ True ++ end ++ center ++ ++ ++ 1 ++ 0 ++ 2 ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ False ++ True ++ 0 ++ ++ ++ ++ ++ True ++ False ++ 0 ++ in ++ ++ ++ True ++ False ++ none ++ ++ ++ True ++ True ++ ++ ++ True ++ False ++ 12 ++ 12 ++ 12 ++ 12 ++ 32 ++ ++ ++ True ++ True ++ end ++ center ++ ++ ++ 1 ++ 0 ++ 2 ++ ++ ++ ++ ++ True ++ False ++ True ++ Save space reducing padding and border radius. ++ 0 ++ ++ ++ ++ 0 ++ 1 ++ ++ ++ ++ ++ True ++ False ++ True ++ Shrink the dash ++ 0 ++ ++ ++ 0 ++ 0 ++ ++ ++ ++ ++ ++ ++ ++ ++ True ++ True ++ ++ ++ True ++ False ++ 12 ++ 12 ++ 12 ++ 12 ++ 32 ++ ++ ++ True ++ False ++ True ++ Customize windows counter indicators ++ 0 ++ ++ ++ 0 ++ 0 ++ ++ ++ ++ ++ True ++ False ++ 6 ++ ++ ++ True ++ True ++ True ++ center ++ center ++ 0.46000000834465027 ++ ++ ++ True ++ False ++ emblem-system-symbolic ++ ++ ++ ++ ++ ++ False ++ True ++ 0 ++ ++ ++ ++ ++ True ++ False ++ ++ Default ++ Dots ++ Squares ++ Dashes ++ Segmented ++ Solid ++ Ciliora ++ Metro ++ ++ ++ ++ False ++ True ++ 1 ++ ++ ++ ++ ++ 1 ++ 0 ++ 2 ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ True ++ True ++ ++ ++ True ++ False ++ 12 ++ 12 ++ 12 ++ 12 ++ 32 ++ ++ ++ True ++ False ++ True ++ Set the background color for the dash. ++ True ++ 0 ++ ++ ++ ++ 0 ++ 1 ++ ++ ++ ++ ++ True ++ False ++ True ++ Customize the dash color ++ 0 ++ ++ ++ 0 ++ 0 ++ ++ ++ ++ ++ True ++ False ++ 6 ++ ++ ++ True ++ True ++ True ++ center ++ center ++ 0.46000000834465027 ++ ++ ++ False ++ True ++ 1 ++ ++ ++ ++ ++ True ++ True ++ end ++ center ++ ++ ++ False ++ True ++ 1 ++ ++ ++ ++ ++ 1 ++ 0 ++ 2 ++ ++ ++ ++ ++ ++ ++ ++ ++ True ++ True ++ ++ ++ True ++ False ++ vertical ++ ++ ++ True ++ False ++ 12 ++ 12 ++ 12 ++ 12 ++ 32 ++ ++ ++ True ++ False ++ True ++ Tune the dash background opacity. ++ 0 ++ ++ ++ ++ 0 ++ 1 ++ ++ ++ ++ ++ True ++ False ++ True ++ Customize opacity ++ 0 ++ ++ ++ 0 ++ 0 ++ ++ ++ ++ ++ True ++ False ++ 6 ++ ++ ++ True ++ True ++ True ++ center ++ center ++ ++ ++ True ++ False ++ emblem-system-symbolic ++ ++ ++ ++ ++ ++ False ++ True ++ 0 ++ ++ ++ ++ ++ True ++ False ++ center ++ ++ Default ++ Fixed ++ Adaptive ++ Dynamic ++ ++ ++ ++ False ++ True ++ 1 ++ ++ ++ ++ ++ 1 ++ 0 ++ 2 ++ ++ ++ ++ ++ False ++ True ++ 0 ++ ++ ++ ++ ++ True ++ False ++ 12 ++ 12 ++ 12 ++ 12 ++ 32 ++ ++ ++ True ++ False ++ Opacity ++ ++ ++ False ++ True ++ 0 ++ ++ ++ ++ ++ True ++ True ++ custom_opacity_adjustement ++ on ++ False ++ 0 ++ 0 ++ 2 ++ right ++ ++ ++ ++ ++ True ++ True ++ 1 ++ ++ ++ ++ ++ False ++ True ++ 1 ++ ++ ++ ++ ++ ++ ++ ++ ++ 100 ++ 80 ++ True ++ True ++ ++ ++ True ++ False ++ 32 ++ ++ ++ True ++ False ++ center ++ 12 ++ Force straight corner ++ ++ 0 ++ ++ ++ True ++ True ++ 0 ++ ++ ++ ++ ++ True ++ True ++ center ++ 3 ++ ++ ++ False ++ True ++ 12 ++ 1 ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ False ++ True ++ 1 ++ ++ ++ ++ ++ 3 ++ ++ ++ ++ ++ True ++ False ++ Appearance ++ ++ ++ 3 ++ False ++ ++ ++ ++ ++ False ++ 24 ++ 24 ++ True ++ True ++ vertical ++ 5 ++ ++ ++ ++ False ++ True ++ 10 ++ 0 ++ ++ ++ ++ ++ True ++ False ++ <b>Dash to Dock</b> ++ True ++ ++ ++ False ++ True ++ 1 ++ ++ ++ ++ ++ True ++ False ++ center ++ ++ ++ True ++ False ++ end ++ version: ++ ++ ++ False ++ True ++ 0 ++ ++ ++ ++ ++ True ++ False ++ start ++ ... ++ ++ ++ False ++ True ++ 1 ++ ++ ++ ++ ++ False ++ True ++ 2 ++ ++ ++ ++ ++ True ++ False ++ Moves the dash out of the overview transforming it in a dock ++ center ++ True ++ ++ ++ False ++ True ++ 3 ++ ++ ++ ++ ++ True ++ False ++ center ++ 5 ++ ++ ++ True ++ False ++ Created by ++ ++ ++ False ++ True ++ 0 ++ ++ ++ ++ ++ True ++ True ++ Michele (<a href="mailto:micxgx@gmail.com">micxgx@gmail.com</a>) ++ True ++ ++ ++ False ++ True ++ 1 ++ ++ ++ ++ ++ False ++ True ++ 4 ++ ++ ++ ++ ++ Webpage ++ True ++ True ++ True ++ ++ center ++ none ++ https://micheleg.github.io/dash-to-dock/ ++ ++ ++ False ++ True ++ 5 ++ ++ ++ ++ ++ True ++ True ++ end ++ <span size="small">This program comes with ABSOLUTELY NO WARRANTY. ++See the <a href="https://www.gnu.org/licenses/old-licenses/gpl-2.0.html">GNU General Public License, version 2 or later</a> for details.</span> ++ True ++ center ++ True ++ ++ ++ True ++ True ++ 6 ++ ++ ++ ++ ++ 4 ++ ++ ++ ++ ++ True ++ False ++ About ++ ++ ++ 4 ++ False ++ ++ ++ ++ ++ 1 ++ 0.01 ++ 0.10000000000000001 ++ ++ ++ 1 ++ 0.01 ++ 0.10000000000000001 ++ ++ ++ True ++ False ++ 12 ++ 12 ++ 12 ++ 12 ++ vertical ++ ++ ++ True ++ False ++ 0 ++ in ++ ++ ++ True ++ False ++ none ++ ++ ++ 100 ++ 80 ++ True ++ True ++ ++ ++ True ++ False ++ 12 ++ 12 ++ 12 ++ 12 ++ vertical ++ 12 ++ ++ ++ True ++ False ++ 32 ++ ++ ++ True ++ True ++ ++ ++ 1 ++ 0 ++ ++ ++ ++ ++ True ++ False ++ True ++ Customize minimum and maximum opacity values ++ fill ++ 0 ++ ++ ++ 0 ++ 0 ++ ++ ++ ++ ++ False ++ True ++ 0 ++ ++ ++ ++ ++ True ++ False ++ 12 ++ 12 ++ 12 ++ 12 ++ 32 ++ ++ ++ True ++ False ++ Minimum opacity ++ ++ ++ False ++ True ++ 0 ++ ++ ++ ++ ++ True ++ True ++ min_opacity_adjustement ++ on ++ False ++ 0 ++ 0 ++ 2 ++ right ++ ++ ++ ++ ++ True ++ True ++ 1 ++ ++ ++ ++ ++ False ++ True ++ 1 ++ ++ ++ ++ ++ True ++ False ++ 12 ++ 12 ++ 12 ++ 12 ++ 32 ++ ++ ++ True ++ False ++ Maximum opacity ++ ++ ++ False ++ True ++ 0 ++ ++ ++ ++ ++ True ++ True ++ max_opacity_adjustement ++ on ++ False ++ 0 ++ 0 ++ 2 ++ right ++ ++ ++ ++ ++ True ++ True ++ 1 ++ ++ ++ ++ ++ False ++ True ++ 1 ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ False ++ True ++ 0 ++ ++ ++ ++ ++ 1000 ++ 50 ++ 250 ++ ++ ++ 10 ++ 0.25 ++ 1 ++ ++ ++ True ++ False ++ 12 ++ 12 ++ 12 ++ 12 ++ vertical ++ ++ ++ True ++ False ++ 0 ++ in ++ ++ ++ True ++ False ++ none ++ ++ ++ 100 ++ 80 ++ True ++ True ++ ++ ++ True ++ False ++ 12 ++ 12 ++ 12 ++ 12 ++ 32 ++ ++ ++ True ++ True ++ end ++ center ++ ++ ++ 1 ++ 0 ++ 2 ++ ++ ++ ++ ++ True ++ False ++ True ++ Number overlay ++ 0 ++ ++ ++ 0 ++ 0 ++ ++ ++ ++ ++ True ++ False ++ Temporarily show the application numbers over the icons, corresponding to the shortcut. ++ True ++ 40 ++ 0 ++ ++ ++ ++ 0 ++ 1 ++ ++ ++ ++ ++ ++ ++ ++ ++ 100 ++ 80 ++ True ++ True ++ ++ ++ True ++ False ++ 12 ++ 12 ++ 12 ++ 12 ++ 32 ++ ++ ++ True ++ True ++ end ++ center ++ ++ ++ 1 ++ 0 ++ 2 ++ ++ ++ ++ ++ True ++ False ++ True ++ Show the dock if it is hidden ++ 0 ++ ++ ++ 0 ++ 0 ++ ++ ++ ++ ++ True ++ False ++ If using autohide, the dock will appear for a short time when triggering the shortcut. ++ True ++ 40 ++ 0 ++ ++ ++ ++ 0 ++ 1 ++ ++ ++ ++ ++ ++ ++ ++ ++ 100 ++ 80 ++ True ++ True ++ ++ ++ True ++ False ++ 12 ++ 12 ++ 12 ++ 12 ++ 32 ++ ++ ++ False ++ center ++ 12 ++ ++ ++ 1 ++ 0 ++ ++ ++ ++ ++ True ++ False ++ True ++ Shortcut for the options above ++ 0 ++ ++ ++ 0 ++ 0 ++ ++ ++ ++ ++ True ++ False ++ Syntax: <Shift>, <Ctrl>, <Alt>, <Super> ++ True ++ 40 ++ 0 ++ ++ ++ ++ 0 ++ 1 ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ True ++ True ++ ++ ++ True ++ False ++ 12 ++ 12 ++ 12 ++ 12 ++ True ++ 6 ++ 32 ++ ++ ++ True ++ True ++ end ++ shortcut_time_adjustment ++ 3 ++ ++ ++ 1 ++ 0 ++ ++ ++ ++ ++ True ++ False ++ True ++ Hide timeout (s) ++ 0 ++ ++ ++ 0 ++ 0 ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ False ++ True ++ 0 ++ ++ ++ ++ ++ 1 ++ 0.050000000000000003 ++ 0.25 ++ ++ ++ True ++ False ++ 12 ++ 12 ++ 12 ++ 12 ++ vertical ++ ++ ++ True ++ False ++ 0 ++ in ++ ++ ++ True ++ False ++ none ++ ++ ++ True ++ True ++ ++ ++ True ++ False ++ 12 ++ 12 ++ 12 ++ 12 ++ 32 ++ ++ ++ True ++ False ++ True ++ Show the dock by mouse hover on the screen edge. ++ True ++ 0 ++ ++ ++ ++ 0 ++ 1 ++ ++ ++ ++ ++ True ++ False ++ True ++ Autohide ++ 0 ++ ++ ++ 0 ++ 0 ++ ++ ++ ++ ++ True ++ True ++ end ++ center ++ ++ ++ 1 ++ 0 ++ 2 ++ ++ ++ ++ ++ Push to show: require pressure to show the dock ++ True ++ True ++ False ++ 0 ++ True ++ ++ ++ 0 ++ 3 ++ 2 ++ ++ ++ ++ ++ Enable in fullscreen mode ++ True ++ True ++ False ++ 12 ++ 0 ++ True ++ ++ ++ 0 ++ 2 ++ 2 ++ ++ ++ ++ ++ ++ ++ ++ ++ True ++ True ++ ++ ++ True ++ False ++ 12 ++ 12 ++ 12 ++ 12 ++ 32 ++ ++ ++ True ++ False ++ True ++ Show the dock when it doesn't obstruct application windows. ++ True ++ 0 ++ ++ ++ ++ 0 ++ 1 ++ ++ ++ ++ ++ True ++ False ++ True ++ Dodge windows ++ 0 ++ ++ ++ 0 ++ 0 ++ ++ ++ ++ ++ True ++ True ++ end ++ center ++ ++ ++ 1 ++ 0 ++ 2 ++ ++ ++ ++ ++ True ++ False ++ vertical ++ ++ ++ All windows ++ True ++ True ++ False ++ 12 ++ 0 ++ True ++ True ++ ++ ++ ++ False ++ True ++ 0 ++ ++ ++ ++ ++ Only focused application's windows ++ True ++ True ++ False ++ 0 ++ True ++ True ++ all_windows_radio_button ++ ++ ++ ++ False ++ True ++ 1 ++ ++ ++ ++ ++ Only maximized windows ++ True ++ True ++ False ++ 0 ++ True ++ True ++ all_windows_radio_button ++ ++ ++ ++ False ++ True ++ 2 ++ ++ ++ ++ ++ 0 ++ 2 ++ 2 ++ ++ ++ ++ ++ ++ ++ ++ ++ True ++ True ++ ++ ++ True ++ False ++ 12 ++ 12 ++ 12 ++ 12 ++ True ++ 6 ++ 32 ++ ++ ++ True ++ True ++ end ++ animation_time_adjustment ++ 3 ++ ++ ++ 1 ++ 0 ++ ++ ++ ++ ++ True ++ False ++ True ++ Animation duration (s) ++ 0 ++ ++ ++ 0 ++ 0 ++ ++ ++ ++ ++ True ++ True ++ end ++ hide_timeout_adjustment ++ 3 ++ ++ ++ 1 ++ 1 ++ ++ ++ ++ ++ True ++ True ++ end ++ show_timeout_adjustment ++ 3 ++ ++ ++ 1 ++ 2 ++ ++ ++ ++ ++ True ++ True ++ 0.000 ++ pressure_threshold_adjustment ++ ++ ++ 1 ++ 3 ++ ++ ++ ++ ++ True ++ False ++ True ++ Hide timeout (s) ++ 0 ++ ++ ++ 0 ++ 1 ++ ++ ++ ++ ++ True ++ False ++ True ++ Show timeout (s) ++ 0 ++ ++ ++ 0 ++ 2 ++ ++ ++ ++ ++ True ++ False ++ True ++ Pressure threshold ++ 0 ++ ++ ++ 0 ++ 3 ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ False ++ True ++ 0 ++ ++ ++ ++ +diff --git a/extensions/dash-to-dock/appIconIndicators.js b/extensions/dash-to-dock/appIconIndicators.js +new file mode 100644 +index 0000000..6eb0706 +--- /dev/null ++++ b/extensions/dash-to-dock/appIconIndicators.js +@@ -0,0 +1,1124 @@ ++const Cogl = imports.gi.Cogl; ++const Cairo = imports.cairo; ++const Clutter = imports.gi.Clutter; ++const GdkPixbuf = imports.gi.GdkPixbuf ++const Gio = imports.gi.Gio; ++const Gtk = imports.gi.Gtk; ++const Lang = imports.lang; ++const Pango = imports.gi.Pango; ++const Shell = imports.gi.Shell; ++const St = imports.gi.St; ++ ++const Util = imports.misc.util; ++ ++const Me = imports.misc.extensionUtils.getCurrentExtension(); ++const Utils = Me.imports.utils; ++ ++let tracker = Shell.WindowTracker.get_default(); ++ ++const RunningIndicatorStyle = { ++ DEFAULT: 0, ++ DOTS: 1, ++ SQUARES: 2, ++ DASHES: 3, ++ SEGMENTED: 4, ++ SOLID: 5, ++ CILIORA: 6, ++ METRO: 7 ++}; ++ ++const MAX_WINDOWS_CLASSES = 4; ++ ++ ++/* ++ * This is the main indicator class to be used. The desired bahviour is ++ * obtained by composing the desired classes below based on the settings. ++ * ++ */ ++var AppIconIndicator = new Lang.Class({ ++ ++ Name: 'DashToDock.AppIconIndicator', ++ ++ _init: function(source, settings) { ++ this._indicators = []; ++ ++ // Unity indicators always enabled for now ++ let unityIndicator = new UnityIndicator(source, settings); ++ this._indicators.push(unityIndicator); ++ ++ // Choose the style for the running indicators ++ let runningIndicator = null; ++ let runningIndicatorStyle; ++ ++ if (settings.get_boolean('apply-custom-theme' )) { ++ runningIndicatorStyle = RunningIndicatorStyle.DOTS; ++ } else { ++ runningIndicatorStyle = settings.get_enum('running-indicator-style'); ++ } ++ ++ switch (runningIndicatorStyle) { ++ case RunningIndicatorStyle.DEFAULT: ++ runningIndicator = new RunningIndicatorBase(source, settings); ++ break; ++ ++ case RunningIndicatorStyle.DOTS: ++ runningIndicator = new RunningIndicatorDots(source, settings); ++ break; ++ ++ case RunningIndicatorStyle.SQUARES: ++ runningIndicator = new RunningIndicatorSquares(source, settings); ++ break; ++ ++ case RunningIndicatorStyle.DASHES: ++ runningIndicator = new RunningIndicatorDashes(source, settings); ++ break; ++ ++ case RunningIndicatorStyle.SEGMENTED: ++ runningIndicator = new RunningIndicatorSegmented(source, settings); ++ break; ++ ++ case RunningIndicatorStyle.SOLID: ++ runningIndicator = new RunningIndicatorSolid(source, settings); ++ break; ++ ++ case RunningIndicatorStyle.CILIORA: ++ runningIndicator = new RunningIndicatorCiliora(source, settings); ++ break; ++ ++ case RunningIndicatorStyle.METRO: ++ runningIndicator = new RunningIndicatorMetro(source, settings); ++ break; ++ ++ default: ++ runningIndicator = new RunningIndicatorBase(source, settings); ++ } ++ ++ this._indicators.push(runningIndicator); ++ }, ++ ++ update: function() { ++ for (let i=0; i 0) ++ this._isFocused = true; ++ else ++ this._isFocused = false; ++ ++ // In the case of workspace isolation, we need to hide the dots of apps with ++ // no windows in the current workspace ++ if (this._source.app.state != Shell.AppState.STOPPED && this._nWindows > 0) ++ this._isRunning = true; ++ else ++ this._isRunning = false; ++ ++ this._updateCounterClass(); ++ this._updateFocusClass(); ++ this._updateDefaultDot(); ++ }, ++ ++ _updateCounterClass: function() { ++ for (let i = 1; i <= MAX_WINDOWS_CLASSES; i++) { ++ let className = 'running' + i; ++ if (i != this._nWindows) ++ this._source.actor.remove_style_class_name(className); ++ else ++ this._source.actor.add_style_class_name(className); ++ } ++ }, ++ ++ _updateFocusClass: function() { ++ if (this._isFocused) ++ this._source.actor.add_style_class_name('focused'); ++ else ++ this._source.actor.remove_style_class_name('focused'); ++ }, ++ ++ _updateDefaultDot: function() { ++ if (this._isRunning) ++ this._source._dot.show(); ++ else ++ this._source._dot.hide(); ++ }, ++ ++ _hideDefaultDot: function() { ++ // I use opacity to hide the default dot because the show/hide function ++ // are used by the parent class. ++ this._source._dot.opacity = 0; ++ }, ++ ++ _restoreDefaultDot: function() { ++ this._source._dot.opacity = 255; ++ }, ++ ++ _enableBacklight: function() { ++ ++ let colorPalette = this._dominantColorExtractor._getColorPalette(); ++ ++ // Fallback ++ if (colorPalette === null) { ++ this._source._iconContainer.set_style( ++ 'border-radius: 5px;' + ++ 'background-gradient-direction: vertical;' + ++ 'background-gradient-start: #e0e0e0;' + ++ 'background-gradient-end: darkgray;' ++ ); ++ ++ return; ++ } ++ ++ this._source._iconContainer.set_style( ++ 'border-radius: 5px;' + ++ 'background-gradient-direction: vertical;' + ++ 'background-gradient-start: ' + colorPalette.original + ';' + ++ 'background-gradient-end: ' + colorPalette.darker + ';' ++ ); ++ ++ }, ++ ++ _disableBacklight: function() { ++ this._source._iconContainer.set_style(null); ++ }, ++ ++ destroy: function() { ++ this.parent(); ++ this._disableBacklight(); ++ // Remove glossy background if the children still exists ++ if (this._source._iconContainer.get_children().length > 1) ++ this._source._iconContainer.get_children()[1].set_style(null); ++ this._restoreDefaultDot(); ++ } ++}); ++ ++ ++const RunningIndicatorDots = new Lang.Class({ ++ ++ Name: 'DashToDock.RunningIndicatorDots', ++ Extends: RunningIndicatorBase, ++ ++ _init: function(source, settings) { ++ ++ this.parent(source, settings) ++ ++ this._hideDefaultDot(); ++ ++ this._area = new St.DrawingArea({x_expand: true, y_expand: true}); ++ ++ // We draw for the bottom case and rotate the canvas for other placements ++ //set center of rotatoins to the center ++ this._area.set_pivot_point(0.5, 0.5); ++ // prepare transformation matrix ++ let m = new Cogl.Matrix(); ++ m.init_identity(); ++ ++ switch (this._side) { ++ case St.Side.TOP: ++ m.xx = -1; ++ m.rotate(180, 0, 0, 1); ++ break ++ ++ case St.Side.BOTTOM: ++ // nothing ++ break; ++ ++ case St.Side.LEFT: ++ m.yy = -1; ++ m.rotate(90, 0, 0, 1); ++ break; ++ ++ case St.Side.RIGHT: ++ m.rotate(-90, 0, 0, 1); ++ break ++ } ++ ++ this._area.set_transform(m); ++ ++ this._area.connect('repaint', Lang.bind(this, this._updateIndicator)); ++ this._source._iconContainer.add_child(this._area); ++ ++ let keys = ['custom-theme-running-dots-color', ++ 'custom-theme-running-dots-border-color', ++ 'custom-theme-running-dots-border-width', ++ 'custom-theme-customize-running-dots', ++ 'unity-backlit-items', ++ 'running-indicator-dominant-color']; ++ ++ keys.forEach(function(key) { ++ this._signalsHandler.add([ ++ this._settings, ++ 'changed::' + key, ++ Lang.bind(this, this.update) ++ ]); ++ }, this); ++ ++ // Apply glossy background ++ // TODO: move to enable/disableBacklit to apply itonly to the running apps? ++ // TODO: move to css class for theming support ++ let path = imports.misc.extensionUtils.getCurrentExtension().path; ++ this._glossyBackgroundStyle = 'background-image: url(\'' + path + '/media/glossy.svg\');' + ++ 'background-size: contain;'; ++ ++ }, ++ ++ update: function() { ++ this.parent(); ++ ++ // Enable / Disable the backlight of running apps ++ if (!this._settings.get_boolean('apply-custom-theme') && this._settings.get_boolean('unity-backlit-items')) { ++ this._source._iconContainer.get_children()[1].set_style(this._glossyBackgroundStyle); ++ if (this._isRunning) ++ this._enableBacklight(); ++ else ++ this._disableBacklight(); ++ } else { ++ this._disableBacklight(); ++ this._source._iconContainer.get_children()[1].set_style(null); ++ } ++ ++ if (this._area) ++ this._area.queue_repaint(); ++ }, ++ ++ _computeStyle: function() { ++ ++ let [width, height] = this._area.get_surface_size(); ++ this._width = height; ++ this._height = width; ++ ++ // By defaut re-use the style - background color, and border width and color - ++ // of the default dot ++ let themeNode = this._source._dot.get_theme_node(); ++ this._borderColor = themeNode.get_border_color(this._side); ++ this._borderWidth = themeNode.get_border_width(this._side); ++ this._bodyColor = themeNode.get_background_color(); ++ ++ if (!this._settings.get_boolean('apply-custom-theme')) { ++ // Adjust for the backlit case ++ if (this._settings.get_boolean('unity-backlit-items')) { ++ // Use dominant color for dots too if the backlit is enables ++ let colorPalette = this._dominantColorExtractor._getColorPalette(); ++ ++ // Slightly adjust the styling ++ this._borderWidth = 2; ++ ++ if (colorPalette !== null) { ++ this._borderColor = Clutter.color_from_string(colorPalette.lighter)[1] ; ++ this._bodyColor = Clutter.color_from_string(colorPalette.darker)[1]; ++ } else { ++ // Fallback ++ this._borderColor = Clutter.color_from_string('white')[1]; ++ this._bodyColor = Clutter.color_from_string('gray')[1]; ++ } ++ } ++ ++ // Apply dominant color if requested ++ if (this._settings.get_boolean('running-indicator-dominant-color')) { ++ let colorPalette = this._dominantColorExtractor._getColorPalette(); ++ if (colorPalette !== null) { ++ this._bodyColor = Clutter.color_from_string(colorPalette.original)[1]; ++ } ++ } ++ ++ // Finally, use customize style if requested ++ if (this._settings.get_boolean('custom-theme-customize-running-dots')) { ++ this._borderColor = Clutter.color_from_string(this._settings.get_string('custom-theme-running-dots-border-color'))[1]; ++ this._borderWidth = this._settings.get_int('custom-theme-running-dots-border-width'); ++ this._bodyColor = Clutter.color_from_string(this._settings.get_string('custom-theme-running-dots-color'))[1]; ++ } ++ } ++ ++ // Define the radius as an arbitrary size, but keep large enough to account ++ // for the drawing of the border. ++ this._radius = Math.max(this._width/22, this._borderWidth/2); ++ this._padding = 0; // distance from the margin ++ this._spacing = this._radius + this._borderWidth; // separation between the dots ++ }, ++ ++ _updateIndicator: function() { ++ ++ let area = this._area; ++ let cr = this._area.get_context(); ++ ++ this._computeStyle(); ++ this._drawIndicator(cr); ++ cr.$dispose(); ++ }, ++ ++ _drawIndicator: function(cr) { ++ // Draw the required numbers of dots ++ let n = this._nWindows; ++ ++ cr.setLineWidth(this._borderWidth); ++ Clutter.cairo_set_source_color(cr, this._borderColor); ++ ++ // draw for the bottom case: ++ cr.translate((this._width - (2*n)*this._radius - (n-1)*this._spacing)/2, this._height - this._padding); ++ for (let i = 0; i < n; i++) { ++ cr.newSubPath(); ++ cr.arc((2*i+1)*this._radius + i*this._spacing, -this._radius - this._borderWidth/2, this._radius, 0, 2*Math.PI); ++ } ++ ++ cr.strokePreserve(); ++ Clutter.cairo_set_source_color(cr, this._bodyColor); ++ cr.fill(); ++ }, ++ ++ destroy: function() { ++ this.parent(); ++ this._area.destroy(); ++ } ++ ++}); ++ ++// Adapted from dash-to-panel by Jason DeRose ++// https://github.com/jderose9/dash-to-panel ++const RunningIndicatorCiliora = new Lang.Class({ ++ ++ Name: 'DashToDock.RunningIndicatorCiliora', ++ Extends: RunningIndicatorDots, ++ ++ _drawIndicator: function(cr) { ++ if (this._isRunning) { ++ ++ let size = Math.max(this._width/20, this._borderWidth); ++ let spacing = size; // separation between the dots ++ let lineLength = this._width - (size*(this._nWindows-1)) - (spacing*(this._nWindows-1)); ++ let padding = this._borderWidth; ++ // For the backlit case here we don't want the outer border visible ++ if (this._settings.get_boolean('unity-backlit-items') && !this._settings.get_boolean('custom-theme-customize-running-dots')) ++ padding = 0; ++ let yOffset = this._height - padding - size; ++ ++ cr.setLineWidth(this._borderWidth); ++ Clutter.cairo_set_source_color(cr, this._borderColor); ++ ++ cr.translate(0, yOffset); ++ cr.newSubPath(); ++ cr.rectangle(0, 0, lineLength, size); ++ for (let i = 1; i < this._nWindows; i++) { ++ cr.newSubPath(); ++ cr.rectangle(lineLength + (i*spacing) + ((i-1)*size), 0, size, size); ++ } ++ ++ cr.strokePreserve(); ++ Clutter.cairo_set_source_color(cr, this._bodyColor); ++ cr.fill(); ++ } ++ } ++}); ++ ++// Adapted from dash-to-panel by Jason DeRose ++// https://github.com/jderose9/dash-to-panel ++const RunningIndicatorSegmented = new Lang.Class({ ++ ++ Name: 'DashToDock.RunningIndicatorSegmented', ++ Extends: RunningIndicatorDots, ++ ++ _drawIndicator: function(cr) { ++ if (this._isRunning) { ++ let size = Math.max(this._width/20, this._borderWidth); ++ let spacing = Math.ceil(this._width/18); // separation between the dots ++ let dashLength = Math.ceil((this._width - ((this._nWindows-1)*spacing))/this._nWindows); ++ let lineLength = this._width - (size*(this._nWindows-1)) - (spacing*(this._nWindows-1)); ++ let padding = this._borderWidth; ++ // For the backlit case here we don't want the outer border visible ++ if (this._settings.get_boolean('unity-backlit-items') && !this._settings.get_boolean('custom-theme-customize-running-dots')) ++ padding = 0; ++ let yOffset = this._height - padding - size; ++ ++ cr.setLineWidth(this._borderWidth); ++ Clutter.cairo_set_source_color(cr, this._borderColor); ++ ++ cr.translate(0, yOffset); ++ for (let i = 0; i < this._nWindows; i++) { ++ cr.newSubPath(); ++ cr.rectangle(i*dashLength + i*spacing, 0, dashLength, size); ++ } ++ ++ cr.strokePreserve(); ++ Clutter.cairo_set_source_color(cr, this._bodyColor); ++ cr.fill() ++ } ++ } ++}); ++ ++// Adapted from dash-to-panel by Jason DeRose ++// https://github.com/jderose9/dash-to-panel ++const RunningIndicatorSolid = new Lang.Class({ ++ ++ Name: 'DashToDock.RunningIndicatorSolid', ++ Extends: RunningIndicatorDots, ++ ++ _drawIndicator: function(cr) { ++ if (this._isRunning) { ++ ++ let size = Math.max(this._width/20, this._borderWidth); ++ let padding = this._borderWidth; ++ // For the backlit case here we don't want the outer border visible ++ if (this._settings.get_boolean('unity-backlit-items') && !this._settings.get_boolean('custom-theme-customize-running-dots')) ++ padding = 0; ++ let yOffset = this._height - padding - size; ++ ++ cr.setLineWidth(this._borderWidth); ++ Clutter.cairo_set_source_color(cr, this._borderColor); ++ ++ cr.translate(0, yOffset); ++ cr.newSubPath(); ++ cr.rectangle(0, 0, this._width, size); ++ ++ cr.strokePreserve(); ++ Clutter.cairo_set_source_color(cr, this._bodyColor); ++ cr.fill(); ++ ++ } ++ } ++}); ++ ++// Adapted from dash-to-panel by Jason DeRose ++// https://github.com/jderose9/dash-to-panel ++const RunningIndicatorSquares = new Lang.Class({ ++ ++ Name: 'DashToDock.RunningIndicatorSquares', ++ Extends: RunningIndicatorDots, ++ ++ _drawIndicator: function(cr) { ++ if (this._isRunning) { ++ let size = Math.max(this._width/11, this._borderWidth); ++ let padding = this._borderWidth; ++ let spacing = Math.ceil(this._width/18); // separation between the dots ++ let yOffset = this._height - padding - size; ++ ++ cr.setLineWidth(this._borderWidth); ++ Clutter.cairo_set_source_color(cr, this._borderColor); ++ ++ cr.translate(Math.floor((this._width - this._nWindows*size - (this._nWindows-1)*spacing)/2), yOffset); ++ for (let i = 0; i < this._nWindows; i++) { ++ cr.newSubPath(); ++ cr.rectangle(i*size + i*spacing, 0, size, size); ++ } ++ cr.strokePreserve(); ++ Clutter.cairo_set_source_color(cr, this._bodyColor); ++ cr.fill(); ++ } ++ } ++}); ++ ++// Adapted from dash-to-panel by Jason DeRose ++// https://github.com/jderose9/dash-to-panel ++const RunningIndicatorDashes = new Lang.Class({ ++ ++ Name: 'DashToDock.RunningIndicatorDashes', ++ Extends: RunningIndicatorDots, ++ ++ _drawIndicator: function(cr) { ++ if (this._isRunning) { ++ let size = Math.max(this._width/20, this._borderWidth); ++ let padding = this._borderWidth; ++ let spacing = Math.ceil(this._width/18); // separation between the dots ++ let dashLength = Math.floor(this._width/4) - spacing; ++ let yOffset = this._height - padding - size; ++ ++ cr.setLineWidth(this._borderWidth); ++ Clutter.cairo_set_source_color(cr, this._borderColor); ++ ++ cr.translate(Math.floor((this._width - this._nWindows*dashLength - (this._nWindows-1)*spacing)/2), yOffset); ++ for (let i = 0; i < this._nWindows; i++) { ++ cr.newSubPath(); ++ cr.rectangle(i*dashLength + i*spacing, 0, dashLength, size); ++ } ++ ++ cr.strokePreserve(); ++ Clutter.cairo_set_source_color(cr, this._bodyColor); ++ cr.fill(); ++ } ++ } ++}); ++ ++// Adapted from dash-to-panel by Jason DeRose ++// https://github.com/jderose9/dash-to-panel ++const RunningIndicatorMetro = new Lang.Class({ ++ ++ Name: 'DashToDock.RunningIndicatorMetro', ++ Extends: RunningIndicatorDots, ++ ++ _init: function(source, settings) { ++ this.parent(source, settings); ++ this._source.actor.add_style_class_name('metro'); ++ }, ++ ++ _drawIndicator: function(cr) { ++ if (this._isRunning) { ++ let size = Math.max(this._width/20, this._borderWidth); ++ let padding = 0; ++ // For the backlit case here we don't want the outer border visible ++ if (this._settings.get_boolean('unity-backlit-items') && !this._settings.get_boolean('custom-theme-customize-running-dots')) ++ padding = 0; ++ let yOffset = this._height - padding - size; ++ ++ let n = this._nWindows; ++ if(n <= 1) { ++ cr.translate(0, yOffset); ++ Clutter.cairo_set_source_color(cr, this._bodyColor); ++ cr.newSubPath(); ++ cr.rectangle(0, 0, this._width, size); ++ cr.fill(); ++ } else { ++ let blackenedLength = (1/48)*this._width; // need to scale with the SVG for the stacked highlight ++ let darkenedLength = this._isFocused ? (2/48)*this._width : (10/48)*this._width; ++ let blackenedColor = this._bodyColor.shade(.3); ++ let darkenedColor = this._bodyColor.shade(.7); ++ ++ cr.translate(0, yOffset); ++ ++ Clutter.cairo_set_source_color(cr, this._bodyColor); ++ cr.newSubPath(); ++ cr.rectangle(0, 0, this._width - darkenedLength - blackenedLength, size); ++ cr.fill(); ++ Clutter.cairo_set_source_color(cr, blackenedColor); ++ cr.newSubPath(); ++ cr.rectangle(this._width - darkenedLength - blackenedLength, 0, 1, size); ++ cr.fill(); ++ Clutter.cairo_set_source_color(cr, darkenedColor); ++ cr.newSubPath(); ++ cr.rectangle(this._width - darkenedLength, 0, darkenedLength, size); ++ cr.fill(); ++ } ++ } ++ }, ++ ++ destroy: function() { ++ this.parent(); ++ this._source.actor.remove_style_class_name('metro'); ++ } ++}); ++ ++/* ++ * Unity like notification and progress indicators ++ */ ++const UnityIndicator = new Lang.Class({ ++ Name: 'DashToDock.UnityIndicator', ++ Extends: IndicatorBase, ++ ++ _init: function(source, settings) { ++ ++ this.parent(source, settings); ++ ++ this._notificationBadgeLabel = new St.Label(); ++ this._notificationBadgeBin = new St.Bin({ ++ child: this._notificationBadgeLabel, ++ x_align: St.Align.END, y_align: St.Align.START, ++ x_expand: true, y_expand: true ++ }); ++ this._notificationBadgeLabel.add_style_class_name('notification-badge'); ++ this._notificationBadgeCount = 0; ++ this._notificationBadgeBin.hide(); ++ ++ this._source._iconContainer.add_child(this._notificationBadgeBin); ++ this._source._iconContainer.connect('allocation-changed', Lang.bind(this, this.updateNotificationBadge)); ++ ++ this._remoteEntries = []; ++ this._source.remoteModel.lookupById(this._source.app.id).forEach( ++ Lang.bind(this, function(entry) { ++ this.insertEntryRemote(entry); ++ }) ++ ); ++ ++ this._signalsHandler.add([ ++ this._source.remoteModel, ++ 'entry-added', ++ Lang.bind(this, this._onLauncherEntryRemoteAdded) ++ ], [ ++ this._source.remoteModel, ++ 'entry-removed', ++ Lang.bind(this, this._onLauncherEntryRemoteRemoved) ++ ]) ++ }, ++ ++ _onLauncherEntryRemoteAdded: function(remoteModel, entry) { ++ if (!entry || !entry.appId()) ++ return; ++ if (this._source && this._source.app && this._source.app.id == entry.appId()) { ++ this.insertEntryRemote(entry); ++ } ++ }, ++ ++ _onLauncherEntryRemoteRemoved: function(remoteModel, entry) { ++ if (!entry || !entry.appId()) ++ return; ++ ++ if (this._source && this._source.app && this._source.app.id == entry.appId()) { ++ this.removeEntryRemote(entry); ++ } ++ }, ++ ++ updateNotificationBadge: function() { ++ let scaleFactor = St.ThemeContext.get_for_stage(global.stage).scale_factor; ++ let [minWidth, natWidth] = this._source._iconContainer.get_preferred_width(-1); ++ let logicalNatWidth = natWidth / scaleFactor; ++ let font_size = Math.max(10, Math.round(logicalNatWidth / 5)); ++ let margin_left = Math.round(logicalNatWidth / 4); ++ ++ this._notificationBadgeLabel.set_style( ++ 'font-size: ' + font_size + 'px;' + ++ 'margin-left: ' + margin_left + 'px;' ++ ); ++ ++ this._notificationBadgeBin.width = Math.round(logicalNatWidth - margin_left); ++ this._notificationBadgeLabel.clutter_text.ellipsize = Pango.EllipsizeMode.MIDDLE; ++ }, ++ ++ _notificationBadgeCountToText: function(count) { ++ if (count <= 9999) { ++ return count.toString(); ++ } else if (count < 1e5) { ++ let thousands = count / 1e3; ++ return thousands.toFixed(1).toString() + "k"; ++ } else if (count < 1e6) { ++ let thousands = count / 1e3; ++ return thousands.toFixed(0).toString() + "k"; ++ } else if (count < 1e8) { ++ let millions = count / 1e6; ++ return millions.toFixed(1).toString() + "M"; ++ } else if (count < 1e9) { ++ let millions = count / 1e6; ++ return millions.toFixed(0).toString() + "M"; ++ } else { ++ let billions = count / 1e9; ++ return billions.toFixed(1).toString() + "B"; ++ } ++ }, ++ ++ setNotificationBadge: function(count) { ++ this._notificationBadgeCount = count; ++ let text = this._notificationBadgeCountToText(count); ++ this._notificationBadgeLabel.set_text(text); ++ }, ++ ++ toggleNotificationBadge: function(activate) { ++ if (activate && this._notificationBadgeCount > 0) { ++ this.updateNotificationBadge(); ++ this._notificationBadgeBin.show(); ++ } ++ else ++ this._notificationBadgeBin.hide(); ++ }, ++ ++ _showProgressOverlay: function() { ++ if (this._progressOverlayArea) { ++ this._updateProgressOverlay(); ++ return; ++ } ++ ++ this._progressOverlayArea = new St.DrawingArea({x_expand: true, y_expand: true}); ++ this._progressOverlayArea.connect('repaint', Lang.bind(this, function() { ++ this._drawProgressOverlay(this._progressOverlayArea); ++ })); ++ ++ this._source._iconContainer.add_child(this._progressOverlayArea); ++ this._updateProgressOverlay(); ++ }, ++ ++ _hideProgressOverlay: function() { ++ if (this._progressOverlayArea) ++ this._progressOverlayArea.destroy(); ++ this._progressOverlayArea = null; ++ }, ++ ++ _updateProgressOverlay: function() { ++ if (this._progressOverlayArea) ++ this._progressOverlayArea.queue_repaint(); ++ }, ++ ++ _drawProgressOverlay: function(area) { ++ let scaleFactor = St.ThemeContext.get_for_stage(global.stage).scale_factor; ++ let [surfaceWidth, surfaceHeight] = area.get_surface_size(); ++ let cr = area.get_context(); ++ ++ let iconSize = this._source.icon.iconSize * scaleFactor; ++ ++ let x = Math.floor((surfaceWidth - iconSize) / 2); ++ let y = Math.floor((surfaceHeight - iconSize) / 2); ++ ++ let lineWidth = Math.floor(1.0 * scaleFactor); ++ let padding = Math.floor(iconSize * 0.05); ++ let width = iconSize - 2.0*padding; ++ let height = Math.floor(Math.min(18.0*scaleFactor, 0.20*iconSize)); ++ x += padding; ++ y += iconSize - height - padding; ++ ++ cr.setLineWidth(lineWidth); ++ ++ // Draw the outer stroke ++ let stroke = new Cairo.LinearGradient(0, y, 0, y + height); ++ let fill = null; ++ stroke.addColorStopRGBA(0.5, 0.5, 0.5, 0.5, 0.1); ++ stroke.addColorStopRGBA(0.9, 0.8, 0.8, 0.8, 0.4); ++ Utils.drawRoundedLine(cr, x + lineWidth/2.0, y + lineWidth/2.0, width, height, true, true, stroke, fill); ++ ++ // Draw the background ++ x += lineWidth; ++ y += lineWidth; ++ width -= 2.0*lineWidth; ++ height -= 2.0*lineWidth; ++ ++ stroke = Cairo.SolidPattern.createRGBA(0.20, 0.20, 0.20, 0.9); ++ fill = new Cairo.LinearGradient(0, y, 0, y + height); ++ fill.addColorStopRGBA(0.4, 0.25, 0.25, 0.25, 1.0); ++ fill.addColorStopRGBA(0.9, 0.35, 0.35, 0.35, 1.0); ++ Utils.drawRoundedLine(cr, x + lineWidth/2.0, y + lineWidth/2.0, width, height, true, true, stroke, fill); ++ ++ // Draw the finished bar ++ x += lineWidth; ++ y += lineWidth; ++ width -= 2.0*lineWidth; ++ height -= 2.0*lineWidth; ++ ++ let finishedWidth = Math.ceil(this._progress * width); ++ stroke = Cairo.SolidPattern.createRGBA(0.8, 0.8, 0.8, 1.0); ++ fill = Cairo.SolidPattern.createRGBA(0.9, 0.9, 0.9, 1.0); ++ ++ if (Clutter.get_default_text_direction() == Clutter.TextDirection.RTL) ++ Utils.drawRoundedLine(cr, x + lineWidth/2.0 + width - finishedWidth, y + lineWidth/2.0, finishedWidth, height, true, true, stroke, fill); ++ else ++ Utils.drawRoundedLine(cr, x + lineWidth/2.0, y + lineWidth/2.0, finishedWidth, height, true, true, stroke, fill); ++ ++ cr.$dispose(); ++ }, ++ ++ setProgress: function(progress) { ++ this._progress = Math.min(Math.max(progress, 0.0), 1.0); ++ this._updateProgressOverlay(); ++ }, ++ ++ toggleProgressOverlay: function(activate) { ++ if (activate) { ++ this._showProgressOverlay(); ++ } ++ else { ++ this._hideProgressOverlay(); ++ } ++ }, ++ ++ insertEntryRemote: function(remote) { ++ if (!remote || this._remoteEntries.indexOf(remote) !== -1) ++ return; ++ ++ this._remoteEntries.push(remote); ++ this._selectEntryRemote(remote); ++ }, ++ ++ removeEntryRemote: function(remote) { ++ if (!remote || this._remoteEntries.indexOf(remote) == -1) ++ return; ++ ++ this._remoteEntries.splice(this._remoteEntries.indexOf(remote), 1); ++ ++ if (this._remoteEntries.length > 0) { ++ this._selectEntryRemote(this._remoteEntries[this._remoteEntries.length-1]); ++ } else { ++ this.setNotificationBadge(0); ++ this.toggleNotificationBadge(false); ++ this.setProgress(0); ++ this.toggleProgressOverlay(false); ++ } ++ }, ++ ++ _selectEntryRemote: function(remote) { ++ if (!remote) ++ return; ++ ++ this._signalsHandler.removeWithLabel('entry-remotes'); ++ ++ this._signalsHandler.addWithLabel('entry-remotes', ++ [ ++ remote, ++ 'count-changed', ++ Lang.bind(this, (remote, value) => { ++ this.setNotificationBadge(value); ++ }) ++ ], [ ++ remote, ++ 'count-visible-changed', ++ Lang.bind(this, (remote, value) => { ++ this.toggleNotificationBadge(value); ++ }) ++ ], [ ++ remote, ++ 'progress-changed', ++ Lang.bind(this, (remote, value) => { ++ this.setProgress(value); ++ }) ++ ], [ ++ remote, ++ 'progress-visible-changed', ++ Lang.bind(this, (remote, value) => { ++ this.toggleProgressOverlay(value); ++ }) ++ ]); ++ ++ this.setNotificationBadge(remote.count()); ++ this.toggleNotificationBadge(remote.countVisible()); ++ this.setProgress(remote.progress()); ++ this.toggleProgressOverlay(remote.progressVisible()); ++ } ++}); ++ ++ ++// We need an icons theme object, this is the only way I managed to get ++// pixel buffers that can be used for calculating the backlight color ++let themeLoader = null; ++ ++// Global icon cache. Used for Unity7 styling. ++let iconCacheMap = new Map(); ++// Max number of items to store ++// We don't expect to ever reach this number, but let's put an hard limit to avoid ++// even the remote possibility of the cached items to grow indefinitely. ++const MAX_CACHED_ITEMS = 1000; ++// When the size exceed it, the oldest 'n' ones are deleted ++const BATCH_SIZE_TO_DELETE = 50; ++// The icon size used to extract the dominant color ++const DOMINANT_COLOR_ICON_SIZE = 64; ++ ++// Compute dominant color frim the app icon. ++// The color is cached for efficiency. ++const DominantColorExtractor = new Lang.Class({ ++ Name: 'DashToDock.DominantColorExtractor', ++ ++ _init: function(app) { ++ this._app = app; ++ }, ++ ++ /** ++ * Try to get the pixel buffer for the current icon, if not fail gracefully ++ */ ++ _getIconPixBuf: function() { ++ let iconTexture = this._app.create_icon_texture(16); ++ ++ if (themeLoader === null) { ++ let ifaceSettings = new Gio.Settings({ schema: "org.gnome.desktop.interface" }); ++ ++ themeLoader = new Gtk.IconTheme(), ++ themeLoader.set_custom_theme(ifaceSettings.get_string('icon-theme')); // Make sure the correct theme is loaded ++ } ++ ++ // Unable to load the icon texture, use fallback ++ if (iconTexture instanceof St.Icon === false) { ++ return null; ++ } ++ ++ iconTexture = iconTexture.get_gicon(); ++ ++ // Unable to load the icon texture, use fallback ++ if (iconTexture === null) { ++ return null; ++ } ++ ++ if (iconTexture instanceof Gio.FileIcon) { ++ // Use GdkPixBuf to load the pixel buffer from the provided file path ++ return GdkPixbuf.Pixbuf.new_from_file(iconTexture.get_file().get_path()); ++ } ++ ++ // Get the pixel buffer from the icon theme ++ let icon_info = themeLoader.lookup_icon(iconTexture.get_names()[0], DOMINANT_COLOR_ICON_SIZE, 0); ++ if (icon_info !== null) ++ return icon_info.load_icon(); ++ else ++ return null; ++ }, ++ ++ /** ++ * The backlight color choosing algorithm was mostly ported to javascript from the ++ * Unity7 C++ source of Canonicals: ++ * https://bazaar.launchpad.net/~unity-team/unity/trunk/view/head:/launcher/LauncherIcon.cpp ++ * so it more or less works the same way. ++ */ ++ _getColorPalette: function() { ++ if (iconCacheMap.get(this._app.get_id())) { ++ // We already know the answer ++ return iconCacheMap.get(this._app.get_id()); ++ } ++ ++ let pixBuf = this._getIconPixBuf(); ++ if (pixBuf == null) ++ return null; ++ ++ let pixels = pixBuf.get_pixels(), ++ offset = 0; ++ ++ let total = 0, ++ rTotal = 0, ++ gTotal = 0, ++ bTotal = 0; ++ ++ let resample_y = 1, ++ resample_x = 1; ++ ++ // Resampling of large icons ++ // We resample icons larger than twice the desired size, as the resampling ++ // to a size s ++ // DOMINANT_COLOR_ICON_SIZE < s < 2*DOMINANT_COLOR_ICON_SIZE, ++ // most of the case exactly DOMINANT_COLOR_ICON_SIZE as the icon size is tipycally ++ // a multiple of it. ++ let width = pixBuf.get_width(); ++ let height = pixBuf.get_height(); ++ ++ // Resample ++ if (height >= 2* DOMINANT_COLOR_ICON_SIZE) ++ resample_y = Math.floor(height/DOMINANT_COLOR_ICON_SIZE); ++ ++ if (width >= 2* DOMINANT_COLOR_ICON_SIZE) ++ resample_x = Math.floor(width/DOMINANT_COLOR_ICON_SIZE); ++ ++ if (resample_x !==1 || resample_y !== 1) ++ pixels = this._resamplePixels(pixels, resample_x, resample_y); ++ ++ // computing the limit outside the for (where it would be repeated at each iteration) ++ // for performance reasons ++ let limit = pixels.length; ++ for (let offset = 0; offset < limit; offset+=4) { ++ let r = pixels[offset], ++ g = pixels[offset + 1], ++ b = pixels[offset + 2], ++ a = pixels[offset + 3]; ++ ++ let saturation = (Math.max(r,g, b) - Math.min(r,g, b)); ++ let relevance = 0.1 * 255 * 255 + 0.9 * a * saturation; ++ ++ rTotal += r * relevance; ++ gTotal += g * relevance; ++ bTotal += b * relevance; ++ ++ total += relevance; ++ } ++ ++ total = total * 255; ++ ++ let r = rTotal / total, ++ g = gTotal / total, ++ b = bTotal / total; ++ ++ let hsv = Utils.ColorUtils.RGBtoHSV(r * 255, g * 255, b * 255); ++ ++ if (hsv.s > 0.15) ++ hsv.s = 0.65; ++ hsv.v = 0.90; ++ ++ let rgb = Utils.ColorUtils.HSVtoRGB(hsv.h, hsv.s, hsv.v); ++ ++ // Cache the result. ++ let backgroundColor = { ++ lighter: Utils.ColorUtils.ColorLuminance(rgb.r, rgb.g, rgb.b, 0.2), ++ original: Utils.ColorUtils.ColorLuminance(rgb.r, rgb.g, rgb.b, 0), ++ darker: Utils.ColorUtils.ColorLuminance(rgb.r, rgb.g, rgb.b, -0.5) ++ }; ++ ++ if (iconCacheMap.size >= MAX_CACHED_ITEMS) { ++ //delete oldest cached values (which are in order of insertions) ++ let ctr=0; ++ for (let key of iconCacheMap.keys()) { ++ if (++ctr > BATCH_SIZE_TO_DELETE) ++ break; ++ iconCacheMap.delete(key); ++ } ++ } ++ ++ iconCacheMap.set(this._app.get_id(), backgroundColor); ++ ++ return backgroundColor; ++ }, ++ ++ /** ++ * Downsample large icons before scanning for the backlight color to ++ * improve performance. ++ * ++ * @param pixBuf ++ * @param pixels ++ * @param resampleX ++ * @param resampleY ++ * ++ * @return []; ++ */ ++ _resamplePixels: function (pixels, resampleX, resampleY) { ++ let resampledPixels = []; ++ // computing the limit outside the for (where it would be repeated at each iteration) ++ // for performance reasons ++ let limit = pixels.length / (resampleX * resampleY) / 4; ++ for (let i = 0; i < limit; i++) { ++ let pixel = i * resampleX * resampleY; ++ ++ resampledPixels.push(pixels[pixel * 4]); ++ resampledPixels.push(pixels[pixel * 4 + 1]); ++ resampledPixels.push(pixels[pixel * 4 + 2]); ++ resampledPixels.push(pixels[pixel * 4 + 3]); ++ } ++ ++ return resampledPixels; ++ }, ++}) +diff --git a/extensions/dash-to-dock/appIcons.js b/extensions/dash-to-dock/appIcons.js +new file mode 100644 +index 0000000..ef9c6e1 +--- /dev/null ++++ b/extensions/dash-to-dock/appIcons.js +@@ -0,0 +1,1171 @@ ++// -*- mode: js; js-indent-level: 4; indent-tabs-mode: nil -*- ++ ++const Clutter = imports.gi.Clutter; ++const GdkPixbuf = imports.gi.GdkPixbuf ++const Gio = imports.gi.Gio; ++const GLib = imports.gi.GLib; ++const Gtk = imports.gi.Gtk; ++const Signals = imports.signals; ++const Lang = imports.lang; ++const Meta = imports.gi.Meta; ++const Shell = imports.gi.Shell; ++const St = imports.gi.St; ++const Mainloop = imports.mainloop; ++ ++// Use __ () and N__() for the extension gettext domain, and reuse ++// the shell domain with the default _() and N_() ++const Gettext = imports.gettext.domain('dashtodock'); ++const __ = Gettext.gettext; ++const N__ = function(e) { return e }; ++ ++const AppDisplay = imports.ui.appDisplay; ++const AppFavorites = imports.ui.appFavorites; ++const Dash = imports.ui.dash; ++const DND = imports.ui.dnd; ++const IconGrid = imports.ui.iconGrid; ++const Main = imports.ui.main; ++const PopupMenu = imports.ui.popupMenu; ++const Tweener = imports.ui.tweener; ++const Util = imports.misc.util; ++const Workspace = imports.ui.workspace; ++ ++const Me = imports.misc.extensionUtils.getCurrentExtension(); ++const Utils = Me.imports.utils; ++const WindowPreview = Me.imports.windowPreview; ++const AppIconIndicators = Me.imports.appIconIndicators; ++ ++let tracker = Shell.WindowTracker.get_default(); ++ ++let DASH_ITEM_LABEL_SHOW_TIME = Dash.DASH_ITEM_LABEL_SHOW_TIME; ++ ++const clickAction = { ++ SKIP: 0, ++ MINIMIZE: 1, ++ LAUNCH: 2, ++ CYCLE_WINDOWS: 3, ++ MINIMIZE_OR_OVERVIEW: 4, ++ PREVIEWS: 5, ++ MINIMIZE_OR_PREVIEWS: 6, ++ QUIT: 7 ++}; ++ ++const scrollAction = { ++ DO_NOTHING: 0, ++ CYCLE_WINDOWS: 1, ++ SWITCH_WORKSPACE: 2 ++}; ++ ++let recentlyClickedAppLoopId = 0; ++let recentlyClickedApp = null; ++let recentlyClickedAppWindows = null; ++let recentlyClickedAppIndex = 0; ++let recentlyClickedAppMonitor = -1; ++ ++/** ++ * Extend AppIcon ++ * ++ * - Pass settings to the constructor and bind settings changes ++ * - Apply a css class based on the number of windows of each application (#N); ++ * - Customized indicators for running applications in place of the default "dot" style which is hidden (#N); ++ * a class of the form "running#N" is applied to the AppWellIcon actor. ++ * like the original .running one. ++ * - Add a .focused style to the focused app ++ * - Customize click actions. ++ * - Update minimization animation target ++ * - Update menu if open on windows change ++ */ ++var MyAppIcon = new Lang.Class({ ++ Name: 'DashToDock.AppIcon', ++ Extends: AppDisplay.AppIcon, ++ ++ // settings are required inside. ++ _init: function(settings, remoteModel, app, monitorIndex, iconParams) { ++ // a prefix is required to avoid conflicting with the parent class variable ++ this._dtdSettings = settings; ++ this.monitorIndex = monitorIndex; ++ this._signalsHandler = new Utils.GlobalSignalsHandler(); ++ this.remoteModel = remoteModel; ++ this._indicator = null; ++ ++ this.parent(app, iconParams); ++ ++ this._updateIndicatorStyle(); ++ ++ // Monitor windows-changes instead of app state. ++ // Keep using the same Id and function callback (that is extended) ++ if (this._stateChangedId > 0) { ++ this.app.disconnect(this._stateChangedId); ++ this._stateChangedId = 0; ++ } ++ ++ this._windowsChangedId = this.app.connect('windows-changed', ++ Lang.bind(this, ++ this.onWindowsChanged)); ++ this._focusAppChangeId = tracker.connect('notify::focus-app', ++ Lang.bind(this, ++ this._onFocusAppChanged)); ++ ++ // In Wayland sessions, this signal is needed to track the state of windows dragged ++ // from one monitor to another. As this is triggered quite often (whenever a new winow ++ // of any application opened or moved to a different desktop), ++ // we restrict this signal to the case when 'isolate-monitors' is true, ++ // and if there are at least 2 monitors. ++ if (this._dtdSettings.get_boolean('isolate-monitors') && ++ Main.layoutManager.monitors.length > 1) { ++ this._signalsHandler.removeWithLabel('isolate-monitors'); ++ this._signalsHandler.addWithLabel('isolate-monitors', [ ++ global.screen, ++ 'window-entered-monitor', ++ Lang.bind(this, this._onWindowEntered) ++ ]); ++ } ++ ++ this._progressOverlayArea = null; ++ this._progress = 0; ++ ++ let keys = ['apply-custom-theme', ++ 'running-indicator-style', ++ ]; ++ ++ keys.forEach(function(key) { ++ this._signalsHandler.add([ ++ this._dtdSettings, ++ 'changed::' + key, ++ Lang.bind(this, this._updateIndicatorStyle) ++ ]); ++ }, this); ++ ++ this._dtdSettings.connect('changed::scroll-action', Lang.bind(this, function() { ++ this._optionalScrollCycleWindows(); ++ })); ++ this._optionalScrollCycleWindows(); ++ ++ this._numberOverlay(); ++ ++ this._previewMenuManager = null; ++ this._previewMenu = null; ++ }, ++ ++ _onDestroy: function() { ++ this.parent(); ++ ++ // This is necessary due to an upstream bug ++ // https://bugzilla.gnome.org/show_bug.cgi?id=757556 ++ // It can be safely removed once it get solved upstrea. ++ if (this._menu) ++ this._menu.close(false); ++ ++ // Disconect global signals ++ ++ if (this._windowsChangedId > 0) ++ this.app.disconnect(this._windowsChangedId); ++ this._windowsChangedId = 0; ++ ++ if (this._focusAppChangeId > 0) { ++ tracker.disconnect(this._focusAppChangeId); ++ this._focusAppChangeId = 0; ++ } ++ ++ this._signalsHandler.destroy(); ++ ++ if (this._scrollEventHandler) ++ this.actor.disconnect(this._scrollEventHandler); ++ }, ++ ++ // TOOD Rename this function ++ _updateIndicatorStyle: function() { ++ ++ if (this._indicator !== null) { ++ this._indicator.destroy(); ++ this._indicator = null; ++ } ++ this._indicator = new AppIconIndicators.AppIconIndicator(this, this._dtdSettings); ++ this._indicator.update(); ++ }, ++ ++ _onWindowEntered: function(metaScreen, monitorIndex, metaWin) { ++ let app = Shell.WindowTracker.get_default().get_window_app(metaWin); ++ if (app && app.get_id() == this.app.get_id()) ++ this.onWindowsChanged(); ++ }, ++ ++ _optionalScrollCycleWindows: function() { ++ if (this._scrollEventHandler) { ++ this.actor.disconnect(this._scrollEventHandler); ++ this._scrollEventHandler = 0; ++ } ++ ++ let isEnabled = this._dtdSettings.get_enum('scroll-action') === scrollAction.CYCLE_WINDOWS; ++ if (!isEnabled) return; ++ this._scrollEventHandler = this.actor.connect('scroll-event', Lang.bind(this, ++ this.onScrollEvent)); ++ }, ++ ++ onScrollEvent: function(actor, event) { ++ ++ // We only activate windows of running applications, i.e. we never open new windows ++ // We check if the app is running, and that the # of windows is > 0 in ++ // case we use workspace isolation, ++ let appIsRunning = this.app.state == Shell.AppState.RUNNING ++ && this.getInterestingWindows().length > 0; ++ ++ if (!appIsRunning) ++ return false ++ ++ if (this._optionalScrollCycleWindowsDeadTimeId > 0) ++ return false; ++ else ++ this._optionalScrollCycleWindowsDeadTimeId = Mainloop.timeout_add(250, Lang.bind(this, function() { ++ this._optionalScrollCycleWindowsDeadTimeId = 0; ++ })); ++ ++ let direction = null; ++ ++ switch (event.get_scroll_direction()) { ++ case Clutter.ScrollDirection.UP: ++ direction = Meta.MotionDirection.UP; ++ break; ++ case Clutter.ScrollDirection.DOWN: ++ direction = Meta.MotionDirection.DOWN; ++ break; ++ case Clutter.ScrollDirection.SMOOTH: ++ let [dx, dy] = event.get_scroll_delta(); ++ if (dy < 0) ++ direction = Meta.MotionDirection.UP; ++ else if (dy > 0) ++ direction = Meta.MotionDirection.DOWN; ++ break; ++ } ++ ++ let focusedApp = tracker.focus_app; ++ if (!Main.overview._shown) { ++ let reversed = direction === Meta.MotionDirection.UP; ++ if (this.app == focusedApp) ++ this._cycleThroughWindows(reversed); ++ else { ++ // Activate the first window ++ let windows = this.getInterestingWindows(); ++ if (windows.length > 0) { ++ let w = windows[0]; ++ Main.activateWindow(w); ++ } ++ } ++ } ++ else ++ this.app.activate(); ++ return true; ++ }, ++ ++ onWindowsChanged: function() { ++ ++ if (this._menu && this._menu.isOpen) ++ this._menu.update(); ++ ++ this._indicator.update(); ++ this.updateIconGeometry(); ++ }, ++ ++ /** ++ * Update taraget for minimization animation ++ */ ++ updateIconGeometry: function() { ++ // If (for unknown reason) the actor is not on the stage the reported size ++ // and position are random values, which might exceeds the integer range ++ // resulting in an error when assigned to the a rect. This is a more like ++ // a workaround to prevent flooding the system with errors. ++ if (this.actor.get_stage() == null) ++ return; ++ ++ let rect = new Meta.Rectangle(); ++ ++ [rect.x, rect.y] = this.actor.get_transformed_position(); ++ [rect.width, rect.height] = this.actor.get_transformed_size(); ++ ++ let windows = this.app.get_windows(); ++ if (this._dtdSettings.get_boolean('multi-monitor')){ ++ let monitorIndex = this.monitorIndex; ++ windows = windows.filter(function(w) { ++ return w.get_monitor() == monitorIndex; ++ }); ++ } ++ windows.forEach(function(w) { ++ w.set_icon_geometry(rect); ++ }); ++ }, ++ ++ _updateRunningStyle: function() { ++ // The logic originally in this function has been moved to ++ // AppIconIndicatorBase._updateDefaultDot(). However it cannot be removed as ++ // it called by the parent constructor. ++ }, ++ ++ popupMenu: function() { ++ this._removeMenuTimeout(); ++ this.actor.fake_release(); ++ this._draggable.fakeRelease(); ++ ++ if (!this._menu) { ++ this._menu = new MyAppIconMenu(this, this._dtdSettings); ++ this._menu.connect('activate-window', Lang.bind(this, function(menu, window) { ++ this.activateWindow(window); ++ })); ++ this._menu.connect('open-state-changed', Lang.bind(this, function(menu, isPoppedUp) { ++ if (!isPoppedUp) ++ this._onMenuPoppedDown(); ++ else { ++ // Setting the max-height is s useful if part of the menu is ++ // scrollable so the minimum height is smaller than the natural height. ++ let monitor_index = Main.layoutManager.findIndexForActor(this.actor); ++ let workArea = Main.layoutManager.getWorkAreaForMonitor(monitor_index); ++ let position = Utils.getPosition(this._dtdSettings); ++ this._isHorizontal = ( position == St.Side.TOP || ++ position == St.Side.BOTTOM); ++ // If horizontal also remove the height of the dash ++ let additional_margin = this._isHorizontal && !this._dtdSettings.get_boolean('dock-fixed') ? Main.overview._dash.actor.height : 0; ++ let verticalMargins = this._menu.actor.margin_top + this._menu.actor.margin_bottom; ++ // Also set a max width to the menu, so long labels (long windows title) get truncated ++ this._menu.actor.style = ('max-height: ' + Math.round(workArea.height - additional_margin - verticalMargins) + 'px;' + ++ 'max-width: 400px'); ++ } ++ })); ++ let id = Main.overview.connect('hiding', Lang.bind(this, function() { ++ this._menu.close(); ++ })); ++ this._menu.actor.connect('destroy', function() { ++ Main.overview.disconnect(id); ++ }); ++ ++ this._menuManager.addMenu(this._menu); ++ } ++ ++ this.emit('menu-state-changed', true); ++ ++ this.actor.set_hover(true); ++ this._menu.popup(); ++ this._menuManager.ignoreRelease(); ++ this.emit('sync-tooltip'); ++ ++ return false; ++ }, ++ ++ _onFocusAppChanged: function() { ++ this._indicator.update(); ++ }, ++ ++ activate: function(button) { ++ let event = Clutter.get_current_event(); ++ let modifiers = event ? event.get_state() : 0; ++ let focusedApp = tracker.focus_app; ++ ++ // Only consider SHIFT and CONTROL as modifiers (exclude SUPER, CAPS-LOCK, etc.) ++ modifiers = modifiers & (Clutter.ModifierType.SHIFT_MASK | Clutter.ModifierType.CONTROL_MASK); ++ ++ // We don't change the CTRL-click behaviour: in such case we just chain ++ // up the parent method and return. ++ if (modifiers & Clutter.ModifierType.CONTROL_MASK) { ++ // Keep default behaviour: launch new window ++ // By calling the parent method I make it compatible ++ // with other extensions tweaking ctrl + click ++ this.parent(button); ++ return; ++ } ++ ++ // We check what type of click we have and if the modifier SHIFT is ++ // being used. We then define what buttonAction should be for this ++ // event. ++ let buttonAction = 0; ++ if (button && button == 2 ) { ++ if (modifiers & Clutter.ModifierType.SHIFT_MASK) ++ buttonAction = this._dtdSettings.get_enum('shift-middle-click-action'); ++ else ++ buttonAction = this._dtdSettings.get_enum('middle-click-action'); ++ } ++ else if (button && button == 1) { ++ if (modifiers & Clutter.ModifierType.SHIFT_MASK) ++ buttonAction = this._dtdSettings.get_enum('shift-click-action'); ++ else ++ buttonAction = this._dtdSettings.get_enum('click-action'); ++ } ++ ++ // We check if the app is running, and that the # of windows is > 0 in ++ // case we use workspace isolation. ++ let windows = this.getInterestingWindows(); ++ let appIsRunning = this.app.state == Shell.AppState.RUNNING ++ && windows.length > 0; ++ ++ // Some action modes (e.g. MINIMIZE_OR_OVERVIEW) require overview to remain open ++ // This variable keeps track of this ++ let shouldHideOverview = true; ++ ++ // We customize the action only when the application is already running ++ if (appIsRunning) { ++ switch (buttonAction) { ++ case clickAction.MINIMIZE: ++ // In overview just activate the app, unless the acion is explicitely ++ // requested with a keyboard modifier ++ if (!Main.overview._shown || modifiers){ ++ // If we have button=2 or a modifier, allow minimization even if ++ // the app is not focused ++ if (this.app == focusedApp || button == 2 || modifiers & Clutter.ModifierType.SHIFT_MASK) { ++ // minimize all windows on double click and always in the case of primary click without ++ // additional modifiers ++ let click_count = 0; ++ if (Clutter.EventType.CLUTTER_BUTTON_PRESS) ++ click_count = event.get_click_count(); ++ let all_windows = (button == 1 && ! modifiers) || click_count > 1; ++ this._minimizeWindow(all_windows); ++ } ++ else ++ this._activateAllWindows(); ++ } ++ else { ++ let w = windows[0]; ++ Main.activateWindow(w); ++ } ++ break; ++ ++ case clickAction.MINIMIZE_OR_OVERVIEW: ++ // When a single window is present, toggle minimization ++ // If only one windows is present toggle minimization, but only when trigggered with the ++ // simple click action (no modifiers, no middle click). ++ if (windows.length == 1 && !modifiers && button == 1) { ++ let w = windows[0]; ++ if (this.app == focusedApp) { ++ // Window is raised, minimize it ++ this._minimizeWindow(w); ++ } else { ++ // Window is minimized, raise it ++ Main.activateWindow(w); ++ } ++ // Launch overview when multiple windows are present ++ // TODO: only show current app windows when gnome shell API will allow it ++ } else { ++ shouldHideOverview = false; ++ Main.overview.toggle(); ++ } ++ break; ++ ++ case clickAction.CYCLE_WINDOWS: ++ if (!Main.overview._shown){ ++ if (this.app == focusedApp) ++ this._cycleThroughWindows(); ++ else { ++ // Activate the first window ++ let w = windows[0]; ++ Main.activateWindow(w); ++ } ++ } ++ else ++ this.app.activate(); ++ break; ++ ++ case clickAction.LAUNCH: ++ this.launchNewWindow(); ++ break; ++ ++ case clickAction.PREVIEWS: ++ if (!Main.overview._shown) { ++ // If only one windows is present just switch to it, but only when trigggered with the ++ // simple click action (no modifiers, no middle click). ++ if (windows.length == 1 && !modifiers && button == 1) { ++ let w = windows[0]; ++ Main.activateWindow(w); ++ } else ++ this._windowPreviews(); ++ } ++ else { ++ this.app.activate(); ++ } ++ break; ++ ++ case clickAction.MINIMIZE_OR_PREVIEWS: ++ // When a single window is present, toggle minimization ++ // If only one windows is present toggle minimization, but only when trigggered with the ++ // simple click action (no modifiers, no middle click). ++ if (!Main.overview._shown){ ++ if (windows.length == 1 && !modifiers && button == 1) { ++ let w = windows[0]; ++ if (this.app == focusedApp) { ++ // Window is raised, minimize it ++ this._minimizeWindow(w); ++ } else { ++ // Window is minimized, raise it ++ Main.activateWindow(w); ++ } ++ } else { ++ // Launch previews when multiple windows are present ++ this._windowPreviews(); ++ } ++ } else { ++ this.app.activate(); ++ } ++ break; ++ ++ case clickAction.QUIT: ++ this.closeAllWindows(); ++ break; ++ ++ case clickAction.SKIP: ++ let w = windows[0]; ++ Main.activateWindow(w); ++ break; ++ } ++ } ++ else { ++ this.launchNewWindow(); ++ } ++ ++ // Hide overview except when action mode requires it ++ if(shouldHideOverview) { ++ Main.overview.hide(); ++ } ++ }, ++ ++ shouldShowTooltip: function() { ++ return this.actor.hover && (!this._menu || !this._menu.isOpen) && ++ (!this._previewMenu || !this._previewMenu.isOpen); ++ }, ++ ++ _windowPreviews: function() { ++ if (!this._previewMenu) { ++ this._previewMenuManager = new PopupMenu.PopupMenuManager(this); ++ ++ this._previewMenu = new WindowPreview.WindowPreviewMenu(this, this._dtdSettings); ++ ++ this._previewMenuManager.addMenu(this._previewMenu); ++ ++ this._previewMenu.connect('open-state-changed', Lang.bind(this, function(menu, isPoppedUp) { ++ if (!isPoppedUp) ++ this._onMenuPoppedDown(); ++ })); ++ let id = Main.overview.connect('hiding', Lang.bind(this, function() { ++ this._previewMenu.close(); ++ })); ++ this._previewMenu.actor.connect('destroy', function() { ++ Main.overview.disconnect(id); ++ }); ++ ++ } ++ ++ if (this._previewMenu.isOpen) ++ this._previewMenu.close(); ++ else ++ this._previewMenu.popup(); ++ ++ return false; ++ }, ++ ++ // Try to do the right thing when attempting to launch a new window of an app. In ++ // particular, if the application doens't allow to launch a new window, activate ++ // the existing window instead. ++ launchNewWindow: function(p) { ++ let appInfo = this.app.get_app_info(); ++ let actions = appInfo.list_actions(); ++ if (this.app.can_open_new_window()) { ++ this.animateLaunch(); ++ // This is used as a workaround for a bug resulting in no new windows being opened ++ // for certain running applications when calling open_new_window(). ++ // ++ // https://bugzilla.gnome.org/show_bug.cgi?id=756844 ++ // ++ // Similar to what done when generating the popupMenu entries, if the application provides ++ // a "New Window" action, use it instead of directly requesting a new window with ++ // open_new_window(), which fails for certain application, notably Nautilus. ++ if (actions.indexOf('new-window') == -1) { ++ this.app.open_new_window(-1); ++ } ++ else { ++ let i = actions.indexOf('new-window'); ++ if (i !== -1) ++ this.app.launch_action(actions[i], global.get_current_time(), -1); ++ } ++ } ++ else { ++ // Try to manually activate the first window. Otherwise, when the app is activated by ++ // switching to a different workspace, a launch spinning icon is shown and disappers only ++ // after a timeout. ++ let windows = this.app.get_windows(); ++ if (windows.length > 0) ++ Main.activateWindow(windows[0]) ++ else ++ this.app.activate(); ++ } ++ }, ++ ++ _numberOverlay: function() { ++ // Add label for a Hot-Key visual aid ++ this._numberOverlayLabel = new St.Label(); ++ this._numberOverlayBin = new St.Bin({ ++ child: this._numberOverlayLabel, ++ x_align: St.Align.START, y_align: St.Align.START, ++ x_expand: true, y_expand: true ++ }); ++ this._numberOverlayLabel.add_style_class_name('number-overlay'); ++ this._numberOverlayOrder = -1; ++ this._numberOverlayBin.hide(); ++ ++ this._iconContainer.add_child(this._numberOverlayBin); ++ ++ }, ++ ++ updateNumberOverlay: function() { ++ // We apply an overall scale factor that might come from a HiDPI monitor. ++ // Clutter dimensions are in physical pixels, but CSS measures are in logical ++ // pixels, so make sure to consider the scale. ++ let scaleFactor = St.ThemeContext.get_for_stage(global.stage).scale_factor; ++ // Set the font size to something smaller than the whole icon so it is ++ // still visible. The border radius is large to make the shape circular ++ let [minWidth, natWidth] = this._iconContainer.get_preferred_width(-1); ++ let font_size = Math.round(Math.max(12, 0.3*natWidth) / scaleFactor); ++ let size = Math.round(font_size*1.2); ++ this._numberOverlayLabel.set_style( ++ 'font-size: ' + font_size + 'px;' + ++ 'border-radius: ' + this.icon.iconSize + 'px;' + ++ 'width: ' + size + 'px; height: ' + size +'px;' ++ ); ++ }, ++ ++ setNumberOverlay: function(number) { ++ this._numberOverlayOrder = number; ++ this._numberOverlayLabel.set_text(number.toString()); ++ }, ++ ++ toggleNumberOverlay: function(activate) { ++ if (activate && this._numberOverlayOrder > -1) { ++ this.updateNumberOverlay(); ++ this._numberOverlayBin.show(); ++ } ++ else ++ this._numberOverlayBin.hide(); ++ }, ++ ++ _minimizeWindow: function(param) { ++ // Param true make all app windows minimize ++ let windows = this.getInterestingWindows(); ++ let current_workspace = global.screen.get_active_workspace(); ++ for (let i = 0; i < windows.length; i++) { ++ let w = windows[i]; ++ if (w.get_workspace() == current_workspace && w.showing_on_its_workspace()) { ++ w.minimize(); ++ // Just minimize one window. By specification it should be the ++ // focused window on the current workspace. ++ if(!param) ++ break; ++ } ++ } ++ }, ++ ++ // By default only non minimized windows are activated. ++ // This activates all windows in the current workspace. ++ _activateAllWindows: function() { ++ // First activate first window so workspace is switched if needed. ++ // We don't do this if isolation is on! ++ if (!this._dtdSettings.get_boolean('isolate-workspaces') && ++ !this._dtdSettings.get_boolean('isolate-monitors')) ++ this.app.activate(); ++ ++ // then activate all other app windows in the current workspace ++ let windows = this.getInterestingWindows(); ++ let activeWorkspace = global.screen.get_active_workspace_index(); ++ ++ if (windows.length <= 0) ++ return; ++ ++ let activatedWindows = 0; ++ ++ for (let i = windows.length - 1; i >= 0; i--) { ++ if (windows[i].get_workspace().index() == activeWorkspace) { ++ Main.activateWindow(windows[i]); ++ activatedWindows++; ++ } ++ } ++ }, ++ ++ //This closes all windows of the app. ++ closeAllWindows: function() { ++ let windows = this.getInterestingWindows(); ++ for (let i = 0; i < windows.length; i++) ++ windows[i].delete(global.get_current_time()); ++ }, ++ ++ _cycleThroughWindows: function(reversed) { ++ // Store for a little amount of time last clicked app and its windows ++ // since the order changes upon window interaction ++ let MEMORY_TIME=3000; ++ ++ let app_windows = this.getInterestingWindows(); ++ ++ if (app_windows.length <1) ++ return ++ ++ if (recentlyClickedAppLoopId > 0) ++ Mainloop.source_remove(recentlyClickedAppLoopId); ++ recentlyClickedAppLoopId = Mainloop.timeout_add(MEMORY_TIME, this._resetRecentlyClickedApp); ++ ++ // If there isn't already a list of windows for the current app, ++ // or the stored list is outdated, use the current windows list. ++ let monitorIsolation = this._dtdSettings.get_boolean('isolate-monitors'); ++ if (!recentlyClickedApp || ++ recentlyClickedApp.get_id() != this.app.get_id() || ++ recentlyClickedAppWindows.length != app_windows.length || ++ (recentlyClickedAppMonitor != this.monitorIndex && monitorIsolation)) { ++ recentlyClickedApp = this.app; ++ recentlyClickedAppWindows = app_windows; ++ recentlyClickedAppMonitor = this.monitorIndex; ++ recentlyClickedAppIndex = 0; ++ } ++ ++ if (reversed) { ++ recentlyClickedAppIndex--; ++ if (recentlyClickedAppIndex < 0) recentlyClickedAppIndex = recentlyClickedAppWindows.length - 1; ++ } else { ++ recentlyClickedAppIndex++; ++ } ++ let index = recentlyClickedAppIndex % recentlyClickedAppWindows.length; ++ let window = recentlyClickedAppWindows[index]; ++ ++ Main.activateWindow(window); ++ }, ++ ++ _resetRecentlyClickedApp: function() { ++ if (recentlyClickedAppLoopId > 0) ++ Mainloop.source_remove(recentlyClickedAppLoopId); ++ recentlyClickedAppLoopId=0; ++ recentlyClickedApp =null; ++ recentlyClickedAppWindows = null; ++ recentlyClickedAppIndex = 0; ++ recentlyClickedAppMonitor = -1; ++ ++ return false; ++ }, ++ ++ // Filter out unnecessary windows, for instance ++ // nautilus desktop window. ++ getInterestingWindows: function() { ++ return getInterestingWindows(this.app, this._dtdSettings, this.monitorIndex); ++ } ++}); ++/** ++ * Extend AppIconMenu ++ * ++ * - Pass settings to the constructor ++ * - set popup arrow side based on dash orientation ++ * - Add close windows option based on quitfromdash extension ++ * (https://github.com/deuill/shell-extension-quitfromdash) ++ * - Add open windows thumbnails instead of list ++ * - update menu when application windows change ++ */ ++const MyAppIconMenu = new Lang.Class({ ++ Name: 'DashToDock.MyAppIconMenu', ++ Extends: AppDisplay.AppIconMenu, ++ ++ _init: function(source, settings) { ++ let side = Utils.getPosition(settings); ++ ++ // Damm it, there has to be a proper way of doing this... ++ // As I can't call the parent parent constructor (?) passing the side ++ // parameter, I overwite what I need later ++ this.parent(source); ++ ++ // Change the initialized side where required. ++ this._arrowSide = side; ++ this._boxPointer._arrowSide = side; ++ this._boxPointer._userArrowSide = side; ++ ++ this._dtdSettings = settings; ++ }, ++ ++ _redisplay: function() { ++ this.removeAll(); ++ ++ if (this._dtdSettings.get_boolean('show-windows-preview')) { ++ // Display the app windows menu items and the separator between windows ++ // of the current desktop and other windows. ++ ++ this._allWindowsMenuItem = new PopupMenu.PopupSubMenuMenuItem(__('All Windows'), false); ++ this._allWindowsMenuItem.actor.hide(); ++ this.addMenuItem(this._allWindowsMenuItem); ++ ++ if (!this._source.app.is_window_backed()) { ++ this._appendSeparator(); ++ ++ let appInfo = this._source.app.get_app_info(); ++ let actions = appInfo.list_actions(); ++ if (this._source.app.can_open_new_window() && ++ actions.indexOf('new-window') == -1) { ++ this._newWindowMenuItem = this._appendMenuItem(_("New Window")); ++ this._newWindowMenuItem.connect('activate', Lang.bind(this, function() { ++ if (this._source.app.state == Shell.AppState.STOPPED) ++ this._source.animateLaunch(); ++ ++ this._source.app.open_new_window(-1); ++ this.emit('activate-window', null); ++ })); ++ this._appendSeparator(); ++ } ++ ++ ++ if (AppDisplay.discreteGpuAvailable && ++ this._source.app.state == Shell.AppState.STOPPED && ++ actions.indexOf('activate-discrete-gpu') == -1) { ++ this._onDiscreteGpuMenuItem = this._appendMenuItem(_("Launch using Dedicated Graphics Card")); ++ this._onDiscreteGpuMenuItem.connect('activate', Lang.bind(this, function() { ++ if (this._source.app.state == Shell.AppState.STOPPED) ++ this._source.animateLaunch(); ++ ++ this._source.app.launch(0, -1, true); ++ this.emit('activate-window', null); ++ })); ++ } ++ ++ for (let i = 0; i < actions.length; i++) { ++ let action = actions[i]; ++ let item = this._appendMenuItem(appInfo.get_action_name(action)); ++ item.connect('activate', Lang.bind(this, function(emitter, event) { ++ this._source.app.launch_action(action, event.get_time(), -1); ++ this.emit('activate-window', null); ++ })); ++ } ++ ++ let canFavorite = global.settings.is_writable('favorite-apps'); ++ ++ if (canFavorite) { ++ this._appendSeparator(); ++ ++ let isFavorite = AppFavorites.getAppFavorites().isFavorite(this._source.app.get_id()); ++ ++ if (isFavorite) { ++ let item = this._appendMenuItem(_("Remove from Favorites")); ++ item.connect('activate', Lang.bind(this, function() { ++ let favs = AppFavorites.getAppFavorites(); ++ favs.removeFavorite(this._source.app.get_id()); ++ })); ++ } else { ++ let item = this._appendMenuItem(_("Add to Favorites")); ++ item.connect('activate', Lang.bind(this, function() { ++ let favs = AppFavorites.getAppFavorites(); ++ favs.addFavorite(this._source.app.get_id()); ++ })); ++ } ++ } ++ ++ if (Shell.AppSystem.get_default().lookup_app('org.gnome.Software.desktop')) { ++ this._appendSeparator(); ++ let item = this._appendMenuItem(_("Show Details")); ++ item.connect('activate', Lang.bind(this, function() { ++ let id = this._source.app.get_id(); ++ let args = GLib.Variant.new('(ss)', [id, '']); ++ Gio.DBus.get(Gio.BusType.SESSION, null, ++ function(o, res) { ++ let bus = Gio.DBus.get_finish(res); ++ bus.call('org.gnome.Software', ++ '/org/gnome/Software', ++ 'org.gtk.Actions', 'Activate', ++ GLib.Variant.new('(sava{sv})', ++ ['details', [args], null]), ++ null, 0, -1, null, null); ++ Main.overview.hide(); ++ }); ++ })); ++ } ++ } ++ ++ } else { ++ this.parent(); ++ } ++ ++ // quit menu ++ this._appendSeparator(); ++ this._quitfromDashMenuItem = this._appendMenuItem(_("Quit")); ++ this._quitfromDashMenuItem.connect('activate', Lang.bind(this, function() { ++ this._source.closeAllWindows(); ++ })); ++ ++ this.update(); ++ }, ++ ++ // update menu content when application windows change. This is desirable as actions ++ // acting on windows (closing) are performed while the menu is shown. ++ update: function() { ++ ++ if(this._dtdSettings.get_boolean('show-windows-preview')){ ++ ++ let windows = this._source.getInterestingWindows(); ++ ++ // update, show or hide the quit menu ++ if ( windows.length > 0) { ++ let quitFromDashMenuText = ""; ++ if (windows.length == 1) ++ this._quitfromDashMenuItem.label.set_text(_("Quit")); ++ else ++ this._quitfromDashMenuItem.label.set_text(_("Quit") + ' ' + windows.length + ' ' + _("Windows")); ++ ++ this._quitfromDashMenuItem.actor.show(); ++ ++ } else { ++ this._quitfromDashMenuItem.actor.hide(); ++ } ++ ++ // update, show, or hide the allWindows menu ++ // Check if there are new windows not already displayed. In such case, repopulate the allWindows ++ // menu. Windows removal is already handled by each preview being connected to the destroy signal ++ let old_windows = this._allWindowsMenuItem.menu._getMenuItems().map(function(item){ ++ return item._window; ++ }); ++ ++ let new_windows = windows.filter(function(w) {return old_windows.indexOf(w) < 0;}); ++ if (new_windows.length > 0) { ++ this._populateAllWindowMenu(windows); ++ ++ // Try to set the width to that of the submenu. ++ // TODO: can't get the actual size, getting a bit less. ++ // Temporary workaround: add 15px to compensate ++ this._allWindowsMenuItem.actor.width = this._allWindowsMenuItem.menu.actor.width + 15; ++ ++ } ++ ++ // The menu is created hidden and never hidded after being shown. Instead, a singlal ++ // connected to its items destroy will set is insensitive if no more windows preview are shown. ++ if (windows.length > 0){ ++ this._allWindowsMenuItem.actor.show(); ++ this._allWindowsMenuItem.setSensitive(true); ++ } ++ ++ // Update separators ++ this._getMenuItems().forEach(Lang.bind(this, this._updateSeparatorVisibility)); ++ } ++ ++ ++ }, ++ ++ _populateAllWindowMenu: function(windows) { ++ ++ this._allWindowsMenuItem.menu.removeAll(); ++ ++ if (windows.length > 0) { ++ ++ let activeWorkspace = global.screen.get_active_workspace(); ++ let separatorShown = windows[0].get_workspace() != activeWorkspace; ++ ++ for (let i = 0; i < windows.length; i++) { ++ let window = windows[i]; ++ if (!separatorShown && window.get_workspace() != activeWorkspace) { ++ this._allWindowsMenuItem.menu.addMenuItem(new PopupMenu.PopupSeparatorMenuItem()); ++ separatorShown = true; ++ } ++ ++ let item = new WindowPreview.WindowPreviewMenuItem(window); ++ this._allWindowsMenuItem.menu.addMenuItem(item); ++ item.connect('activate', Lang.bind(this, function() { ++ this.emit('activate-window', window); ++ })); ++ ++ // This is to achieve a more gracefull transition when the last windows is closed. ++ item.connect('destroy', Lang.bind(this, function() { ++ if(this._allWindowsMenuItem.menu._getMenuItems().length == 1) // It's still counting the item just going to be destroyed ++ this._allWindowsMenuItem.setSensitive(false); ++ })); ++ } ++ } ++ }, ++}); ++Signals.addSignalMethods(MyAppIconMenu.prototype); ++ ++// Filter out unnecessary windows, for instance ++// nautilus desktop window. ++function getInterestingWindows(app, settings, monitorIndex) { ++ let windows = app.get_windows().filter(function(w) { ++ return !w.skip_taskbar; ++ }); ++ ++ // When using workspace isolation, we filter out windows ++ // that are not in the current workspace ++ if (settings.get_boolean('isolate-workspaces')) ++ windows = windows.filter(function(w) { ++ return w.get_workspace().index() == global.screen.get_active_workspace_index(); ++ }); ++ ++ if (settings.get_boolean('isolate-monitors')) ++ windows = windows.filter(function(w) { ++ return w.get_monitor() == monitorIndex; ++ }); ++ ++ return windows; ++} ++ ++/** ++ * A wrapper class around the ShowAppsIcon class. ++ * ++ * - Pass settings to the constructor ++ * - set label position based on dash orientation (Note, I am reusing most machinery of the appIcon class) ++ * - implement a popupMenu based on the AppIcon code (Note, I am reusing most machinery of the appIcon class) ++ * ++ * I can't subclass the original object because of this: https://bugzilla.gnome.org/show_bug.cgi?id=688973. ++ * thus use this pattern where the real showAppsIcon object is encaptulated, and a reference to it will be properly wired upon ++ * use of this class in place of the original showAppsButton. ++ * ++ */ ++ ++ var ShowAppsIconWrapper = new Lang.Class({ ++ Name: 'DashToDock.ShowAppsIconWrapper', ++ ++ _init: function(settings) { ++ this._dtdSettings = settings; ++ this.realShowAppsIcon = new Dash.ShowAppsIcon(); ++ ++ /* the variable equivalent to toggleButton has a different name in the appIcon class ++ (actor): duplicate reference to easily reuse appIcon methods */ ++ this.actor = this.realShowAppsIcon.toggleButton; ++ ++ // Re-use appIcon methods ++ this._removeMenuTimeout = AppDisplay.AppIcon.prototype._removeMenuTimeout; ++ this._setPopupTimeout = AppDisplay.AppIcon.prototype._setPopupTimeout; ++ this._onButtonPress = AppDisplay.AppIcon.prototype._onButtonPress; ++ this._onKeyboardPopupMenu = AppDisplay.AppIcon.prototype._onKeyboardPopupMenu; ++ this._onLeaveEvent = AppDisplay.AppIcon.prototype._onLeaveEvent; ++ this._onTouchEvent = AppDisplay.AppIcon.prototype._onTouchEvent; ++ this._onMenuPoppedDown = AppDisplay.AppIcon.prototype._onMenuPoppedDown; ++ ++ // No action on clicked (showing of the appsview is controlled elsewhere) ++ this._onClicked = Lang.bind(this, function(actor, button) { ++ this._removeMenuTimeout(); ++ }); ++ ++ this.actor.connect('leave-event', Lang.bind(this, this._onLeaveEvent)); ++ this.actor.connect('button-press-event', Lang.bind(this, this._onButtonPress)); ++ this.actor.connect('touch-event', Lang.bind(this, this._onTouchEvent)); ++ this.actor.connect('clicked', Lang.bind(this, this._onClicked)); ++ this.actor.connect('popup-menu', Lang.bind(this, this._onKeyboardPopupMenu)); ++ ++ this._menu = null; ++ this._menuManager = new PopupMenu.PopupMenuManager(this); ++ this._menuTimeoutId = 0; ++ ++ this.showLabel = itemShowLabel; ++ }, ++ ++ popupMenu: function() { ++ this._removeMenuTimeout(); ++ this.actor.fake_release(); ++ ++ if (!this._menu) { ++ this._menu = new MyShowAppsIconMenu(this, this._dtdSettings); ++ this._menu.connect('open-state-changed', Lang.bind(this, function(menu, isPoppedUp) { ++ if (!isPoppedUp) ++ this._onMenuPoppedDown(); ++ })); ++ let id = Main.overview.connect('hiding', Lang.bind(this, function() { ++ this._menu.close(); ++ })); ++ this._menu.actor.connect('destroy', function() { ++ Main.overview.disconnect(id); ++ }); ++ this._menuManager.addMenu(this._menu); ++ } ++ ++ //this.emit('menu-state-changed', true); ++ ++ this.actor.set_hover(true); ++ this._menu.popup(); ++ this._menuManager.ignoreRelease(); ++ this.emit('sync-tooltip'); ++ ++ return false; ++ } ++}); ++Signals.addSignalMethods(ShowAppsIconWrapper.prototype); ++ ++ ++/** ++ * A menu for the showAppsIcon ++ */ ++const MyShowAppsIconMenu = new Lang.Class({ ++ Name: 'DashToDock.ShowAppsIconMenu', ++ Extends: MyAppIconMenu, ++ ++ _redisplay: function() { ++ this.removeAll(); ++ ++ /* Translators: %s is "Settings", which is automatically translated. You ++ can also translate the full message if this fits better your language. */ ++ let name = __('Dash to Dock %s').format(_('Settings')) ++ let item = this._appendMenuItem(name); ++ ++ item.connect('activate', function () { ++ Util.spawn(["gnome-shell-extension-prefs", Me.metadata.uuid]); ++ }); ++ } ++}); ++ ++/** ++ * This function is used for both extendShowAppsIcon and extendDashItemContainer ++ */ ++function itemShowLabel() { ++ // Check if the label is still present at all. When switching workpaces, the ++ // item might have been destroyed in between. ++ if (!this._labelText || this.label.get_stage() == null) ++ return; ++ ++ this.label.set_text(this._labelText); ++ this.label.opacity = 0; ++ this.label.show(); ++ ++ let [stageX, stageY] = this.get_transformed_position(); ++ let node = this.label.get_theme_node(); ++ ++ let itemWidth = this.allocation.x2 - this.allocation.x1; ++ let itemHeight = this.allocation.y2 - this.allocation.y1; ++ ++ let labelWidth = this.label.get_width(); ++ let labelHeight = this.label.get_height(); ++ ++ let x, y, xOffset, yOffset; ++ ++ let position = Utils.getPosition(this._dtdSettings); ++ this._isHorizontal = ((position == St.Side.TOP) || (position == St.Side.BOTTOM)); ++ let labelOffset = node.get_length('-x-offset'); ++ ++ switch (position) { ++ case St.Side.LEFT: ++ yOffset = Math.floor((itemHeight - labelHeight) / 2); ++ y = stageY + yOffset; ++ xOffset = labelOffset; ++ x = stageX + this.get_width() + xOffset; ++ break; ++ case St.Side.RIGHT: ++ yOffset = Math.floor((itemHeight - labelHeight) / 2); ++ y = stageY + yOffset; ++ xOffset = labelOffset; ++ x = Math.round(stageX) - labelWidth - xOffset; ++ break; ++ case St.Side.TOP: ++ y = stageY + labelOffset + itemHeight; ++ xOffset = Math.floor((itemWidth - labelWidth) / 2); ++ x = stageX + xOffset; ++ break; ++ case St.Side.BOTTOM: ++ yOffset = labelOffset; ++ y = stageY - labelHeight - yOffset; ++ xOffset = Math.floor((itemWidth - labelWidth) / 2); ++ x = stageX + xOffset; ++ break; ++ } ++ ++ // keep the label inside the screen border ++ // Only needed fot the x coordinate. ++ ++ // Leave a few pixel gap ++ let gap = 5; ++ let monitor = Main.layoutManager.findMonitorForActor(this); ++ if (x - monitor.x < gap) ++ x += monitor.x - x + labelOffset; ++ else if (x + labelWidth > monitor.x + monitor.width - gap) ++ x -= x + labelWidth - (monitor.x + monitor.width) + gap; ++ ++ this.label.set_position(x, y); ++ Tweener.addTween(this.label, { ++ opacity: 255, ++ time: DASH_ITEM_LABEL_SHOW_TIME, ++ transition: 'easeOutQuad', ++ }); ++} +diff --git a/extensions/dash-to-dock/convenience.js b/extensions/dash-to-dock/convenience.js +new file mode 100644 +index 0000000..bc50419 +--- /dev/null ++++ b/extensions/dash-to-dock/convenience.js +@@ -0,0 +1,74 @@ ++/* -*- mode: js; js-basic-offset: 4; indent-tabs-mode: nil -*- */ ++ ++/* ++ * Part of this file comes from gnome-shell-extensions: ++ * https://gitlab.gnome.org/GNOME/gnome-shell-extensions/ ++ */ ++ ++const Gettext = imports.gettext; ++const Gio = imports.gi.Gio; ++ ++const Config = imports.misc.config; ++const ExtensionUtils = imports.misc.extensionUtils; ++ ++/** ++ * initTranslations: ++ * @domain: (optional): the gettext domain to use ++ * ++ * Initialize Gettext to load translations from extensionsdir/locale. ++ * If @domain is not provided, it will be taken from metadata['gettext-domain'] ++ */ ++function initTranslations(domain) { ++ let extension = ExtensionUtils.getCurrentExtension(); ++ ++ domain = domain || extension.metadata['gettext-domain']; ++ ++ // Check if this extension was built with "make zip-file", and thus ++ // has the locale files in a subfolder ++ // otherwise assume that extension has been installed in the ++ // same prefix as gnome-shell ++ let localeDir = extension.dir.get_child('locale'); ++ if (localeDir.query_exists(null)) ++ Gettext.bindtextdomain(domain, localeDir.get_path()); ++ else ++ Gettext.bindtextdomain(domain, Config.LOCALEDIR); ++} ++ ++/** ++ * getSettings: ++ * @schema: (optional): the GSettings schema id ++ * ++ * Builds and return a GSettings schema for @schema, using schema files ++ * in extensionsdir/schemas. If @schema is not provided, it is taken from ++ * metadata['settings-schema']. ++ */ ++function getSettings(schema) { ++ let extension = ExtensionUtils.getCurrentExtension(); ++ ++ schema = schema || extension.metadata['settings-schema']; ++ ++ const GioSSS = Gio.SettingsSchemaSource; ++ ++ // Check if this extension was built with "make zip-file", and thus ++ // has the schema files in a subfolder ++ // otherwise assume that extension has been installed in the ++ // same prefix as gnome-shell (and therefore schemas are available ++ // in the standard folders) ++ let schemaDir = extension.dir.get_child('schemas'); ++ let schemaSource; ++ if (schemaDir.query_exists(null)) ++ schemaSource = GioSSS.new_from_directory(schemaDir.get_path(), ++ GioSSS.get_default(), ++ false); ++ else ++ schemaSource = GioSSS.get_default(); ++ ++ let schemaObj = schemaSource.lookup(schema, true); ++ if (!schemaObj) ++ throw new Error('Schema ' + schema + ' could not be found for extension ' ++ + extension.metadata.uuid + '. Please check your installation.'); ++ ++ return new Gio.Settings({ ++ settings_schema: schemaObj ++ }); ++} +diff --git a/extensions/dash-to-dock/dash.js b/extensions/dash-to-dock/dash.js +new file mode 100644 +index 0000000..4cf5aa2 +--- /dev/null ++++ b/extensions/dash-to-dock/dash.js +@@ -0,0 +1,1175 @@ ++// -*- mode: js; js-indent-level: 4; indent-tabs-mode: nil -*- ++ ++const Clutter = imports.gi.Clutter; ++const Gio = imports.gi.Gio; ++const GLib = imports.gi.GLib; ++const Gtk = imports.gi.Gtk; ++const Signals = imports.signals; ++const Lang = imports.lang; ++const Meta = imports.gi.Meta; ++const Shell = imports.gi.Shell; ++const St = imports.gi.St; ++const Mainloop = imports.mainloop; ++ ++const AppDisplay = imports.ui.appDisplay; ++const AppFavorites = imports.ui.appFavorites; ++const Dash = imports.ui.dash; ++const DND = imports.ui.dnd; ++const IconGrid = imports.ui.iconGrid; ++const Main = imports.ui.main; ++const PopupMenu = imports.ui.popupMenu; ++const Tweener = imports.ui.tweener; ++const Util = imports.misc.util; ++const Workspace = imports.ui.workspace; ++ ++const Me = imports.misc.extensionUtils.getCurrentExtension(); ++const Utils = Me.imports.utils; ++const AppIcons = Me.imports.appIcons; ++ ++let DASH_ANIMATION_TIME = Dash.DASH_ANIMATION_TIME; ++let DASH_ITEM_LABEL_HIDE_TIME = Dash.DASH_ITEM_LABEL_HIDE_TIME; ++let DASH_ITEM_HOVER_TIMEOUT = Dash.DASH_ITEM_HOVER_TIMEOUT; ++ ++/** ++ * Extend DashItemContainer ++ * ++ * - Pass settings to the constructor ++ * - set label position based on dash orientation ++ * ++ * I can't subclass the original object because of this: https://bugzilla.gnome.org/show_bug.cgi?id=688973. ++ * thus use this ugly pattern. ++ */ ++function extendDashItemContainer(dashItemContainer, settings) { ++ dashItemContainer._dtdSettings = settings; ++ dashItemContainer.showLabel = AppIcons.itemShowLabel; ++} ++ ++/** ++ * This class is a fork of the upstream DashActor class (ui.dash.js) ++ * ++ * Summary of changes: ++ * - passed settings to class as parameter ++ * - modified chldBox calculations for when 'show-apps-at-top' option is checked ++ * - handle horizontal dash ++ */ ++const MyDashActor = new Lang.Class({ ++ Name: 'DashToDock.MyDashActor', ++ ++ _init: function(settings) { ++ // a prefix is required to avoid conflicting with the parent class variable ++ this._dtdSettings = settings; ++ this._rtl = (Clutter.get_default_text_direction() == Clutter.TextDirection.RTL); ++ ++ this._position = Utils.getPosition(settings); ++ this._isHorizontal = ((this._position == St.Side.TOP) || ++ (this._position == St.Side.BOTTOM)); ++ ++ let layout = new Clutter.BoxLayout({ ++ orientation: this._isHorizontal ? Clutter.Orientation.HORIZONTAL : Clutter.Orientation.VERTICAL ++ }); ++ ++ this.actor = new Shell.GenericContainer({ ++ name: 'dash', ++ layout_manager: layout, ++ clip_to_allocation: true ++ }); ++ this.actor.connect('get-preferred-width', Lang.bind(this, this._getPreferredWidth)); ++ this.actor.connect('get-preferred-height', Lang.bind(this, this._getPreferredHeight)); ++ this.actor.connect('allocate', Lang.bind(this, this._allocate)); ++ ++ this.actor._delegate = this; ++ }, ++ ++ _allocate: function(actor, box, flags) { ++ let contentBox = box; ++ let availWidth = contentBox.x2 - contentBox.x1; ++ let availHeight = contentBox.y2 - contentBox.y1; ++ ++ let [appIcons, showAppsButton] = actor.get_children(); ++ let [showAppsMinHeight, showAppsNatHeight] = showAppsButton.get_preferred_height(availWidth); ++ let [showAppsMinWidth, showAppsNatWidth] = showAppsButton.get_preferred_width(availHeight); ++ ++ let offset_x = this._isHorizontal?showAppsNatWidth:0; ++ let offset_y = this._isHorizontal?0:showAppsNatHeight; ++ ++ let childBox = new Clutter.ActorBox(); ++ if ((this._dtdSettings.get_boolean('show-apps-at-top') && !this._isHorizontal) ++ || (this._dtdSettings.get_boolean('show-apps-at-top') && !this._rtl) ++ || (!this._dtdSettings.get_boolean('show-apps-at-top') && this._isHorizontal && this._rtl)) { ++ childBox.x1 = contentBox.x1 + offset_x; ++ childBox.y1 = contentBox.y1 + offset_y; ++ childBox.x2 = contentBox.x2; ++ childBox.y2 = contentBox.y2; ++ appIcons.allocate(childBox, flags); ++ ++ childBox.y1 = contentBox.y1; ++ childBox.x1 = contentBox.x1; ++ childBox.x2 = contentBox.x1 + showAppsNatWidth; ++ childBox.y2 = contentBox.y1 + showAppsNatHeight; ++ showAppsButton.allocate(childBox, flags); ++ } ++ else { ++ childBox.x1 = contentBox.x1; ++ childBox.y1 = contentBox.y1; ++ childBox.x2 = contentBox.x2 - offset_x; ++ childBox.y2 = contentBox.y2 - offset_y; ++ appIcons.allocate(childBox, flags); ++ ++ childBox.x2 = contentBox.x2; ++ childBox.y2 = contentBox.y2; ++ childBox.x1 = contentBox.x2 - showAppsNatWidth; ++ childBox.y1 = contentBox.y2 - showAppsNatHeight; ++ showAppsButton.allocate(childBox, flags); ++ } ++ }, ++ ++ _getPreferredWidth: function(actor, forHeight, alloc) { ++ // We want to request the natural height of all our children ++ // as our natural height, so we chain up to StWidget (which ++ // then calls BoxLayout), but we only request the showApps ++ // button as the minimum size ++ ++ let [, natWidth] = this.actor.layout_manager.get_preferred_width(this.actor, forHeight); ++ ++ let themeNode = this.actor.get_theme_node(); ++ let [, showAppsButton] = this.actor.get_children(); ++ let [minWidth, ] = showAppsButton.get_preferred_height(forHeight); ++ ++ alloc.min_size = minWidth; ++ alloc.natural_size = natWidth; ++ ++ }, ++ ++ _getPreferredHeight: function(actor, forWidth, alloc) { ++ // We want to request the natural height of all our children ++ // as our natural height, so we chain up to StWidget (which ++ // then calls BoxLayout), but we only request the showApps ++ // button as the minimum size ++ ++ let [, natHeight] = this.actor.layout_manager.get_preferred_height(this.actor, forWidth); ++ ++ let themeNode = this.actor.get_theme_node(); ++ let [, showAppsButton] = this.actor.get_children(); ++ let [minHeight, ] = showAppsButton.get_preferred_height(forWidth); ++ ++ alloc.min_size = minHeight; ++ alloc.natural_size = natHeight; ++ } ++}); ++ ++const baseIconSizes = [16, 22, 24, 32, 48, 64, 96, 128]; ++ ++/** ++ * This class is a fork of the upstream dash class (ui.dash.js) ++ * ++ * Summary of changes: ++ * - disconnect global signals adding a destroy method; ++ * - play animations even when not in overview mode ++ * - set a maximum icon size ++ * - show running and/or favorite applications ++ * - emit a custom signal when an app icon is added ++ * - hide showApps label when the custom menu is shown. ++ * - add scrollview ++ * ensure actor is visible on keyfocus inseid the scrollview ++ * - add 128px icon size, might be usefull for hidpi display ++ * - sync minimization application target position. ++ * - keep running apps ordered. ++ */ ++var MyDash = new Lang.Class({ ++ Name: 'DashToDock.MyDash', ++ ++ _init: function(settings, remoteModel, monitorIndex) { ++ this._dtdSettings = settings; ++ ++ // Initialize icon variables and size ++ this._maxHeight = -1; ++ this.iconSize = this._dtdSettings.get_int('dash-max-icon-size'); ++ this._availableIconSizes = baseIconSizes; ++ this._shownInitially = false; ++ this._initializeIconSize(this.iconSize); ++ ++ this._remoteModel = remoteModel; ++ this._monitorIndex = monitorIndex; ++ this._position = Utils.getPosition(settings); ++ this._isHorizontal = ((this._position == St.Side.TOP) || ++ (this._position == St.Side.BOTTOM)); ++ this._signalsHandler = new Utils.GlobalSignalsHandler(); ++ ++ this._dragPlaceholder = null; ++ this._dragPlaceholderPos = -1; ++ this._animatingPlaceholdersCount = 0; ++ this._showLabelTimeoutId = 0; ++ this._resetHoverTimeoutId = 0; ++ this._ensureAppIconVisibilityTimeoutId = 0; ++ this._labelShowing = false; ++ ++ this._containerObject = new MyDashActor(settings); ++ this._container = this._containerObject.actor; ++ this._scrollView = new St.ScrollView({ ++ name: 'dashtodockDashScrollview', ++ hscrollbar_policy: Gtk.PolicyType.NEVER, ++ vscrollbar_policy: Gtk.PolicyType.NEVER, ++ enable_mouse_scrolling: false ++ }); ++ ++ this._scrollView.connect('scroll-event', Lang.bind(this, this._onScrollEvent)); ++ ++ this._box = new St.BoxLayout({ ++ vertical: !this._isHorizontal, ++ clip_to_allocation: false, ++ x_align: Clutter.ActorAlign.START, ++ y_align: Clutter.ActorAlign.START ++ }); ++ this._box._delegate = this; ++ this._container.add_actor(this._scrollView); ++ this._scrollView.add_actor(this._box); ++ ++ // Create a wrapper around the real showAppsIcon in order to add a popupMenu. ++ let showAppsIconWrapper = new AppIcons.ShowAppsIconWrapper(this._dtdSettings); ++ showAppsIconWrapper.connect('menu-state-changed', Lang.bind(this, function(showAppsIconWrapper, opened) { ++ this._itemMenuStateChanged(showAppsIconWrapper, opened); ++ })); ++ // an instance of the showAppsIcon class is encapsulated in the wrapper ++ this._showAppsIcon = showAppsIconWrapper.realShowAppsIcon; ++ ++ this._showAppsIcon.childScale = 1; ++ this._showAppsIcon.childOpacity = 255; ++ this._showAppsIcon.icon.setIconSize(this.iconSize); ++ this._hookUpLabel(this._showAppsIcon); ++ ++ this.showAppsButton = this._showAppsIcon.toggleButton; ++ ++ this._container.add_actor(this._showAppsIcon); ++ ++ let rtl = Clutter.get_default_text_direction() == Clutter.TextDirection.RTL; ++ this.actor = new St.Bin({ ++ child: this._container, ++ y_align: St.Align.START, ++ x_align: rtl ? St.Align.END : St.Align.START ++ }); ++ ++ if (this._isHorizontal) { ++ this.actor.connect('notify::width', Lang.bind(this, function() { ++ if (this._maxHeight != this.actor.width) ++ this._queueRedisplay(); ++ this._maxHeight = this.actor.width; ++ })); ++ } ++ else { ++ this.actor.connect('notify::height', Lang.bind(this, function() { ++ if (this._maxHeight != this.actor.height) ++ this._queueRedisplay(); ++ this._maxHeight = this.actor.height; ++ })); ++ } ++ ++ // Update minimization animation target position on allocation of the ++ // container and on scrollview change. ++ this._box.connect('notify::allocation', Lang.bind(this, this._updateAppsIconGeometry)); ++ let scrollViewAdjustment = this._isHorizontal ? this._scrollView.hscroll.adjustment : this._scrollView.vscroll.adjustment; ++ scrollViewAdjustment.connect('notify::value', Lang.bind(this, this._updateAppsIconGeometry)); ++ ++ this._workId = Main.initializeDeferredWork(this._box, Lang.bind(this, this._redisplay)); ++ ++ this._settings = new Gio.Settings({ ++ schema_id: 'org.gnome.shell' ++ }); ++ ++ this._appSystem = Shell.AppSystem.get_default(); ++ ++ this._signalsHandler.add([ ++ this._appSystem, ++ 'installed-changed', ++ Lang.bind(this, function() { ++ AppFavorites.getAppFavorites().reload(); ++ this._queueRedisplay(); ++ }) ++ ], [ ++ AppFavorites.getAppFavorites(), ++ 'changed', ++ Lang.bind(this, this._queueRedisplay) ++ ], [ ++ this._appSystem, ++ 'app-state-changed', ++ Lang.bind(this, this._queueRedisplay) ++ ], [ ++ Main.overview, ++ 'item-drag-begin', ++ Lang.bind(this, this._onDragBegin) ++ ], [ ++ Main.overview, ++ 'item-drag-end', ++ Lang.bind(this, this._onDragEnd) ++ ], [ ++ Main.overview, ++ 'item-drag-cancelled', ++ Lang.bind(this, this._onDragCancelled) ++ ]); ++ }, ++ ++ destroy: function() { ++ this._signalsHandler.destroy(); ++ }, ++ ++ _onScrollEvent: function(actor, event) { ++ // If scroll is not used because the icon is resized, let the scroll event propagate. ++ if (!this._dtdSettings.get_boolean('icon-size-fixed')) ++ return Clutter.EVENT_PROPAGATE; ++ ++ // reset timeout to avid conflicts with the mousehover event ++ if (this._ensureAppIconVisibilityTimeoutId > 0) { ++ Mainloop.source_remove(this._ensureAppIconVisibilityTimeoutId); ++ this._ensureAppIconVisibilityTimeoutId = 0; ++ } ++ ++ // Skip to avoid double events mouse ++ if (event.is_pointer_emulated()) ++ return Clutter.EVENT_STOP; ++ ++ let adjustment, delta; ++ ++ if (this._isHorizontal) ++ adjustment = this._scrollView.get_hscroll_bar().get_adjustment(); ++ else ++ adjustment = this._scrollView.get_vscroll_bar().get_adjustment(); ++ ++ let increment = adjustment.step_increment; ++ ++ switch (event.get_scroll_direction()) { ++ case Clutter.ScrollDirection.UP: ++ delta = -increment; ++ break; ++ case Clutter.ScrollDirection.DOWN: ++ delta = +increment; ++ break; ++ case Clutter.ScrollDirection.SMOOTH: ++ let [dx, dy] = event.get_scroll_delta(); ++ delta = dy * increment; ++ // Also consider horizontal component, for instance touchpad ++ if (this._isHorizontal) ++ delta += dx * increment; ++ break; ++ } ++ ++ adjustment.set_value(adjustment.get_value() + delta); ++ ++ return Clutter.EVENT_STOP; ++ }, ++ ++ _onDragBegin: function() { ++ this._dragCancelled = false; ++ this._dragMonitor = { ++ dragMotion: Lang.bind(this, this._onDragMotion) ++ }; ++ DND.addDragMonitor(this._dragMonitor); ++ ++ if (this._box.get_n_children() == 0) { ++ this._emptyDropTarget = new Dash.EmptyDropTargetItem(); ++ this._box.insert_child_at_index(this._emptyDropTarget, 0); ++ this._emptyDropTarget.show(true); ++ } ++ }, ++ ++ _onDragCancelled: function() { ++ this._dragCancelled = true; ++ this._endDrag(); ++ }, ++ ++ _onDragEnd: function() { ++ if (this._dragCancelled) ++ return; ++ ++ this._endDrag(); ++ }, ++ ++ _endDrag: function() { ++ this._clearDragPlaceholder(); ++ this._clearEmptyDropTarget(); ++ this._showAppsIcon.setDragApp(null); ++ DND.removeDragMonitor(this._dragMonitor); ++ }, ++ ++ _onDragMotion: function(dragEvent) { ++ let app = Dash.getAppFromSource(dragEvent.source); ++ if (app == null) ++ return DND.DragMotionResult.CONTINUE; ++ ++ let showAppsHovered = this._showAppsIcon.contains(dragEvent.targetActor); ++ ++ if (!this._box.contains(dragEvent.targetActor) || showAppsHovered) ++ this._clearDragPlaceholder(); ++ ++ if (showAppsHovered) ++ this._showAppsIcon.setDragApp(app); ++ else ++ this._showAppsIcon.setDragApp(null); ++ ++ return DND.DragMotionResult.CONTINUE; ++ }, ++ ++ _appIdListToHash: function(apps) { ++ let ids = {}; ++ for (let i = 0; i < apps.length; i++) ++ ids[apps[i].get_id()] = apps[i]; ++ return ids; ++ }, ++ ++ _queueRedisplay: function() { ++ Main.queueDeferredWork(this._workId); ++ }, ++ ++ _hookUpLabel: function(item, appIcon) { ++ item.child.connect('notify::hover', Lang.bind(this, function() { ++ this._syncLabel(item, appIcon); ++ })); ++ ++ let id = Main.overview.connect('hiding', Lang.bind(this, function() { ++ this._labelShowing = false; ++ item.hideLabel(); ++ })); ++ item.child.connect('destroy', function() { ++ Main.overview.disconnect(id); ++ }); ++ ++ if (appIcon) { ++ appIcon.connect('sync-tooltip', Lang.bind(this, function() { ++ this._syncLabel(item, appIcon); ++ })); ++ } ++ }, ++ ++ _createAppItem: function(app) { ++ let appIcon = new AppIcons.MyAppIcon(this._dtdSettings, this._remoteModel, app, this._monitorIndex, ++ { setSizeManually: true, ++ showLabel: false }); ++ ++ if (appIcon._draggable) { ++ appIcon._draggable.connect('drag-begin', Lang.bind(this, function() { ++ appIcon.actor.opacity = 50; ++ })); ++ appIcon._draggable.connect('drag-end', Lang.bind(this, function() { ++ appIcon.actor.opacity = 255; ++ })); ++ } ++ ++ appIcon.connect('menu-state-changed', Lang.bind(this, function(appIcon, opened) { ++ this._itemMenuStateChanged(item, opened); ++ })); ++ ++ let item = new Dash.DashItemContainer(); ++ ++ extendDashItemContainer(item, this._dtdSettings); ++ item.setChild(appIcon.actor); ++ ++ appIcon.actor.connect('notify::hover', Lang.bind(this, function() { ++ if (appIcon.actor.hover) { ++ this._ensureAppIconVisibilityTimeoutId = Mainloop.timeout_add(100, Lang.bind(this, function() { ++ ensureActorVisibleInScrollView(this._scrollView, appIcon.actor); ++ this._ensureAppIconVisibilityTimeoutId = 0; ++ return GLib.SOURCE_REMOVE; ++ })); ++ } ++ else { ++ if (this._ensureAppIconVisibilityTimeoutId > 0) { ++ Mainloop.source_remove(this._ensureAppIconVisibilityTimeoutId); ++ this._ensureAppIconVisibilityTimeoutId = 0; ++ } ++ } ++ })); ++ ++ appIcon.actor.connect('clicked', Lang.bind(this, function(actor) { ++ ensureActorVisibleInScrollView(this._scrollView, actor); ++ })); ++ ++ appIcon.actor.connect('key-focus-in', Lang.bind(this, function(actor) { ++ let [x_shift, y_shift] = ensureActorVisibleInScrollView(this._scrollView, actor); ++ ++ // This signal is triggered also by mouse click. The popup menu is opened at the original ++ // coordinates. Thus correct for the shift which is going to be applied to the scrollview. ++ if (appIcon._menu) { ++ appIcon._menu._boxPointer.xOffset = -x_shift; ++ appIcon._menu._boxPointer.yOffset = -y_shift; ++ } ++ })); ++ ++ // Override default AppIcon label_actor, now the ++ // accessible_name is set at DashItemContainer.setLabelText ++ appIcon.actor.label_actor = null; ++ item.setLabelText(app.get_name()); ++ ++ appIcon.icon.setIconSize(this.iconSize); ++ this._hookUpLabel(item, appIcon); ++ ++ return item; ++ }, ++ ++ /** ++ * Return an array with the "proper" appIcons currently in the dash ++ */ ++ getAppIcons: function() { ++ // Only consider children which are "proper" ++ // icons (i.e. ignoring drag placeholders) and which are not ++ // animating out (which means they will be destroyed at the end of ++ // the animation) ++ let iconChildren = this._box.get_children().filter(function(actor) { ++ return actor.child && ++ actor.child._delegate && ++ actor.child._delegate.icon && ++ !actor.animatingOut; ++ }); ++ ++ let appIcons = iconChildren.map(function(actor) { ++ return actor.child._delegate; ++ }); ++ ++ return appIcons; ++ }, ++ ++ _updateAppsIconGeometry: function() { ++ let appIcons = this.getAppIcons(); ++ appIcons.forEach(function(icon) { ++ icon.updateIconGeometry(); ++ }); ++ }, ++ ++ _itemMenuStateChanged: function(item, opened) { ++ // When the menu closes, it calls sync_hover, which means ++ // that the notify::hover handler does everything we need to. ++ if (opened) { ++ if (this._showLabelTimeoutId > 0) { ++ Mainloop.source_remove(this._showLabelTimeoutId); ++ this._showLabelTimeoutId = 0; ++ } ++ ++ item.hideLabel(); ++ } ++ else { ++ // I want to listen from outside when a menu is closed. I used to ++ // add a custom signal to the appIcon, since gnome 3.8 the signal ++ // calling this callback was added upstream. ++ this.emit('menu-closed'); ++ } ++ }, ++ ++ _syncLabel: function(item, appIcon) { ++ let shouldShow = appIcon ? appIcon.shouldShowTooltip() : item.child.get_hover(); ++ ++ if (shouldShow) { ++ if (this._showLabelTimeoutId == 0) { ++ let timeout = this._labelShowing ? 0 : DASH_ITEM_HOVER_TIMEOUT; ++ this._showLabelTimeoutId = Mainloop.timeout_add(timeout, Lang.bind(this, function() { ++ this._labelShowing = true; ++ item.showLabel(); ++ this._showLabelTimeoutId = 0; ++ return GLib.SOURCE_REMOVE; ++ })); ++ GLib.Source.set_name_by_id(this._showLabelTimeoutId, '[gnome-shell] item.showLabel'); ++ if (this._resetHoverTimeoutId > 0) { ++ Mainloop.source_remove(this._resetHoverTimeoutId); ++ this._resetHoverTimeoutId = 0; ++ } ++ } ++ } ++ else { ++ if (this._showLabelTimeoutId > 0) ++ Mainloop.source_remove(this._showLabelTimeoutId); ++ this._showLabelTimeoutId = 0; ++ item.hideLabel(); ++ if (this._labelShowing) { ++ this._resetHoverTimeoutId = Mainloop.timeout_add(DASH_ITEM_HOVER_TIMEOUT, Lang.bind(this, function() { ++ this._labelShowing = false; ++ this._resetHoverTimeoutId = 0; ++ return GLib.SOURCE_REMOVE; ++ })); ++ GLib.Source.set_name_by_id(this._resetHoverTimeoutId, '[gnome-shell] this._labelShowing'); ++ } ++ } ++ }, ++ ++ _adjustIconSize: function() { ++ // For the icon size, we only consider children which are "proper" ++ // icons (i.e. ignoring drag placeholders) and which are not ++ // animating out (which means they will be destroyed at the end of ++ // the animation) ++ let iconChildren = this._box.get_children().filter(function(actor) { ++ return actor.child && ++ actor.child._delegate && ++ actor.child._delegate.icon && ++ !actor.animatingOut; ++ }); ++ ++ iconChildren.push(this._showAppsIcon); ++ ++ if (this._maxHeight == -1) ++ return; ++ ++ // Check if the container is present in the stage. This avoids critical ++ // errors when unlocking the screen ++ if (!this._container.get_stage()) ++ return; ++ ++ let themeNode = this._container.get_theme_node(); ++ let maxAllocation = new Clutter.ActorBox({ ++ x1: 0, ++ y1: 0, ++ x2: this._isHorizontal ? this._maxHeight : 42 /* whatever */, ++ y2: this._isHorizontal ? 42 : this._maxHeight ++ }); ++ let maxContent = themeNode.get_content_box(maxAllocation); ++ let availHeight; ++ if (this._isHorizontal) ++ availHeight = maxContent.x2 - maxContent.x1; ++ else ++ availHeight = maxContent.y2 - maxContent.y1; ++ let spacing = themeNode.get_length('spacing'); ++ ++ let firstButton = iconChildren[0].child; ++ let firstIcon = firstButton._delegate.icon; ++ ++ let minHeight, natHeight, minWidth, natWidth; ++ ++ // Enforce the current icon size during the size request ++ firstIcon.setIconSize(this.iconSize); ++ [minHeight, natHeight] = firstButton.get_preferred_height(-1); ++ [minWidth, natWidth] = firstButton.get_preferred_width(-1); ++ ++ let scaleFactor = St.ThemeContext.get_for_stage(global.stage).scale_factor; ++ let iconSizes = this._availableIconSizes.map(function(s) { ++ return s * scaleFactor; ++ }); ++ ++ // Subtract icon padding and box spacing from the available height ++ if (this._isHorizontal) ++ availHeight -= iconChildren.length * (natWidth - this.iconSize * scaleFactor) + ++ (iconChildren.length - 1) * spacing; ++ else ++ availHeight -= iconChildren.length * (natHeight - this.iconSize * scaleFactor) + ++ (iconChildren.length - 1) * spacing; ++ ++ let availSize = availHeight / iconChildren.length; ++ ++ ++ let newIconSize = this._availableIconSizes[0]; ++ for (let i = 0; i < iconSizes.length; i++) { ++ if (iconSizes[i] < availSize) ++ newIconSize = this._availableIconSizes[i]; ++ } ++ ++ if (newIconSize == this.iconSize) ++ return; ++ ++ let oldIconSize = this.iconSize; ++ this.iconSize = newIconSize; ++ this.emit('icon-size-changed'); ++ ++ let scale = oldIconSize / newIconSize; ++ for (let i = 0; i < iconChildren.length; i++) { ++ let icon = iconChildren[i].child._delegate.icon; ++ ++ // Set the new size immediately, to keep the icons' sizes ++ // in sync with this.iconSize ++ icon.setIconSize(this.iconSize); ++ ++ // Don't animate the icon size change when the overview ++ // is transitioning, or when initially filling ++ // the dash ++ if (Main.overview.animationInProgress || ++ !this._shownInitially) ++ continue; ++ ++ let [targetWidth, targetHeight] = icon.icon.get_size(); ++ ++ // Scale the icon's texture to the previous size and ++ // tween to the new size ++ icon.icon.set_size(icon.icon.width * scale, ++ icon.icon.height * scale); ++ ++ Tweener.addTween(icon.icon, ++ { width: targetWidth, ++ height: targetHeight, ++ time: DASH_ANIMATION_TIME, ++ transition: 'easeOutQuad', ++ }); ++ } ++ }, ++ ++ _redisplay: function() { ++ let favorites = AppFavorites.getAppFavorites().getFavoriteMap(); ++ ++ let running = this._appSystem.get_running(); ++ if (this._dtdSettings.get_boolean('isolate-workspaces') || ++ this._dtdSettings.get_boolean('isolate-monitors')) { ++ // When using isolation, we filter out apps that have no windows in ++ // the current workspace ++ let settings = this._dtdSettings; ++ let monitorIndex = this._monitorIndex; ++ running = running.filter(function(_app) { ++ return AppIcons.getInterestingWindows(_app, settings, monitorIndex).length != 0; ++ }); ++ } ++ ++ let children = this._box.get_children().filter(function(actor) { ++ return actor.child && ++ actor.child._delegate && ++ actor.child._delegate.app; ++ }); ++ // Apps currently in the dash ++ let oldApps = children.map(function(actor) { ++ return actor.child._delegate.app; ++ }); ++ // Apps supposed to be in the dash ++ let newApps = []; ++ ++ if (this._dtdSettings.get_boolean('show-favorites')) { ++ for (let id in favorites) ++ newApps.push(favorites[id]); ++ } ++ ++ // We reorder the running apps so that they don't change position on the ++ // dash with every redisplay() call ++ if (this._dtdSettings.get_boolean('show-running')) { ++ // First: add the apps from the oldApps list that are still running ++ for (let i = 0; i < oldApps.length; i++) { ++ let index = running.indexOf(oldApps[i]); ++ if (index > -1) { ++ let app = running.splice(index, 1)[0]; ++ if (this._dtdSettings.get_boolean('show-favorites') && (app.get_id() in favorites)) ++ continue; ++ newApps.push(app); ++ } ++ } ++ // Second: add the new apps ++ for (let i = 0; i < running.length; i++) { ++ let app = running[i]; ++ if (this._dtdSettings.get_boolean('show-favorites') && (app.get_id() in favorites)) ++ continue; ++ newApps.push(app); ++ } ++ } ++ ++ // Figure out the actual changes to the list of items; we iterate ++ // over both the list of items currently in the dash and the list ++ // of items expected there, and collect additions and removals. ++ // Moves are both an addition and a removal, where the order of ++ // the operations depends on whether we encounter the position ++ // where the item has been added first or the one from where it ++ // was removed. ++ // There is an assumption that only one item is moved at a given ++ // time; when moving several items at once, everything will still ++ // end up at the right position, but there might be additional ++ // additions/removals (e.g. it might remove all the launchers ++ // and add them back in the new order even if a smaller set of ++ // additions and removals is possible). ++ // If above assumptions turns out to be a problem, we might need ++ // to use a more sophisticated algorithm, e.g. Longest Common ++ // Subsequence as used by diff. ++ ++ let addedItems = []; ++ let removedActors = []; ++ ++ let newIndex = 0; ++ let oldIndex = 0; ++ while ((newIndex < newApps.length) || (oldIndex < oldApps.length)) { ++ // No change at oldIndex/newIndex ++ if (oldApps[oldIndex] && oldApps[oldIndex] == newApps[newIndex]) { ++ oldIndex++; ++ newIndex++; ++ continue; ++ } ++ ++ // App removed at oldIndex ++ if (oldApps[oldIndex] && (newApps.indexOf(oldApps[oldIndex]) == -1)) { ++ removedActors.push(children[oldIndex]); ++ oldIndex++; ++ continue; ++ } ++ ++ // App added at newIndex ++ if (newApps[newIndex] && (oldApps.indexOf(newApps[newIndex]) == -1)) { ++ let newItem = this._createAppItem(newApps[newIndex]); ++ addedItems.push({ app: newApps[newIndex], ++ item: newItem, ++ pos: newIndex }); ++ newIndex++; ++ continue; ++ } ++ ++ // App moved ++ let insertHere = newApps[newIndex + 1] && (newApps[newIndex + 1] == oldApps[oldIndex]); ++ let alreadyRemoved = removedActors.reduce(function(result, actor) { ++ let removedApp = actor.child._delegate.app; ++ return result || removedApp == newApps[newIndex]; ++ }, false); ++ ++ if (insertHere || alreadyRemoved) { ++ let newItem = this._createAppItem(newApps[newIndex]); ++ addedItems.push({ ++ app: newApps[newIndex], ++ item: newItem, ++ pos: newIndex + removedActors.length ++ }); ++ newIndex++; ++ } ++ else { ++ removedActors.push(children[oldIndex]); ++ oldIndex++; ++ } ++ } ++ ++ for (let i = 0; i < addedItems.length; i++) ++ this._box.insert_child_at_index(addedItems[i].item, ++ addedItems[i].pos); ++ ++ for (let i = 0; i < removedActors.length; i++) { ++ let item = removedActors[i]; ++ ++ // Don't animate item removal when the overview is transitioning ++ if (!Main.overview.animationInProgress) ++ item.animateOutAndDestroy(); ++ else ++ item.destroy(); ++ } ++ ++ this._adjustIconSize(); ++ ++ for (let i = 0; i < addedItems.length; i++) ++ // Emit a custom signal notifying that a new item has been added ++ this.emit('item-added', addedItems[i]); ++ ++ // Skip animations on first run when adding the initial set ++ // of items, to avoid all items zooming in at once ++ ++ let animate = this._shownInitially && ++ !Main.overview.animationInProgress; ++ ++ if (!this._shownInitially) ++ this._shownInitially = true; ++ ++ for (let i = 0; i < addedItems.length; i++) ++ addedItems[i].item.show(animate); ++ ++ // Workaround for https://bugzilla.gnome.org/show_bug.cgi?id=692744 ++ // Without it, StBoxLayout may use a stale size cache ++ this._box.queue_relayout(); ++ ++ // This is required for icon reordering when the scrollview is used. ++ this._updateAppsIconGeometry(); ++ ++ // This will update the size, and the corresponding number for each icon ++ this._updateNumberOverlay(); ++ }, ++ ++ _updateNumberOverlay: function() { ++ let appIcons = this.getAppIcons(); ++ let counter = 1; ++ appIcons.forEach(function(icon) { ++ if (counter < 10){ ++ icon.setNumberOverlay(counter); ++ counter++; ++ } ++ else if (counter == 10) { ++ icon.setNumberOverlay(0); ++ counter++; ++ } ++ else { ++ // No overlay after 10 ++ icon.setNumberOverlay(-1); ++ } ++ icon.updateNumberOverlay(); ++ }); ++ ++ }, ++ ++ toggleNumberOverlay: function(activate) { ++ let appIcons = this.getAppIcons(); ++ appIcons.forEach(function(icon) { ++ icon.toggleNumberOverlay(activate); ++ }); ++ }, ++ ++ _initializeIconSize: function(max_size) { ++ let max_allowed = baseIconSizes[baseIconSizes.length-1]; ++ max_size = Math.min(max_size, max_allowed); ++ ++ if (this._dtdSettings.get_boolean('icon-size-fixed')) ++ this._availableIconSizes = [max_size]; ++ else { ++ this._availableIconSizes = baseIconSizes.filter(function(val) { ++ return (val numChildren) ++ pos = numChildren; ++ } ++ else ++ pos = 0; // always insert at the top when dash is empty ++ ++ // Take into account childredn position in rtl ++ if (this._isHorizontal && (Clutter.get_default_text_direction() == Clutter.TextDirection.RTL)) ++ pos = numChildren - pos; ++ ++ if ((pos != this._dragPlaceholderPos) && (pos <= numFavorites) && (this._animatingPlaceholdersCount == 0)) { ++ this._dragPlaceholderPos = pos; ++ ++ // Don't allow positioning before or after self ++ if ((favPos != -1) && (pos == favPos || pos == favPos + 1)) { ++ this._clearDragPlaceholder(); ++ return DND.DragMotionResult.CONTINUE; ++ } ++ ++ // If the placeholder already exists, we just move ++ // it, but if we are adding it, expand its size in ++ // an animation ++ let fadeIn; ++ if (this._dragPlaceholder) { ++ this._dragPlaceholder.destroy(); ++ fadeIn = false; ++ } ++ else ++ fadeIn = true; ++ ++ this._dragPlaceholder = new Dash.DragPlaceholderItem(); ++ this._dragPlaceholder.child.set_width (this.iconSize); ++ this._dragPlaceholder.child.set_height (this.iconSize / 2); ++ this._box.insert_child_at_index(this._dragPlaceholder, ++ this._dragPlaceholderPos); ++ this._dragPlaceholder.show(fadeIn); ++ // Ensure the next and previous icon are visible when moving the placeholder ++ // (I assume there's room for both of them) ++ if (this._dragPlaceholderPos > 1) ++ ensureActorVisibleInScrollView(this._scrollView, this._box.get_children()[this._dragPlaceholderPos-1]); ++ if (this._dragPlaceholderPos < this._box.get_children().length-1) ++ ensureActorVisibleInScrollView(this._scrollView, this._box.get_children()[this._dragPlaceholderPos+1]); ++ } ++ ++ // Remove the drag placeholder if we are not in the ++ // "favorites zone" ++ if (pos > numFavorites) ++ this._clearDragPlaceholder(); ++ ++ if (!this._dragPlaceholder) ++ return DND.DragMotionResult.NO_DROP; ++ ++ let srcIsFavorite = (favPos != -1); ++ ++ if (srcIsFavorite) ++ return DND.DragMotionResult.MOVE_DROP; ++ ++ return DND.DragMotionResult.COPY_DROP; ++ }, ++ ++ /** ++ * Draggable target interface ++ */ ++ acceptDrop: function(source, actor, x, y, time) { ++ let app = Dash.getAppFromSource(source); ++ ++ // Don't allow favoriting of transient apps ++ if (app == null || app.is_window_backed()) ++ return false; ++ ++ if (!this._settings.is_writable('favorite-apps') || !this._dtdSettings.get_boolean('show-favorites')) ++ return false; ++ ++ let id = app.get_id(); ++ ++ let favorites = AppFavorites.getAppFavorites().getFavoriteMap(); ++ ++ let srcIsFavorite = (id in favorites); ++ ++ let favPos = 0; ++ let children = this._box.get_children(); ++ for (let i = 0; i < this._dragPlaceholderPos; i++) { ++ if (this._dragPlaceholder && (children[i] == this._dragPlaceholder)) ++ continue; ++ ++ let childId = children[i].child._delegate.app.get_id(); ++ if (childId == id) ++ continue; ++ if (childId in favorites) ++ favPos++; ++ } ++ ++ // No drag placeholder means we don't wan't to favorite the app ++ // and we are dragging it to its original position ++ if (!this._dragPlaceholder) ++ return true; ++ ++ Meta.later_add(Meta.LaterType.BEFORE_REDRAW, Lang.bind(this, function() { ++ let appFavorites = AppFavorites.getAppFavorites(); ++ if (srcIsFavorite) ++ appFavorites.moveFavoriteToPos(id, favPos); ++ else ++ appFavorites.addFavoriteAtPos(id, favPos); ++ return false; ++ })); ++ ++ return true; ++ }, ++ ++ showShowAppsButton: function() { ++ this.showAppsButton.visible = true ++ this.showAppsButton.set_width(-1) ++ this.showAppsButton.set_height(-1) ++ }, ++ ++ hideShowAppsButton: function() { ++ this.showAppsButton.hide() ++ this.showAppsButton.set_width(0) ++ this.showAppsButton.set_height(0) ++ } ++}); ++ ++Signals.addSignalMethods(MyDash.prototype); ++ ++/** ++ * This is a copy of the same function in utils.js, but also adjust horizontal scrolling ++ * and perform few further cheks on the current value to avoid changing the values when ++ * it would be clamp to the current one in any case. ++ * Return the amount of shift applied ++ */ ++function ensureActorVisibleInScrollView(scrollView, actor) { ++ let adjust_v = true; ++ let adjust_h = true; ++ ++ let vadjustment = scrollView.vscroll.adjustment; ++ let hadjustment = scrollView.hscroll.adjustment; ++ let [vvalue, vlower, vupper, vstepIncrement, vpageIncrement, vpageSize] = vadjustment.get_values(); ++ let [hvalue, hlower, hupper, hstepIncrement, hpageIncrement, hpageSize] = hadjustment.get_values(); ++ ++ let [hvalue0, vvalue0] = [hvalue, vvalue]; ++ ++ let voffset = 0; ++ let hoffset = 0; ++ let fade = scrollView.get_effect('fade'); ++ if (fade) { ++ voffset = fade.vfade_offset; ++ hoffset = fade.hfade_offset; ++ } ++ ++ let box = actor.get_allocation_box(); ++ let y1 = box.y1, y2 = box.y2, x1 = box.x1, x2 = box.x2; ++ ++ let parent = actor.get_parent(); ++ while (parent != scrollView) { ++ if (!parent) ++ throw new Error('Actor not in scroll view'); ++ ++ let box = parent.get_allocation_box(); ++ y1 += box.y1; ++ y2 += box.y1; ++ x1 += box.x1; ++ x2 += box.x1; ++ parent = parent.get_parent(); ++ } ++ ++ if (y1 < vvalue + voffset) ++ vvalue = Math.max(0, y1 - voffset); ++ else if (vvalue < vupper - vpageSize && y2 > vvalue + vpageSize - voffset) ++ vvalue = Math.min(vupper -vpageSize, y2 + voffset - vpageSize); ++ ++ if (x1 < hvalue + hoffset) ++ hvalue = Math.max(0, x1 - hoffset); ++ else if (hvalue < hupper - hpageSize && x2 > hvalue + hpageSize - hoffset) ++ hvalue = Math.min(hupper - hpageSize, x2 + hoffset - hpageSize); ++ ++ if (vvalue !== vvalue0) { ++ Tweener.addTween(vadjustment, { value: vvalue, ++ time: Util.SCROLL_TIME, ++ transition: 'easeOutQuad' ++ }); ++ } ++ ++ if (hvalue !== hvalue0) { ++ Tweener.addTween(hadjustment, ++ { value: hvalue, ++ time: Util.SCROLL_TIME, ++ transition: 'easeOutQuad' }); ++ } ++ ++ return [hvalue- hvalue0, vvalue - vvalue0]; ++} +diff --git a/extensions/dash-to-dock/docking.js b/extensions/dash-to-dock/docking.js +new file mode 100644 +index 0000000..11810a1 +--- /dev/null ++++ b/extensions/dash-to-dock/docking.js +@@ -0,0 +1,1925 @@ ++// -*- mode: js; js-indent-level: 4; indent-tabs-mode: nil -*- ++ ++const Clutter = imports.gi.Clutter; ++const GLib = imports.gi.GLib; ++const Gtk = imports.gi.Gtk; ++const Lang = imports.lang; ++const Meta = imports.gi.Meta; ++const Shell = imports.gi.Shell; ++const St = imports.gi.St; ++const Mainloop = imports.mainloop; ++const Params = imports.misc.params; ++ ++const Main = imports.ui.main; ++const Dash = imports.ui.dash; ++const IconGrid = imports.ui.iconGrid; ++const Overview = imports.ui.overview; ++const OverviewControls = imports.ui.overviewControls; ++const PointerWatcher = imports.ui.pointerWatcher; ++const Tweener = imports.ui.tweener; ++const Signals = imports.signals; ++const ViewSelector = imports.ui.viewSelector; ++const WorkspaceSwitcherPopup= imports.ui.workspaceSwitcherPopup; ++const Layout = imports.ui.layout; ++const LayoutManager = imports.ui.main.layoutManager; ++ ++const Me = imports.misc.extensionUtils.getCurrentExtension(); ++const Convenience = Me.imports.convenience; ++const Utils = Me.imports.utils; ++const Intellihide = Me.imports.intellihide; ++const Theming = Me.imports.theming; ++const MyDash = Me.imports.dash; ++const LauncherAPI = Me.imports.launcherAPI; ++ ++const DOCK_DWELL_CHECK_INTERVAL = 100; ++ ++var State = { ++ HIDDEN: 0, ++ SHOWING: 1, ++ SHOWN: 2, ++ HIDING: 3 ++}; ++ ++const scrollAction = { ++ DO_NOTHING: 0, ++ CYCLE_WINDOWS: 1, ++ SWITCH_WORKSPACE: 2 ++}; ++ ++/** ++ * A simple St.Widget with one child whose allocation takes into account the ++ * slide out of its child via the _slidex parameter ([0:1]). ++ * ++ * Required since I want to track the input region of this container which is ++ * based on its allocation even if the child overlows the parent actor. By doing ++ * this the region of the dash that is slideout is not steling anymore the input ++ * regions making the extesion usable when the primary monitor is the right one. ++ * ++ * The slidex parameter can be used to directly animate the sliding. The parent ++ * must have a WEST (SOUTH) anchor_point to achieve the sliding to the RIGHT (BOTTOM) ++ * side. ++ * ++ * It can't be an extended object because of this: https://bugzilla.gnome.org/show_bug.cgi?id=688973. ++ * thus use the Shell.GenericContainer pattern. ++*/ ++const DashSlideContainer = new Lang.Class({ ++ Name: 'DashToDock.DashSlideContainer', ++ ++ _init: function(params) { ++ // Default local params ++ let localDefaults = { ++ side: St.Side.LEFT, ++ initialSlideValue: 1 ++ } ++ ++ let localParams = Params.parse(params, localDefaults, true); ++ ++ if (params) { ++ // Remove local params before passing the params to the parent ++ // constructor to avoid errors. ++ let prop; ++ for (prop in localDefaults) { ++ if ((prop in params)) ++ delete params[prop]; ++ } ++ } ++ ++ this.actor = new Shell.GenericContainer(params); ++ this.actor.connect('get-preferred-width', Lang.bind(this, this._getPreferredWidth)); ++ this.actor.connect('get-preferred-height', Lang.bind(this, this._getPreferredHeight)); ++ this.actor.connect('allocate', Lang.bind(this, this._allocate)); ++ ++ this.actor._delegate = this; ++ ++ this._child = null; ++ ++ // slide parameter: 1 = visible, 0 = hidden. ++ this._slidex = localParams.initialSlideValue; ++ this._side = localParams.side; ++ this._slideoutSize = 0; // minimum size when slided out ++ }, ++ ++ _allocate: function(actor, box, flags) { ++ if (this._child == null) ++ return; ++ ++ let availWidth = box.x2 - box.x1; ++ let availHeight = box.y2 - box.y1; ++ let [minChildWidth, minChildHeight, natChildWidth, natChildHeight] = ++ this._child.get_preferred_size(); ++ ++ let childWidth = natChildWidth; ++ let childHeight = natChildHeight; ++ ++ let childBox = new Clutter.ActorBox(); ++ ++ let slideoutSize = this._slideoutSize; ++ ++ if (this._side == St.Side.LEFT) { ++ childBox.x1 = (this._slidex -1) * (childWidth - slideoutSize); ++ childBox.x2 = slideoutSize + this._slidex*(childWidth - slideoutSize); ++ childBox.y1 = 0; ++ childBox.y2 = childBox.y1 + childHeight; ++ } ++ else if ((this._side == St.Side.RIGHT) || (this._side == St.Side.BOTTOM)) { ++ childBox.x1 = 0; ++ childBox.x2 = childWidth; ++ childBox.y1 = 0; ++ childBox.y2 = childBox.y1 + childHeight; ++ } ++ else if (this._side == St.Side.TOP) { ++ childBox.x1 = 0; ++ childBox.x2 = childWidth; ++ childBox.y1 = (this._slidex -1) * (childHeight - slideoutSize); ++ childBox.y2 = slideoutSize + this._slidex * (childHeight - slideoutSize); ++ } ++ ++ this._child.allocate(childBox, flags); ++ this._child.set_clip(-childBox.x1, -childBox.y1, ++ -childBox.x1+availWidth, -childBox.y1 + availHeight); ++ }, ++ ++ /** ++ * Just the child width but taking into account the slided out part ++ */ ++ _getPreferredWidth: function(actor, forHeight, alloc) { ++ let [minWidth, natWidth] = this._child.get_preferred_width(forHeight); ++ if ((this._side == St.Side.LEFT) || (this._side == St.Side.RIGHT)) { ++ minWidth = (minWidth - this._slideoutSize) * this._slidex + this._slideoutSize; ++ natWidth = (natWidth - this._slideoutSize) * this._slidex + this._slideoutSize; ++ } ++ ++ alloc.min_size = minWidth; ++ alloc.natural_size = natWidth; ++ }, ++ ++ /** ++ * Just the child height but taking into account the slided out part ++ */ ++ _getPreferredHeight: function(actor, forWidth, alloc) { ++ let [minHeight, natHeight] = this._child.get_preferred_height(forWidth); ++ if ((this._side == St.Side.TOP) || (this._side == St.Side.BOTTOM)) { ++ minHeight = (minHeight - this._slideoutSize) * this._slidex + this._slideoutSize; ++ natHeight = (natHeight - this._slideoutSize) * this._slidex + this._slideoutSize; ++ } ++ alloc.min_size = minHeight; ++ alloc.natural_size = natHeight; ++ }, ++ ++ /** ++ * I was expecting it to be a virtual function... stil I don't understand ++ * how things work. ++ */ ++ add_child: function(actor) { ++ // I'm supposed to have only on child ++ if (this._child !== null) ++ this.actor.remove_child(actor); ++ ++ this._child = actor; ++ this.actor.add_child(actor); ++ }, ++ ++ set slidex(value) { ++ this._slidex = value; ++ this._child.queue_relayout(); ++ }, ++ ++ get slidex() { ++ return this._slidex; ++ } ++}); ++ ++const DockedDash = new Lang.Class({ ++ Name: 'DashToDock.DockedDash', ++ ++ _init: function(settings, remoteModel, monitorIndex) { ++ this._rtl = (Clutter.get_default_text_direction() == Clutter.TextDirection.RTL); ++ ++ // Load settings ++ this._settings = settings; ++ this._remoteModel = remoteModel; ++ this._monitorIndex = monitorIndex; ++ // Connect global signals ++ this._signalsHandler = new Utils.GlobalSignalsHandler(); ++ ++ this._bindSettingsChanges(); ++ ++ this._position = Utils.getPosition(settings); ++ this._isHorizontal = ((this._position == St.Side.TOP) || (this._position == St.Side.BOTTOM)); ++ ++ // Temporary ignore hover events linked to autohide for whatever reason ++ this._ignoreHover = false; ++ this._oldignoreHover = null; ++ // This variables are linked to the settings regardles of autohide or intellihide ++ // being temporary disable. Get set by _updateVisibilityMode; ++ this._autohideIsEnabled = null; ++ this._intellihideIsEnabled = null; ++ this._fixedIsEnabled = null; ++ ++ // Create intellihide object to monitor windows overlapping ++ this._intellihide = new Intellihide.Intellihide(this._settings, this._monitorIndex); ++ ++ // initialize dock state ++ this._dockState = State.HIDDEN; ++ ++ // Put dock on the required monitor ++ this._monitor = Main.layoutManager.monitors[this._monitorIndex]; ++ ++ // this store size and the position where the dash is shown; ++ // used by intellihide module to check window overlap. ++ this.staticBox = new Clutter.ActorBox(); ++ ++ // Initialize pressure barrier variables ++ this._canUsePressure = false; ++ this._pressureBarrier = null; ++ this._barrier = null; ++ this._removeBarrierTimeoutId = 0; ++ ++ // Initialize dwelling system variables ++ this._dockDwelling = false; ++ this._dockWatch = null; ++ this._dockDwellUserTime = 0; ++ this._dockDwellTimeoutId = 0 ++ ++ // Create a new dash object ++ this.dash = new MyDash.MyDash(this._settings, this._remoteModel, this._monitorIndex); ++ ++ if (!this._settings.get_boolean('show-show-apps-button')) ++ this.dash.hideShowAppsButton(); ++ ++ // Create the main actor and the containers for sliding in and out and ++ // centering, turn on track hover ++ ++ let positionStyleClass = ['top', 'right', 'bottom', 'left']; ++ // This is the centering actor ++ this.actor = new St.Bin({ ++ name: 'dashtodockContainer', ++ reactive: false, ++ style_class: positionStyleClass[this._position], ++ x_align: this._isHorizontal?St.Align.MIDDLE:St.Align.START, ++ y_align: this._isHorizontal?St.Align.START:St.Align.MIDDLE ++ }); ++ this.actor._delegate = this; ++ ++ // This is the sliding actor whose allocation is to be tracked for input regions ++ this._slider = new DashSlideContainer({ ++ side: this._position, ++ initialSlideValue: 0 ++ }); ++ ++ // This is the actor whose hover status us tracked for autohide ++ this._box = new St.BoxLayout({ ++ name: 'dashtodockBox', ++ reactive: true, ++ track_hover: true ++ }); ++ this._box.connect('notify::hover', Lang.bind(this, this._hoverChanged)); ++ ++ // Create and apply height constraint to the dash. It's controlled by this.actor height ++ this.constrainSize = new Clutter.BindConstraint({ ++ source: this.actor, ++ coordinate: this._isHorizontal?Clutter.BindCoordinate.WIDTH:Clutter.BindCoordinate.HEIGHT ++ }); ++ this.dash.actor.add_constraint(this.constrainSize); ++ ++ this._signalsHandler.add([ ++ Main.overview, ++ 'item-drag-begin', ++ Lang.bind(this, this._onDragStart) ++ ], [ ++ Main.overview, ++ 'item-drag-end', ++ Lang.bind(this, this._onDragEnd) ++ ], [ ++ Main.overview, ++ 'item-drag-cancelled', ++ Lang.bind(this, this._onDragEnd) ++ ], [ ++ // update when workarea changes, for instance if other extensions modify the struts ++ //(like moving th panel at the bottom) ++ global.screen, ++ 'workareas-changed', ++ Lang.bind(this, this._resetPosition) ++ ], [ ++ Main.overview, ++ 'showing', ++ Lang.bind(this, this._onOverviewShowing) ++ ], [ ++ Main.overview, ++ 'hiding', ++ Lang.bind(this, this._onOverviewHiding) ++ ], [ ++ // Hide on appview ++ Main.overview.viewSelector, ++ 'page-changed', ++ Lang.bind(this, this._pageChanged) ++ ], [ ++ Main.overview.viewSelector, ++ 'page-empty', ++ Lang.bind(this, this._onPageEmpty) ++ ], [ ++ // Ensure the ShowAppsButton status is kept in sync ++ Main.overview.viewSelector._showAppsButton, ++ 'notify::checked', ++ Lang.bind(this, this._syncShowAppsButtonToggled) ++ ], [ ++ global.screen, ++ 'in-fullscreen-changed', ++ Lang.bind(this, this._updateBarrier) ++ ], [ ++ // Monitor windows overlapping ++ this._intellihide, ++ 'status-changed', ++ Lang.bind(this, this._updateDashVisibility) ++ ], [ ++ // Keep dragged icon consistent in size with this dash ++ this.dash, ++ 'icon-size-changed', ++ Lang.bind(this, function() { ++ Main.overview.dashIconSize = this.dash.iconSize; ++ }) ++ ], [ ++ // This duplicate the similar signal which is in owerview.js. ++ // Being connected and thus executed later this effectively ++ // overwrite any attempt to use the size of the default dash ++ //which given the customization is usually much smaller. ++ // I can't easily disconnect the original signal ++ Main.overview._controls.dash, ++ 'icon-size-changed', ++ Lang.bind(this, function() { ++ Main.overview.dashIconSize = this.dash.iconSize; ++ }) ++ ]); ++ ++ this._injectionsHandler = new Utils.InjectionsHandler(); ++ this._themeManager = new Theming.ThemeManager(this._settings, this); ++ ++ // Since the actor is not a topLevel child and its parent is now not added to the Chrome, ++ // the allocation change of the parent container (slide in and slideout) doesn't trigger ++ // anymore an update of the input regions. Force the update manually. ++ this.actor.connect('notify::allocation', ++ Lang.bind(Main.layoutManager, Main.layoutManager._queueUpdateRegions)); ++ ++ this.dash._container.connect('allocation-changed', Lang.bind(this, this._updateStaticBox)); ++ this._slider.actor.connect(this._isHorizontal ? 'notify::x' : 'notify::y', Lang.bind(this, this._updateStaticBox)); ++ ++ // sync hover after a popupmenu is closed ++ this.dash.connect('menu-closed', Lang.bind(this, function() { ++ this._box.sync_hover(); ++ })); ++ ++ // Load optional features that need to be activated for one dock only ++ if (this._monitorIndex == this._settings.get_int('preferred-monitor')) ++ this._enableExtraFeatures(); ++ // Load optional features that need to be activated once per dock ++ this._optionalScrollWorkspaceSwitch(); ++ ++ // Delay operations that require the shell to be fully loaded and with ++ // user theme applied. ++ ++ this._paintId = this.actor.connect('paint', Lang.bind(this, this._initialize)); ++ ++ // Manage the which is used to reserve space in the overview for the dock ++ // Add and additional dashSpacer positioned according to the dash positioning. ++ // It gets restored on extension unload. ++ this._dashSpacer = new OverviewControls.DashSpacer(); ++ this._dashSpacer.setDashActor(this._box); ++ ++ if (this._position == St.Side.LEFT) ++ Main.overview._controls._group.insert_child_at_index(this._dashSpacer, this._rtl ? -1 : 0); // insert on first ++ else if (this._position == St.Side.RIGHT) ++ Main.overview._controls._group.insert_child_at_index(this._dashSpacer, this._rtl ? 0 : -1); // insert on last ++ else if (this._position == St.Side.TOP) ++ Main.overview._overview.insert_child_at_index(this._dashSpacer, 0); ++ else if (this._position == St.Side.BOTTOM) ++ Main.overview._overview.insert_child_at_index(this._dashSpacer, -1); ++ ++ // Add dash container actor and the container to the Chrome. ++ this.actor.set_child(this._slider.actor); ++ this._slider.add_child(this._box); ++ this._box.add_actor(this.dash.actor); ++ ++ // Add aligning container without tracking it for input region ++ Main.uiGroup.add_child(this.actor); ++ ++ if (this._settings.get_boolean('dock-fixed')) { ++ // Note: tracking the fullscreen directly on the slider actor causes some hiccups when fullscreening ++ // windows of certain applications ++ Main.layoutManager._trackActor(this.actor, {affectsInputRegion: false, trackFullscreen: true}); ++ Main.layoutManager._trackActor(this._slider.actor, {affectsStruts: true}); ++ } ++ else ++ Main.layoutManager._trackActor(this._slider.actor); ++ ++ // Set initial position ++ this._resetDepth(); ++ this._resetPosition(); ++ }, ++ ++ _initialize: function() { ++ if (this._paintId > 0) { ++ this.actor.disconnect(this._paintId); ++ this._paintId=0; ++ } ++ ++ // Apply custome css class according to the settings ++ this._themeManager.updateCustomTheme(); ++ ++ // Since Gnome 3.8 dragging an app without having opened the overview before cause the attemp to ++ //animate a null target since some variables are not initialized when the viewSelector is created ++ if (Main.overview.viewSelector._activePage == null) ++ Main.overview.viewSelector._activePage = Main.overview.viewSelector._workspacesPage; ++ ++ this._updateVisibilityMode(); ++ ++ // In case we are already inside the overview when the extension is loaded, ++ // for instance on unlocking the screen if it was locked with the overview open. ++ if (Main.overview.visibleTarget) { ++ this._onOverviewShowing(); ++ this._pageChanged(); ++ } ++ ++ // Setup pressure barrier (GS38+ only) ++ this._updatePressureBarrier(); ++ this._updateBarrier(); ++ ++ // setup dwelling system if pressure barriers are not available ++ this._setupDockDwellIfNeeded(); ++ }, ++ ++ destroy: function() { ++ // Disconnect global signals ++ this._signalsHandler.destroy(); ++ // The dash, intellihide and themeManager have global signals as well internally ++ this.dash.destroy(); ++ this._intellihide.destroy(); ++ this._themeManager.destroy(); ++ ++ this._injectionsHandler.destroy(); ++ ++ // Destroy main clutter actor: this should be sufficient removing it and ++ // destroying all its children ++ this.actor.destroy(); ++ ++ // Remove barrier timeout ++ if (this._removeBarrierTimeoutId > 0) ++ Mainloop.source_remove(this._removeBarrierTimeoutId); ++ ++ // Remove existing barrier ++ this._removeBarrier(); ++ ++ // Remove pointer watcher ++ if (this._dockWatch) { ++ PointerWatcher.getPointerWatcher()._removeWatch(this._dockWatch); ++ this._dockWatch = null; ++ } ++ ++ // Remove the dashSpacer ++ this._dashSpacer.destroy(); ++ ++ // Restore legacyTray position ++ this._resetLegacyTray(); ++ ++ }, ++ ++ _bindSettingsChanges: function() { ++ this._signalsHandler.add([ ++ this._settings, ++ 'changed::scroll-action', ++ Lang.bind(this, function() { ++ this._optionalScrollWorkspaceSwitch(); ++ }) ++ ], [ ++ this._settings, ++ 'changed::dash-max-icon-size', ++ Lang.bind(this, function() { ++ this.dash.setIconSize(this._settings.get_int('dash-max-icon-size')); ++ }) ++ ], [ ++ this._settings, ++ 'changed::icon-size-fixed', ++ Lang.bind(this, function() { ++ this.dash.setIconSize(this._settings.get_int('dash-max-icon-size')); ++ }) ++ ], [ ++ this._settings, ++ 'changed::show-favorites', ++ Lang.bind(this, function() { ++ this.dash.resetAppIcons(); ++ }) ++ ], [ ++ this._settings, ++ 'changed::show-running', ++ Lang.bind(this, function() { ++ this.dash.resetAppIcons(); ++ }) ++ ], [ ++ this._settings, ++ 'changed::show-apps-at-top', ++ Lang.bind(this, function() { ++ this.dash.resetAppIcons(); ++ }) ++ ], [ ++ this._settings, ++ 'changed::show-show-apps-button', ++ Lang.bind(this, function() { ++ if (this._settings.get_boolean('show-show-apps-button')) ++ this.dash.showShowAppsButton(); ++ else ++ this.dash.hideShowAppsButton(); ++ }) ++ ], [ ++ this._settings, ++ 'changed::dock-fixed', ++ Lang.bind(this, function() { ++ if (this._settings.get_boolean('dock-fixed')) { ++ Main.layoutManager._untrackActor(this.actor); ++ Main.layoutManager._trackActor(this.actor, {affectsInputRegion: false, trackFullscreen: true}); ++ Main.layoutManager._untrackActor(this._slider.actor); ++ Main.layoutManager._trackActor(this._slider.actor, {affectsStruts: true}); ++ } else { ++ Main.layoutManager._untrackActor(this.actor); ++ Main.layoutManager._untrackActor(this._slider.actor); ++ Main.layoutManager._trackActor(this._slider.actor); ++ } ++ ++ this._resetPosition(); ++ ++ // Add or remove barrier depending on if dock-fixed ++ this._updateBarrier(); ++ ++ this._updateVisibilityMode(); ++ }) ++ ], [ ++ this._settings, ++ 'changed::intellihide', ++ Lang.bind(this, this._updateVisibilityMode) ++ ], [ ++ this._settings, ++ 'changed::intellihide-mode', ++ Lang.bind(this, function() { ++ this._intellihide.forceUpdate(); ++ }) ++ ], [ ++ this._settings, ++ 'changed::autohide', ++ Lang.bind(this, function() { ++ this._updateVisibilityMode(); ++ this._updateBarrier(); ++ }) ++ ], [ ++ this._settings, ++ 'changed::autohide-in-fullscreen', ++ Lang.bind(this, this._updateBarrier) ++ ], ++ [ ++ this._settings, ++ 'changed::extend-height', ++ Lang.bind(this, this._resetPosition) ++ ], [ ++ this._settings, ++ 'changed::height-fraction', ++ Lang.bind(this, this._resetPosition) ++ ], [ ++ this._settings, ++ 'changed::require-pressure-to-show', ++ Lang.bind(this, function() { ++ // Remove pointer watcher ++ if (this._dockWatch) { ++ PointerWatcher.getPointerWatcher()._removeWatch(this._dockWatch); ++ this._dockWatch = null; ++ } ++ this._setupDockDwellIfNeeded(); ++ this._updateBarrier(); ++ }) ++ ], [ ++ this._settings, ++ 'changed::pressure-threshold', ++ Lang.bind(this, function() { ++ this._updatePressureBarrier(); ++ this._updateBarrier(); ++ }) ++ ]); ++ ++ }, ++ ++ /** ++ * This is call when visibility settings change ++ */ ++ _updateVisibilityMode: function() { ++ if (this._settings.get_boolean('dock-fixed')) { ++ this._fixedIsEnabled = true; ++ this._autohideIsEnabled = false; ++ this._intellihideIsEnabled = false; ++ } ++ else { ++ this._fixedIsEnabled = false; ++ this._autohideIsEnabled = this._settings.get_boolean('autohide') ++ this._intellihideIsEnabled = this._settings.get_boolean('intellihide') ++ } ++ ++ if (this._intellihideIsEnabled) ++ this._intellihide.enable(); ++ else ++ this._intellihide.disable(); ++ ++ this._updateDashVisibility(); ++ }, ++ ++ /** ++ * Show/hide dash based on, in order of priority: ++ * overview visibility ++ * fixed mode ++ * intellihide ++ * autohide ++ * overview visibility ++ */ ++ _updateDashVisibility: function() { ++ if (Main.overview.visibleTarget) ++ return; ++ ++ if (this._fixedIsEnabled) { ++ this._removeAnimations(); ++ this._animateIn(this._settings.get_double('animation-time'), 0); ++ } ++ else if (this._intellihideIsEnabled) { ++ if (this._intellihide.getOverlapStatus()) { ++ this._ignoreHover = false; ++ // Do not hide if autohide is enabled and mouse is hover ++ if (!this._box.hover || !this._autohideIsEnabled) ++ this._animateOut(this._settings.get_double('animation-time'), 0); ++ } ++ else { ++ this._ignoreHover = true; ++ this._removeAnimations(); ++ this._animateIn(this._settings.get_double('animation-time'), 0); ++ } ++ } ++ else { ++ if (this._autohideIsEnabled) { ++ this._ignoreHover = false; ++ global.sync_pointer(); ++ ++ if (this._box.hover) ++ this._animateIn(this._settings.get_double('animation-time'), 0); ++ else ++ this._animateOut(this._settings.get_double('animation-time'), 0); ++ } ++ else ++ this._animateOut(this._settings.get_double('animation-time'), 0); ++ } ++ }, ++ ++ _onOverviewShowing: function() { ++ this._ignoreHover = true; ++ this._intellihide.disable(); ++ this._removeAnimations(); ++ this._animateIn(this._settings.get_double('animation-time'), 0); ++ }, ++ ++ _onOverviewHiding: function() { ++ this._ignoreHover = false; ++ this._intellihide.enable(); ++ this._updateDashVisibility(); ++ }, ++ ++ _hoverChanged: function() { ++ if (!this._ignoreHover) { ++ // Skip if dock is not in autohide mode for instance because it is shown ++ // by intellihide. ++ if (this._autohideIsEnabled) { ++ if (this._box.hover) ++ this._show(); ++ else ++ this._hide(); ++ } ++ } ++ }, ++ ++ getDockState: function() { ++ return this._dockState; ++ }, ++ ++ _show: function() { ++ if ((this._dockState == State.HIDDEN) || (this._dockState == State.HIDING)) { ++ if (this._dockState == State.HIDING) ++ // suppress all potential queued hiding animations - i.e. added to Tweener but not started, ++ // always give priority to show ++ this._removeAnimations(); ++ ++ this.emit('showing'); ++ this._animateIn(this._settings.get_double('animation-time'), 0); ++ } ++ }, ++ ++ _hide: function() { ++ // If no hiding animation is running or queued ++ if ((this._dockState == State.SHOWN) || (this._dockState == State.SHOWING)) { ++ let delay; ++ ++ if (this._dockState == State.SHOWING) ++ //if a show already started, let it finish; queue hide without removing the show. ++ // to obtain this I increase the delay to avoid the overlap and interference ++ // between the animations ++ delay = this._settings.get_double('hide-delay') + this._settings.get_double('animation-time'); ++ else ++ delay = this._settings.get_double('hide-delay'); ++ ++ this.emit('hiding'); ++ this._animateOut(this._settings.get_double('animation-time'), delay); ++ } ++ }, ++ ++ _animateIn: function(time, delay) { ++ this._dockState = State.SHOWING; ++ ++ Tweener.addTween(this._slider, { ++ slidex: 1, ++ time: time, ++ delay: delay, ++ transition: 'easeOutQuad', ++ onComplete: Lang.bind(this, function() { ++ this._dockState = State.SHOWN; ++ // Remove barrier so that mouse pointer is released and can access monitors on other side of dock ++ // NOTE: Delay needed to keep mouse from moving past dock and re-hiding dock immediately. This ++ // gives users an opportunity to hover over the dock ++ if (this._removeBarrierTimeoutId > 0) ++ Mainloop.source_remove(this._removeBarrierTimeoutId); ++ this._removeBarrierTimeoutId = Mainloop.timeout_add(100, Lang.bind(this, this._removeBarrier)); ++ }) ++ }); ++ }, ++ ++ _animateOut: function(time, delay) { ++ this._dockState = State.HIDING; ++ Tweener.addTween(this._slider, { ++ slidex: 0, ++ time: time, ++ delay: delay , ++ transition: 'easeOutQuad', ++ onComplete: Lang.bind(this, function() { ++ this._dockState = State.HIDDEN; ++ // Remove queued barried removal if any ++ if (this._removeBarrierTimeoutId > 0) ++ Mainloop.source_remove(this._removeBarrierTimeoutId); ++ this._updateBarrier(); ++ }) ++ }); ++ }, ++ ++ /** ++ * Dwelling system based on the GNOME Shell 3.14 messageTray code. ++ */ ++ _setupDockDwellIfNeeded: function() { ++ // If we don't have extended barrier features, then we need ++ // to support the old tray dwelling mechanism. ++ if (!global.display.supports_extended_barriers() || !this._settings.get_boolean('require-pressure-to-show')) { ++ let pointerWatcher = PointerWatcher.getPointerWatcher(); ++ this._dockWatch = pointerWatcher.addWatch(DOCK_DWELL_CHECK_INTERVAL, Lang.bind(this, this._checkDockDwell)); ++ this._dockDwelling = false; ++ this._dockDwellUserTime = 0; ++ } ++ }, ++ ++ _checkDockDwell: function(x, y) { ++ ++ let workArea = Main.layoutManager.getWorkAreaForMonitor(this._monitor.index) ++ let shouldDwell; ++ // Check for the correct screen edge, extending the sensitive area to the whole workarea, ++ // minus 1 px to avoid conflicting with other active corners. ++ if (this._position == St.Side.LEFT) ++ shouldDwell = (x == this._monitor.x) && (y > workArea.y) && (y < workArea.y + workArea.height); ++ else if (this._position == St.Side.RIGHT) ++ shouldDwell = (x == this._monitor.x + this._monitor.width - 1) && (y > workArea.y) && (y < workArea.y + workArea.height); ++ else if (this._position == St.Side.TOP) ++ shouldDwell = (y == this._monitor.y) && (x > workArea.x) && (x < workArea.x + workArea.width); ++ else if (this._position == St.Side.BOTTOM) ++ shouldDwell = (y == this._monitor.y + this._monitor.height - 1) && (x > workArea.x) && (x < workArea.x + workArea.width); ++ ++ if (shouldDwell) { ++ // We only set up dwell timeout when the user is not hovering over the dock ++ // already (!this._box.hover). ++ // The _dockDwelling variable is used so that we only try to ++ // fire off one dock dwell - if it fails (because, say, the user has the mouse down), ++ // we don't try again until the user moves the mouse up and down again. ++ if (!this._dockDwelling && !this._box.hover && (this._dockDwellTimeoutId == 0)) { ++ // Save the interaction timestamp so we can detect user input ++ let focusWindow = global.display.focus_window; ++ this._dockDwellUserTime = focusWindow ? focusWindow.user_time : 0; ++ ++ this._dockDwellTimeoutId = Mainloop.timeout_add(this._settings.get_double('show-delay') * 1000, ++ Lang.bind(this, this._dockDwellTimeout)); ++ GLib.Source.set_name_by_id(this._dockDwellTimeoutId, '[dash-to-dock] this._dockDwellTimeout'); ++ } ++ this._dockDwelling = true; ++ } ++ else { ++ this._cancelDockDwell(); ++ this._dockDwelling = false; ++ } ++ }, ++ ++ _cancelDockDwell: function() { ++ if (this._dockDwellTimeoutId != 0) { ++ Mainloop.source_remove(this._dockDwellTimeoutId); ++ this._dockDwellTimeoutId = 0; ++ } ++ }, ++ ++ _dockDwellTimeout: function() { ++ this._dockDwellTimeoutId = 0; ++ ++ if (!this._settings.get_boolean('autohide-in-fullscreen') && this._monitor.inFullscreen) ++ return GLib.SOURCE_REMOVE; ++ ++ // We don't want to open the tray when a modal dialog ++ // is up, so we check the modal count for that. When we are in the ++ // overview we have to take the overview's modal push into account ++ if (Main.modalCount > (Main.overview.visible ? 1 : 0)) ++ return GLib.SOURCE_REMOVE; ++ ++ // If the user interacted with the focus window since we started the tray ++ // dwell (by clicking or typing), don't activate the message tray ++ let focusWindow = global.display.focus_window; ++ let currentUserTime = focusWindow ? focusWindow.user_time : 0; ++ if (currentUserTime != this._dockDwellUserTime) ++ return GLib.SOURCE_REMOVE; ++ ++ // Reuse the pressure version function, the logic is the same ++ this._onPressureSensed(); ++ return GLib.SOURCE_REMOVE; ++ }, ++ ++ _updatePressureBarrier: function() { ++ this._canUsePressure = global.display.supports_extended_barriers(); ++ let pressureThreshold = this._settings.get_double('pressure-threshold'); ++ ++ // Remove existing pressure barrier ++ if (this._pressureBarrier) { ++ this._pressureBarrier.destroy(); ++ this._pressureBarrier = null; ++ } ++ ++ if (this._barrier) { ++ this._barrier.destroy(); ++ this._barrier = null; ++ } ++ ++ // Create new pressure barrier based on pressure threshold setting ++ if (this._canUsePressure) { ++ this._pressureBarrier = new Layout.PressureBarrier(pressureThreshold, this._settings.get_double('show-delay')*1000, ++ Shell.ActionMode.NORMAL | Shell.ActionMode.OVERVIEW); ++ this._pressureBarrier.connect('trigger', Lang.bind(this, function(barrier) { ++ if (!this._settings.get_boolean('autohide-in-fullscreen') && this._monitor.inFullscreen) ++ return; ++ this._onPressureSensed(); ++ })); ++ } ++ }, ++ ++ /** ++ * handler for mouse pressure sensed ++ */ ++ _onPressureSensed: function() { ++ if (Main.overview.visibleTarget) ++ return; ++ ++ // In case the mouse move away from the dock area before hovering it, in such case the leave event ++ // would never be triggered and the dock would stay visible forever. ++ let triggerTimeoutId = Mainloop.timeout_add(250, Lang.bind(this, function() { ++ triggerTimeoutId = 0; ++ ++ let [x, y, mods] = global.get_pointer(); ++ let shouldHide = true; ++ switch (this._position) { ++ case St.Side.LEFT: ++ if (x <= this.staticBox.x2 && ++ x >= this._monitor.x && ++ y >= this._monitor.y && ++ y <= this._monitor.y + this._monitor.height) { ++ shouldHide = false; ++ } ++ break; ++ case St.Side.RIGHT: ++ if (x >= this.staticBox.x1 && ++ x <= this._monitor.x + this._monitor.width && ++ y >= this._monitor.y && ++ y <= this._monitor.y + this._monitor.height) { ++ shouldHide = false; ++ } ++ break; ++ case St.Side.TOP: ++ if (x >= this._monitor.x && ++ x <= this._monitor.x + this._monitor.width && ++ y <= this.staticBox.y2 && ++ y >= this._monitor.y) { ++ shouldHide = false; ++ } ++ break; ++ case St.Side.BOTTOM: ++ if (x >= this._monitor.x && ++ x <= this._monitor.x + this._monitor.width && ++ y >= this.staticBox.y1 && ++ y <= this._monitor.y + this._monitor.height) { ++ shouldHide = false; ++ } ++ } ++ if (shouldHide) { ++ this._hoverChanged(); ++ return GLib.SOURCE_REMOVE; ++ } ++ else { ++ return GLib.SOURCE_CONTINUE; ++ } ++ ++ })); ++ ++ this._show(); ++ }, ++ ++ /** ++ * Remove pressure barrier ++ */ ++ _removeBarrier: function() { ++ if (this._barrier) { ++ if (this._pressureBarrier) ++ this._pressureBarrier.removeBarrier(this._barrier); ++ this._barrier.destroy(); ++ this._barrier = null; ++ } ++ this._removeBarrierTimeoutId = 0; ++ return false; ++ }, ++ ++ /** ++ * Update pressure barrier size ++ */ ++ _updateBarrier: function() { ++ // Remove existing barrier ++ this._removeBarrier(); ++ ++ // The barrier needs to be removed in fullscreen with autohide disabled, otherwise the mouse can ++ // get trapped on monitor. ++ if (this._monitor.inFullscreen && !this._settings.get_boolean('autohide-in-fullscreen')) ++ return ++ ++ // Manually reset pressure barrier ++ // This is necessary because we remove the pressure barrier when it is triggered to show the dock ++ if (this._pressureBarrier) { ++ this._pressureBarrier._reset(); ++ this._pressureBarrier._isTriggered = false; ++ } ++ ++ // Create new barrier ++ // The barrier extends to the whole workarea, minus 1 px to avoid conflicting with other active corners ++ // Note: dash in fixed position doesn't use pressure barrier. ++ if (this._canUsePressure && this._autohideIsEnabled && this._settings.get_boolean('require-pressure-to-show')) { ++ let x1, x2, y1, y2, direction; ++ let workArea = Main.layoutManager.getWorkAreaForMonitor(this._monitor.index) ++ ++ if (this._position == St.Side.LEFT) { ++ x1 = this._monitor.x + 1; ++ x2 = x1; ++ y1 = workArea.y + 1; ++ y2 = workArea.y + workArea.height - 1; ++ direction = Meta.BarrierDirection.POSITIVE_X; ++ } ++ else if (this._position == St.Side.RIGHT) { ++ x1 = this._monitor.x + this._monitor.width - 1; ++ x2 = x1; ++ y1 = workArea.y + 1; ++ y2 = workArea.y + workArea.height - 1; ++ direction = Meta.BarrierDirection.NEGATIVE_X; ++ } ++ else if (this._position == St.Side.TOP) { ++ x1 = workArea.x + 1; ++ x2 = workArea.x + workArea.width - 1; ++ y1 = this._monitor.y; ++ y2 = y1; ++ direction = Meta.BarrierDirection.POSITIVE_Y; ++ } ++ else if (this._position == St.Side.BOTTOM) { ++ x1 = workArea.x + 1; ++ x2 = workArea.x + workArea.width - 1; ++ y1 = this._monitor.y + this._monitor.height; ++ y2 = y1; ++ direction = Meta.BarrierDirection.NEGATIVE_Y; ++ } ++ ++ this._barrier = new Meta.Barrier({ ++ display: global.display, ++ x1: x1, ++ x2: x2, ++ y1: y1, ++ y2: y2, ++ directions: direction ++ }); ++ if (this._pressureBarrier) ++ this._pressureBarrier.addBarrier(this._barrier); ++ } ++ }, ++ ++ _isPrimaryMonitor: function() { ++ return (this._monitorIndex == Main.layoutManager.primaryIndex); ++ }, ++ ++ _resetPosition: function() { ++ // Ensure variables linked to settings are updated. ++ this._updateVisibilityMode(); ++ ++ let extendHeight = this._settings.get_boolean('extend-height'); ++ ++ // Note: do not use the workarea coordinates in the direction on which the dock is placed, ++ // to avoid a loop [position change -> workArea change -> position change] with ++ // fixed dock. ++ let workArea = Main.layoutManager.getWorkAreaForMonitor(this._monitorIndex); ++ ++ // Reserve space for the dash on the overview ++ // if the dock is on the primary monitor ++ if (this._isPrimaryMonitor()) ++ this._dashSpacer.show(); ++ else ++ // No space is required in the overview of the dash ++ this._dashSpacer.hide(); ++ ++ let fraction = this._settings.get_double('height-fraction'); ++ ++ if (extendHeight) ++ fraction = 1; ++ else if ((fraction < 0) || (fraction > 1)) ++ fraction = 0.95; ++ ++ let anchor_point; ++ ++ if (this._isHorizontal) { ++ this.actor.width = Math.round( fraction * workArea.width); ++ ++ let pos_y; ++ if (this._position == St.Side.BOTTOM) { ++ pos_y = this._monitor.y + this._monitor.height; ++ anchor_point = Clutter.Gravity.SOUTH_WEST; ++ } ++ else { ++ pos_y = this._monitor.y; ++ anchor_point = Clutter.Gravity.NORTH_WEST; ++ } ++ ++ this.actor.move_anchor_point_from_gravity(anchor_point); ++ this.actor.x = workArea.x + Math.round((1 - fraction) / 2 * workArea.width); ++ this.actor.y = pos_y; ++ ++ if (extendHeight) { ++ this.dash._container.set_width(this.actor.width); ++ this.actor.add_style_class_name('extended'); ++ } ++ else { ++ this.dash._container.set_width(-1); ++ this.actor.remove_style_class_name('extended'); ++ } ++ } ++ else { ++ this.actor.height = Math.round(fraction * workArea.height); ++ ++ let pos_x; ++ if (this._position == St.Side.RIGHT) { ++ pos_x = this._monitor.x + this._monitor.width; ++ anchor_point = Clutter.Gravity.NORTH_EAST; ++ } ++ else { ++ pos_x = this._monitor.x; ++ anchor_point = Clutter.Gravity.NORTH_WEST; ++ } ++ ++ this.actor.move_anchor_point_from_gravity(anchor_point); ++ this.actor.x = pos_x; ++ this.actor.y = workArea.y + Math.round((1 - fraction) / 2 * workArea.height); ++ ++ if (extendHeight) { ++ this.dash._container.set_height(this.actor.height); ++ this.actor.add_style_class_name('extended'); ++ } ++ else { ++ this.dash._container.set_height(-1); ++ this.actor.remove_style_class_name('extended'); ++ } ++ } ++ ++ this._y0 = this.actor.y; ++ ++ this._adjustLegacyTray(); ++ }, ++ ++ // Set the dash at the correct depth in z ++ _resetDepth: function() { ++ // Keep the dash below the modalDialogGroup and the legacyTray ++ if (Main.legacyTray && Main.legacyTray.actor) ++ Main.layoutManager.uiGroup.set_child_below_sibling(this.actor, Main.legacyTray.actor); ++ else ++ Main.layoutManager.uiGroup.set_child_below_sibling(this.actor, Main.layoutManager.modalDialogGroup); ++ }, ++ ++ _adjustLegacyTray: function() { ++ // The legacyTray has been removed in GNOME Shell 3.26. ++ // Once we drop support for previous releases this fuction can be dropped too. ++ if (!Main.legacyTray) ++ return; ++ ++ let use_work_area = true; ++ ++ if (this._fixedIsEnabled && !this._settings.get_boolean('extend-height') ++ && this._isPrimaryMonitor() ++ && ((this._position == St.Side.BOTTOM) || (this._position == St.Side.LEFT))) ++ use_work_area = false; ++ ++ Main.legacyTray.actor.clear_constraints(); ++ let constraint = new Layout.MonitorConstraint({ ++ primary: true, ++ work_area: use_work_area ++ }); ++ Main.legacyTray.actor.add_constraint(constraint); ++ }, ++ ++ _resetLegacyTray: function() { ++ // The legacyTray has been removed in GNOME Shell 3.26. ++ // Once we drop support for previous releases this fuction can be dropped too. ++ if (!Main.legacyTray) ++ return; ++ Main.legacyTray.actor.clear_constraints(); ++ let constraint = new Layout.MonitorConstraint({ ++ primary: true, ++ work_area: true ++ }); ++ Main.legacyTray.actor.add_constraint(constraint); ++ }, ++ ++ _updateStaticBox: function() { ++ this.staticBox.init_rect( ++ this.actor.x + this._slider.actor.x - (this._position == St.Side.RIGHT ? this._box.width : 0), ++ this.actor.y + this._slider.actor.y - (this._position == St.Side.BOTTOM ? this._box.height : 0), ++ this._box.width, ++ this._box.height ++ ); ++ ++ this._intellihide.updateTargetBox(this.staticBox); ++ }, ++ ++ _removeAnimations: function() { ++ Tweener.removeTweens(this._slider); ++ }, ++ ++ _onDragStart: function() { ++ // The dash need to be above the top_window_group, otherwise it doesn't ++ // accept dnd of app icons when not in overiew mode. ++ Main.layoutManager.uiGroup.set_child_above_sibling(this.actor, global.top_window_group); ++ this._oldignoreHover = this._ignoreHover; ++ this._ignoreHover = true; ++ this._animateIn(this._settings.get_double('animation-time'), 0); ++ }, ++ ++ _onDragEnd: function() { ++ // Restore drag default dash stack order ++ this._resetDepth(); ++ if (this._oldignoreHover !== null) ++ this._ignoreHover = this._oldignoreHover; ++ this._oldignoreHover = null; ++ this._box.sync_hover(); ++ if (Main.overview._shown) ++ this._pageChanged(); ++ }, ++ ++ _pageChanged: function() { ++ let activePage = Main.overview.viewSelector.getActivePage(); ++ let dashVisible = (activePage == ViewSelector.ViewPage.WINDOWS || ++ activePage == ViewSelector.ViewPage.APPS); ++ ++ if (dashVisible) ++ this._animateIn(this._settings.get_double('animation-time'), 0); ++ else ++ this._animateOut(this._settings.get_double('animation-time'), 0); ++ }, ++ ++ _onPageEmpty: function() { ++ /* The dash spacer is required only in the WINDOWS view if in the default position. ++ * The 'page-empty' signal is emitted in between a change of view, ++ * signalling the spacer can be added and removed without visible effect, ++ * as it's done for the upstream dashSpacer. ++ * ++ * Moreover, hiding the spacer ensure the appGrid allocaton is triggered. ++ * This matter as the appview spring animation is triggered by to first reallocaton of the appGrid, ++ * (See appDisplay.js, line 202 on GNOME Shell 3.14: ++ * this._grid.actor.connect('notify::allocation', ...) ++ * which in turn seems to be triggered by changes in the other actors in the overview. ++ * Normally, as far as I could understand, either the dashSpacer being hidden or the workspacesThumbnails ++ * sliding out would trigger the allocation. However, with no stock dash ++ * and no thumbnails, which happen if the user configured only 1 and static workspace, ++ * the animation out of icons is not played. ++ */ ++ ++ let activePage = Main.overview.viewSelector.getActivePage(); ++ this._dashSpacer.visible = (this._isHorizontal || activePage == ViewSelector.ViewPage.WINDOWS); ++ }, ++ ++ /** ++ * Show dock and give key focus to it ++ */ ++ _onAccessibilityFocus: function() { ++ this._box.navigate_focus(null, Gtk.DirectionType.TAB_FORWARD, false); ++ this._animateIn(this._settings.get_double('animation-time'), 0); ++ }, ++ ++ /** ++ * Keep ShowAppsButton status in sync with the overview status ++ */ ++ _syncShowAppsButtonToggled: function() { ++ let status = Main.overview.viewSelector._showAppsButton.checked; ++ if (this.dash.showAppsButton.checked !== status) ++ this.dash.showAppsButton.checked = status; ++ }, ++ ++ // Optional features to be enabled only for the main Dock ++ _enableExtraFeatures: function() { ++ // Restore dash accessibility ++ Main.ctrlAltTabManager.addGroup( ++ this.dash.actor, _('Dash'), 'user-bookmarks-symbolic', ++ {focusCallback: Lang.bind(this, this._onAccessibilityFocus)}); ++ }, ++ ++ /** ++ * Switch workspace by scrolling over the dock ++ */ ++ _optionalScrollWorkspaceSwitch: function() { ++ let label = 'optionalScrollWorkspaceSwitch'; ++ ++ function isEnabled() { ++ return this._settings.get_enum('scroll-action') === scrollAction.SWITCH_WORKSPACE; ++ } ++ ++ this._settings.connect('changed::scroll-action', Lang.bind(this, function() { ++ if (Lang.bind(this, isEnabled)()) ++ Lang.bind(this, enable)(); ++ else ++ Lang.bind(this, disable)(); ++ })); ++ ++ if (Lang.bind(this, isEnabled)()) ++ Lang.bind(this, enable)(); ++ ++ function enable() { ++ this._signalsHandler.removeWithLabel(label); ++ ++ this._signalsHandler.addWithLabel(label, [ ++ this._box, ++ 'scroll-event', ++ Lang.bind(this, onScrollEvent) ++ ]); ++ ++ this._optionalScrollWorkspaceSwitchDeadTimeId = 0; ++ } ++ ++ function disable() { ++ this._signalsHandler.removeWithLabel(label); ++ ++ if (this._optionalScrollWorkspaceSwitchDeadTimeId > 0) { ++ Mainloop.source_remove(this._optionalScrollWorkspaceSwitchDeadTimeId); ++ this._optionalScrollWorkspaceSwitchDeadTimeId = 0; ++ } ++ } ++ ++ // This was inspired to desktop-scroller@obsidien.github.com ++ function onScrollEvent(actor, event) { ++ // When in overview change workscape only in windows view ++ if (Main.overview.visible && Main.overview.viewSelector.getActivePage() !== ViewSelector.ViewPage.WINDOWS) ++ return false; ++ ++ let activeWs = global.screen.get_active_workspace(); ++ let direction = null; ++ ++ switch (event.get_scroll_direction()) { ++ case Clutter.ScrollDirection.UP: ++ direction = Meta.MotionDirection.UP; ++ break; ++ case Clutter.ScrollDirection.DOWN: ++ direction = Meta.MotionDirection.DOWN; ++ break; ++ case Clutter.ScrollDirection.SMOOTH: ++ let [dx, dy] = event.get_scroll_delta(); ++ if (dy < 0) ++ direction = Meta.MotionDirection.UP; ++ else if (dy > 0) ++ direction = Meta.MotionDirection.DOWN; ++ break; ++ } ++ ++ if (direction !== null) { ++ // Prevent scroll events from triggering too many workspace switches ++ // by adding a 250ms deadtime between each scroll event. ++ // Usefull on laptops when using a touchpad. ++ ++ // During the deadtime do nothing ++ if (this._optionalScrollWorkspaceSwitchDeadTimeId > 0) ++ return false; ++ else ++ this._optionalScrollWorkspaceSwitchDeadTimeId = Mainloop.timeout_add(250, Lang.bind(this, function() { ++ this._optionalScrollWorkspaceSwitchDeadTimeId = 0; ++ })); ++ ++ let ws; ++ ++ ws = activeWs.get_neighbor(direction) ++ ++ if (Main.wm._workspaceSwitcherPopup == null) ++ Main.wm._workspaceSwitcherPopup = new WorkspaceSwitcherPopup.WorkspaceSwitcherPopup(); ++ // Set the actor non reactive, so that it doesn't prevent the ++ // clicks events from reaching the dash actor. I can't see a reason ++ // why it should be reactive. ++ Main.wm._workspaceSwitcherPopup.actor.reactive = false; ++ Main.wm._workspaceSwitcherPopup.connect('destroy', function() { ++ Main.wm._workspaceSwitcherPopup = null; ++ }); ++ ++ // Do not show wokspaceSwithcer in overview ++ if (!Main.overview.visible) ++ Main.wm._workspaceSwitcherPopup.display(direction, ws.index()); ++ Main.wm.actionMoveWorkspace(ws); ++ ++ return true; ++ } ++ else ++ return false; ++ } ++ }, ++ ++ _activateApp: function(appIndex) { ++ let children = this.dash._box.get_children().filter(function(actor) { ++ return actor.child && ++ actor.child._delegate && ++ actor.child._delegate.app; ++ }); ++ ++ // Apps currently in the dash ++ let apps = children.map(function(actor) { ++ return actor.child._delegate; ++ }); ++ ++ // Activate with button = 1, i.e. same as left click ++ let button = 1; ++ if (appIndex < apps.length) ++ apps[appIndex].activate(button); ++ } ++ ++}); ++ ++Signals.addSignalMethods(DockedDash.prototype); ++ ++/* ++ * Handle keybaord shortcuts ++ */ ++const KeyboardShortcuts = new Lang.Class({ ++ ++ Name: 'DashToDock.KeyboardShortcuts', ++ ++ _numHotkeys: 10, ++ ++ _init: function(settings, allDocks){ ++ ++ this._settings = settings; ++ this._allDocks = allDocks; ++ this._signalsHandler = new Utils.GlobalSignalsHandler(); ++ ++ this._hotKeysEnabled = false; ++ if (this._settings.get_boolean('hot-keys')) ++ this._enableHotKeys(); ++ ++ this._signalsHandler.add([ ++ this._settings, ++ 'changed::hot-keys', ++ Lang.bind(this, function() { ++ if (this._settings.get_boolean('hot-keys')) ++ Lang.bind(this, this._enableHotKeys)(); ++ else ++ Lang.bind(this, this._disableHotKeys)(); ++ }) ++ ]); ++ ++ this._optionalNumberOverlay(); ++ }, ++ ++ destroy: function (){ ++ // Remove keybindings ++ this._disableHotKeys(); ++ this._disableExtraShortcut(); ++ this._signalsHandler.destroy(); ++ }, ++ ++ _enableHotKeys: function() { ++ if (this._hotKeysEnabled) ++ return; ++ ++ // Setup keyboard bindings for dash elements ++ let keys = ['app-hotkey-', 'app-shift-hotkey-', 'app-ctrl-hotkey-']; ++ keys.forEach( function(key) { ++ for (let i = 0; i < this._numHotkeys; i++) { ++ let appNum = i; ++ Main.wm.addKeybinding(key + (i + 1), this._settings, ++ Meta.KeyBindingFlags.NONE, ++ Shell.ActionMode.NORMAL | Shell.ActionMode.OVERVIEW, ++ Lang.bind(this, function() { ++ this._allDocks[0]._activateApp(appNum); ++ this._showOverlay(); ++ })); ++ } ++ }, this); ++ ++ this._hotKeysEnabled = true; ++ }, ++ ++ _disableHotKeys: function() { ++ if (!this._hotKeysEnabled) ++ return; ++ ++ let keys = ['app-hotkey-', 'app-shift-hotkey-', 'app-ctrl-hotkey-']; ++ keys.forEach( function(key) { ++ for (let i = 0; i < this._numHotkeys; i++) ++ Main.wm.removeKeybinding(key + (i + 1)); ++ }, this); ++ ++ this._hotKeysEnabled = false; ++ }, ++ ++ _optionalNumberOverlay: function() { ++ this._shortcutIsSet = false; ++ // Enable extra shortcut if either 'overlay' or 'show-dock' are true ++ if (this._settings.get_boolean('hot-keys') && ++ (this._settings.get_boolean('hotkeys-overlay') || this._settings.get_boolean('hotkeys-show-dock'))) ++ this._enableExtraShortcut(); ++ ++ this._signalsHandler.add([ ++ this._settings, ++ 'changed::hot-keys', ++ Lang.bind(this, this._checkHotkeysOptions) ++ ], [ ++ this._settings, ++ 'changed::hotkeys-overlay', ++ Lang.bind(this, this._checkHotkeysOptions) ++ ], [ ++ this._settings, ++ 'changed::hotkeys-show-dock', ++ Lang.bind(this, this._checkHotkeysOptions) ++ ]); ++ }, ++ ++ _checkHotkeysOptions: function() { ++ if (this._settings.get_boolean('hot-keys') && ++ (this._settings.get_boolean('hotkeys-overlay') || this._settings.get_boolean('hotkeys-show-dock'))) ++ this._enableExtraShortcut(); ++ else ++ this._disableExtraShortcut(); ++ }, ++ ++ _enableExtraShortcut: function() { ++ if (!this._shortcutIsSet) { ++ Main.wm.addKeybinding('shortcut', this._settings, ++ Meta.KeyBindingFlags.NONE, ++ Shell.ActionMode.NORMAL | Shell.ActionMode.OVERVIEW, ++ Lang.bind(this, this._showOverlay)); ++ this._shortcutIsSet = true; ++ } ++ }, ++ ++ _disableExtraShortcut: function() { ++ if (this._shortcutIsSet) { ++ Main.wm.removeKeybinding('shortcut'); ++ this._shortcutIsSet = false; ++ } ++ }, ++ ++ _showOverlay: function() { ++ for (let i = 0; i < this._allDocks.length; i++) { ++ let dock = this._allDocks[i]; ++ if (dock._settings.get_boolean('hotkeys-overlay')) ++ dock.dash.toggleNumberOverlay(true); ++ ++ // Restart the counting if the shortcut is pressed again ++ if (dock._numberOverlayTimeoutId) { ++ Mainloop.source_remove(dock._numberOverlayTimeoutId); ++ dock._numberOverlayTimeoutId = 0; ++ } ++ ++ // Hide the overlay/dock after the timeout ++ let timeout = dock._settings.get_double('shortcut-timeout') * 1000; ++ dock._numberOverlayTimeoutId = Mainloop.timeout_add(timeout, Lang.bind(dock, function() { ++ dock._numberOverlayTimeoutId = 0; ++ dock.dash.toggleNumberOverlay(false); ++ // Hide the dock again if necessary ++ dock._updateDashVisibility(); ++ })); ++ ++ // Show the dock if it is hidden ++ if (dock._settings.get_boolean('hotkeys-show-dock')) { ++ let showDock = (dock._intellihideIsEnabled || dock._autohideIsEnabled); ++ if (showDock) ++ dock._show(); ++ } ++ } ++ } ++ ++}); ++ ++/** ++ * Isolate overview to open new windows for inactive apps ++ * Note: the future implementaion is not fully contained here. Some bits are around in other methods of other classes. ++ * This class just take care of enabling/disabling the option. ++ */ ++const WorkspaceIsolation = new Lang.Class({ ++ ++ Name: 'DashToDock.WorkspaceIsolation', ++ ++ _init: function(settings, allDocks) { ++ ++ this._settings = settings; ++ this._allDocks = allDocks; ++ ++ this._signalsHandler = new Utils.GlobalSignalsHandler(); ++ this._injectionsHandler = new Utils.InjectionsHandler(); ++ ++ this._signalsHandler.add([ ++ this._settings, ++ 'changed::isolate-workspaces', ++ Lang.bind(this, function() { ++ this._allDocks.forEach(function(dock) { ++ dock.dash.resetAppIcons(); ++ }); ++ if (this._settings.get_boolean('isolate-workspaces') || ++ this._settings.get_boolean('isolate-monitors')) ++ Lang.bind(this, this._enable)(); ++ else ++ Lang.bind(this, this._disable)(); ++ }) ++ ],[ ++ this._settings, ++ 'changed::isolate-monitors', ++ Lang.bind(this, function() { ++ this._allDocks.forEach(function(dock) { ++ dock.dash.resetAppIcons(); ++ }); ++ if (this._settings.get_boolean('isolate-workspaces') || ++ this._settings.get_boolean('isolate-monitors')) ++ Lang.bind(this, this._enable)(); ++ else ++ Lang.bind(this, this._disable)(); ++ }) ++ ]); ++ ++ if (this._settings.get_boolean('isolate-workspaces') || ++ this._settings.get_boolean('isolate-monitors')) ++ this._enable(); ++ ++ }, ++ ++ _enable: function() { ++ ++ // ensure I never double-register/inject ++ // although it should never happen ++ this._disable(); ++ ++ this._allDocks.forEach(function(dock) { ++ this._signalsHandler.addWithLabel('isolation', [ ++ global.screen, ++ 'restacked', ++ Lang.bind(dock.dash, dock.dash._queueRedisplay) ++ ], [ ++ global.window_manager, ++ 'switch-workspace', ++ Lang.bind(dock.dash, dock.dash._queueRedisplay) ++ ]); ++ ++ // This last signal is only needed for monitor isolation, as windows ++ // might migrate from one monitor to another without triggering 'restacked' ++ if (this._settings.get_boolean('isolate-monitors')) ++ this._signalsHandler.addWithLabel('isolation', [ ++ global.screen, ++ 'window-entered-monitor', ++ Lang.bind(dock.dash, dock.dash._queueRedisplay) ++ ]); ++ ++ }, this); ++ ++ // here this is the Shell.App ++ function IsolatedOverview() { ++ // These lines take care of Nautilus for icons on Desktop ++ let windows = this.get_windows().filter(function(w) { ++ return w.get_workspace().index() == global.screen.get_active_workspace_index(); ++ }); ++ if (windows.length == 1) ++ if (windows[0].skip_taskbar) ++ return this.open_new_window(-1); ++ ++ if (this.is_on_workspace(global.screen.get_active_workspace())) ++ return Main.activateWindow(windows[0]); ++ return this.open_new_window(-1); ++ } ++ ++ this._injectionsHandler.addWithLabel('isolation', [ ++ Shell.App.prototype, ++ 'activate', ++ IsolatedOverview ++ ]); ++ }, ++ ++ _disable: function () { ++ this._signalsHandler.removeWithLabel('isolation'); ++ this._injectionsHandler.removeWithLabel('isolation'); ++ }, ++ ++ destroy: function() { ++ this._signalsHandler.destroy(); ++ this._injectionsHandler.destroy(); ++ } ++ ++}); ++ ++ ++var DockManager = new Lang.Class({ ++ Name: 'DashToDock.DockManager', ++ ++ _init: function() { ++ this._remoteModel = new LauncherAPI.LauncherEntryRemoteModel(); ++ this._settings = Convenience.getSettings('org.gnome.shell.extensions.dash-to-dock'); ++ this._oldDash = Main.overview._dash; ++ /* Array of all the docks created */ ++ this._allDocks = []; ++ this._createDocks(); ++ ++ // status variable: true when the overview is shown through the dash ++ // applications button. ++ this._forcedOverview = false; ++ ++ // Connect relevant signals to the toggling function ++ this._bindSettingsChanges(); ++ }, ++ ++ _toggle: function() { ++ this._deleteDocks(); ++ this._createDocks(); ++ this.emit('toggled'); ++ }, ++ ++ _bindSettingsChanges: function() { ++ // Connect relevant signals to the toggling function ++ this._signalsHandler = new Utils.GlobalSignalsHandler(); ++ this._signalsHandler.add([ ++ global.screen, ++ 'monitors-changed', ++ Lang.bind(this, this._toggle) ++ ], [ ++ this._settings, ++ 'changed::multi-monitor', ++ Lang.bind(this, this._toggle) ++ ], [ ++ this._settings, ++ 'changed::preferred-monitor', ++ Lang.bind(this, this._toggle) ++ ], [ ++ this._settings, ++ 'changed::dock-position', ++ Lang.bind(this, this._toggle) ++ ], [ ++ this._settings, ++ 'changed::extend-height', ++ Lang.bind(this, this._adjustPanelCorners) ++ ], [ ++ this._settings, ++ 'changed::dock-fixed', ++ Lang.bind(this, this._adjustPanelCorners) ++ ]); ++ }, ++ ++ _createDocks: function() { ++ ++ this._preferredMonitorIndex = this._settings.get_int('preferred-monitor'); ++ // In case of multi-monitor, we consider the dock on the primary monitor to be the preferred (main) one ++ // regardless of the settings ++ // The dock goes on the primary monitor also if the settings are incosistent (e.g. desired monitor not connected). ++ if (this._settings.get_boolean('multi-monitor') || ++ this._preferredMonitorIndex < 0 || this._preferredMonitorIndex > Main.layoutManager.monitors.length - 1 ++ ) { ++ this._preferredMonitorIndex = Main.layoutManager.primaryIndex; ++ } else { ++ // Gdk and shell monitors numbering differ at least under wayland: ++ // While the primary monitor appears to be always index 0 in Gdk, ++ // the shell can assign a different number (Main.layoutManager.primaryMonitor) ++ // This ensure the indexing in the settings (Gdk) and in the shell are matched, ++ // i.e. that we start counting from the primaryMonitorIndex ++ this._preferredMonitorIndex = (Main.layoutManager.primaryIndex + this._preferredMonitorIndex) % Main.layoutManager.monitors.length ; ++ } ++ ++ // First we create the main Dock, to get the extra features to bind to this one ++ let dock = new DockedDash(this._settings, this._remoteModel, this._preferredMonitorIndex); ++ this._mainShowAppsButton = dock.dash.showAppsButton; ++ this._allDocks.push(dock); ++ ++ // connect app icon into the view selector ++ dock.dash.showAppsButton.connect('notify::checked', Lang.bind(this, this._onShowAppsButtonToggled)); ++ ++ // Make the necessary changes to Main.overview._dash ++ this._prepareMainDash(); ++ ++ // Adjust corners if necessary ++ this._adjustPanelCorners(); ++ ++ if (this._settings.get_boolean('multi-monitor')) { ++ let nMon = Main.layoutManager.monitors.length; ++ for (let iMon = 0; iMon < nMon; iMon++) { ++ if (iMon == this._preferredMonitorIndex) ++ continue; ++ let dock = new DockedDash(this._settings, this._remoteModel, iMon); ++ this._allDocks.push(dock); ++ // connect app icon into the view selector ++ dock.dash.showAppsButton.connect('notify::checked', Lang.bind(this, this._onShowAppsButtonToggled)); ++ } ++ } ++ ++ // Load optional features. We load *after* the docks are created, since ++ // we need to connect the signals to all dock instances. ++ this._workspaceIsolation = new WorkspaceIsolation(this._settings, this._allDocks); ++ this._keyboardShortcuts = new KeyboardShortcuts(this._settings, this._allDocks); ++ }, ++ ++ _prepareMainDash: function() { ++ // Pretend I'm the dash: meant to make appgrd swarm animation come from the ++ // right position of the appShowButton. ++ Main.overview._dash = this._allDocks[0].dash; ++ ++ // set stored icon size to the new dash ++ Main.overview.dashIconSize = this._allDocks[0].dash.iconSize; ++ ++ // Hide usual Dash ++ Main.overview._controls.dash.actor.hide(); ++ ++ // Also set dash width to 1, so it's almost not taken into account by code ++ // calculaing the reserved space in the overview. The reason to keep it at 1 is ++ // to allow its visibility change to trigger an allocaion of the appGrid which ++ // in turn is triggergin the appsIcon spring animation, required when no other ++ // actors has this effect, i.e in horizontal mode and without the workspaceThumnails ++ // 1 static workspace only) ++ Main.overview._controls.dash.actor.set_width(1); ++ }, ++ ++ _deleteDocks: function() { ++ // Remove extra features ++ this._workspaceIsolation.destroy(); ++ this._keyboardShortcuts.destroy(); ++ ++ // Delete all docks ++ let nDocks = this._allDocks.length; ++ for (let i = nDocks-1; i >= 0; i--) { ++ this._allDocks[i].destroy(); ++ this._allDocks.pop(); ++ } ++ }, ++ ++ _restoreDash: function() { ++ Main.overview._controls.dash.actor.show(); ++ Main.overview._controls.dash.actor.set_width(-1); //reset default dash size ++ // This force the recalculation of the icon size ++ Main.overview._controls.dash._maxHeight = -1; ++ ++ // reset stored icon size to the default dash ++ Main.overview.dashIconSize = Main.overview._controls.dash.iconSize; ++ ++ Main.overview._dash = this._oldDash; ++ }, ++ ++ _onShowAppsButtonToggled: function(button) { ++ // Sync the status of the default appButtons. Only if the two statuses are ++ // different, that means the user interacted with the extension provided ++ // application button, cutomize the behaviour. Otherwise the shell has changed the ++ // status (due to the _syncShowAppsButtonToggled function below) and it ++ // has already performed the desired action. ++ ++ let animate = this._settings.get_boolean('animate-show-apps'); ++ let selector = Main.overview.viewSelector; ++ ++ if (selector._showAppsButton.checked !== button.checked) { ++ // find visible view ++ let visibleView; ++ Main.overview.viewSelector.appDisplay._views.every(function(v, index) { ++ if (v.view.actor.visible) { ++ visibleView = index; ++ return false; ++ } ++ else ++ return true; ++ }); ++ ++ if (button.checked) { ++ // force spring animation triggering.By default the animation only ++ // runs if we are already inside the overview. ++ if (!Main.overview._shown) { ++ this._forcedOverview = true; ++ let view = Main.overview.viewSelector.appDisplay._views[visibleView].view; ++ let grid = view._grid; ++ if (animate) { ++ // Animate in the the appview, hide the appGrid to avoiud flashing ++ // Go to the appView before entering the overview, skipping the workspaces. ++ // Do this manually avoiding opacity in transitions so that the setting of the opacity ++ // to 0 doesn't get overwritten. ++ Main.overview.viewSelector._activePage.opacity = 0; ++ Main.overview.viewSelector._activePage.hide(); ++ Main.overview.viewSelector._activePage = Main.overview.viewSelector._appsPage; ++ Main.overview.viewSelector._activePage.show(); ++ grid.actor.opacity = 0; ++ ++ // The animation has to be trigered manually because the AppDisplay.animate ++ // method is waiting for an allocation not happening, as we skip the workspace view ++ // and the appgrid could already be allocated from previous shown. ++ // It has to be triggered after the overview is shown as wrong coordinates are obtained ++ // otherwise. ++ let overviewShownId = Main.overview.connect('shown', Lang.bind(this, function() { ++ Main.overview.disconnect(overviewShownId); ++ Meta.later_add(Meta.LaterType.BEFORE_REDRAW, Lang.bind(this, function() { ++ grid.actor.opacity = 255; ++ grid.animateSpring(IconGrid.AnimationDirection.IN, this._allDocks[0].dash.showAppsButton); ++ })); ++ })); ++ } ++ else { ++ Main.overview.viewSelector._activePage = Main.overview.viewSelector._appsPage; ++ Main.overview.viewSelector._activePage.show(); ++ grid.actor.opacity = 255; ++ } ++ ++ } ++ ++ // Finally show the overview ++ selector._showAppsButton.checked = true; ++ Main.overview.show(); ++ } ++ else { ++ if (this._forcedOverview) { ++ // force exiting overview if needed ++ ++ if (animate) { ++ // Manually trigger springout animation without activating the ++ // workspaceView to avoid the zoomout animation. Hide the appPage ++ // onComplete to avoid ugly flashing of original icons. ++ let view = Main.overview.viewSelector.appDisplay._views[visibleView].view; ++ let grid = view._grid; ++ view.animate(IconGrid.AnimationDirection.OUT, Lang.bind(this, function() { ++ Main.overview.viewSelector._appsPage.hide(); ++ Main.overview.hide(); ++ selector._showAppsButton.checked = false; ++ this._forcedOverview = false; ++ })); ++ } ++ else { ++ Main.overview.hide(); ++ this._forcedOverview = false; ++ } ++ } ++ else { ++ selector._showAppsButton.checked = false; ++ this._forcedOverview = false; ++ } ++ } ++ } ++ ++ // whenever the button is unactivated even if not by the user still reset the ++ // forcedOverview flag ++ if (button.checked == false) ++ this._forcedOverview = false; ++ }, ++ ++ destroy: function() { ++ this._signalsHandler.destroy(); ++ this._deleteDocks(); ++ this._revertPanelCorners(); ++ this._restoreDash(); ++ this._remoteModel.destroy(); ++ }, ++ ++ /** ++ * Adjust Panel corners ++ */ ++ _adjustPanelCorners: function() { ++ let position = Utils.getPosition(this._settings); ++ let isHorizontal = ((position == St.Side.TOP) || (position == St.Side.BOTTOM)); ++ let extendHeight = this._settings.get_boolean('extend-height'); ++ let fixedIsEnabled = this._settings.get_boolean('dock-fixed'); ++ let dockOnPrimary = this._settings.get_boolean('multi-monitor') || ++ this._preferredMonitorIndex == Main.layoutManager.primaryIndex; ++ ++ if (!isHorizontal && dockOnPrimary && extendHeight && fixedIsEnabled) { ++ Main.panel._rightCorner.actor.hide(); ++ Main.panel._leftCorner.actor.hide(); ++ } ++ else ++ this._revertPanelCorners(); ++ }, ++ ++ _revertPanelCorners: function() { ++ Main.panel._leftCorner.actor.show(); ++ Main.panel._rightCorner.actor.show(); ++ } ++}); ++Signals.addSignalMethods(DockManager.prototype); +diff --git a/extensions/dash-to-dock/extension.js b/extensions/dash-to-dock/extension.js +new file mode 100644 +index 0000000..97c1dbb +--- /dev/null ++++ b/extensions/dash-to-dock/extension.js +@@ -0,0 +1,23 @@ ++// -*- mode: js; js-indent-level: 4; indent-tabs-mode: nil -*- ++ ++const Me = imports.misc.extensionUtils.getCurrentExtension(); ++const Docking = Me.imports.docking; ++const Convenience = Me.imports.convenience; ++ ++// We declare this with var so it can be accessed by other extensions in ++// GNOME Shell 3.26+ (mozjs52+). ++var dockManager; ++ ++function init() { ++ Convenience.initTranslations('dashtodock'); ++} ++ ++function enable() { ++ dockManager = new Docking.DockManager(); ++} ++ ++function disable() { ++ dockManager.destroy(); ++ ++ dockManager=null; ++} +diff --git a/extensions/dash-to-dock/intellihide.js b/extensions/dash-to-dock/intellihide.js +new file mode 100644 +index 0000000..1fd2699 +--- /dev/null ++++ b/extensions/dash-to-dock/intellihide.js +@@ -0,0 +1,323 @@ ++// -*- mode: js; js-indent-level: 4; indent-tabs-mode: nil -*- ++ ++const GLib = imports.gi.GLib; ++const Lang = imports.lang; ++const Mainloop = imports.mainloop; ++const Meta = imports.gi.Meta; ++const Shell = imports.gi.Shell; ++ ++const Main = imports.ui.main; ++const Signals = imports.signals; ++ ++const Me = imports.misc.extensionUtils.getCurrentExtension(); ++const Utils = Me.imports.utils; ++ ++// A good compromise between reactivity and efficiency; to be tuned. ++const INTELLIHIDE_CHECK_INTERVAL = 100; ++ ++const OverlapStatus = { ++ UNDEFINED: -1, ++ FALSE: 0, ++ TRUE: 1 ++}; ++ ++const IntellihideMode = { ++ ALL_WINDOWS: 0, ++ FOCUS_APPLICATION_WINDOWS: 1, ++ MAXIMIZED_WINDOWS : 2 ++}; ++ ++// List of windows type taken into account. Order is important (keep the original ++// enum order). ++const handledWindowTypes = [ ++ Meta.WindowType.NORMAL, ++ Meta.WindowType.DOCK, ++ Meta.WindowType.DIALOG, ++ Meta.WindowType.MODAL_DIALOG, ++ Meta.WindowType.TOOLBAR, ++ Meta.WindowType.MENU, ++ Meta.WindowType.UTILITY, ++ Meta.WindowType.SPLASHSCREEN ++]; ++ ++/** ++ * A rough and ugly implementation of the intellihide behaviour. ++ * Intallihide object: emit 'status-changed' signal when the overlap of windows ++ * with the provided targetBoxClutter.ActorBox changes; ++ */ ++var Intellihide = new Lang.Class({ ++ Name: 'DashToDock.Intellihide', ++ ++ _init: function(settings, monitorIndex) { ++ // Load settings ++ this._settings = settings; ++ this._monitorIndex = monitorIndex; ++ ++ this._signalsHandler = new Utils.GlobalSignalsHandler(); ++ this._tracker = Shell.WindowTracker.get_default(); ++ this._focusApp = null; // The application whose window is focused. ++ this._topApp = null; // The application whose window is on top on the monitor with the dock. ++ ++ this._isEnabled = false; ++ this.status = OverlapStatus.UNDEFINED; ++ this._targetBox = null; ++ ++ this._checkOverlapTimeoutContinue = false; ++ this._checkOverlapTimeoutId = 0; ++ ++ this._trackedWindows = new Map(); ++ ++ // Connect global signals ++ this._signalsHandler.add([ ++ // Add signals on windows created from now on ++ global.display, ++ 'window-created', ++ Lang.bind(this, this._windowCreated) ++ ], [ ++ // triggered for instance when the window list order changes, ++ // included when the workspace is switched ++ global.screen, ++ 'restacked', ++ Lang.bind(this, this._checkOverlap) ++ ], [ ++ // when windows are alwasy on top, the focus window can change ++ // without the windows being restacked. Thus monitor window focus change. ++ this._tracker, ++ 'notify::focus-app', ++ Lang.bind(this, this._checkOverlap) ++ ], [ ++ // update wne monitor changes, for instance in multimonitor when monitor are attached ++ global.screen, ++ 'monitors-changed', ++ Lang.bind(this, this._checkOverlap ) ++ ]); ++ }, ++ ++ destroy: function() { ++ // Disconnect global signals ++ this._signalsHandler.destroy(); ++ ++ // Remove residual windows signals ++ this.disable(); ++ }, ++ ++ enable: function() { ++ this._isEnabled = true; ++ this._status = OverlapStatus.UNDEFINED; ++ global.get_window_actors().forEach(function(wa) { ++ this._addWindowSignals(wa); ++ }, this); ++ this._doCheckOverlap(); ++ }, ++ ++ disable: function() { ++ this._isEnabled = false; ++ ++ for (let wa of this._trackedWindows.keys()) { ++ this._removeWindowSignals(wa); ++ } ++ this._trackedWindows.clear(); ++ ++ if (this._checkOverlapTimeoutId > 0) { ++ Mainloop.source_remove(this._checkOverlapTimeoutId); ++ this._checkOverlapTimeoutId = 0; ++ } ++ }, ++ ++ _windowCreated: function(display, metaWindow) { ++ this._addWindowSignals(metaWindow.get_compositor_private()); ++ }, ++ ++ _addWindowSignals: function(wa) { ++ if (!this._handledWindow(wa)) ++ return; ++ let signalId = wa.connect('allocation-changed', Lang.bind(this, this._checkOverlap, wa.get_meta_window())); ++ this._trackedWindows.set(wa, signalId); ++ wa.connect('destroy', Lang.bind(this, this._removeWindowSignals)); ++ }, ++ ++ _removeWindowSignals: function(wa) { ++ if (this._trackedWindows.get(wa)) { ++ wa.disconnect(this._trackedWindows.get(wa)); ++ this._trackedWindows.delete(wa); ++ } ++ ++ }, ++ ++ updateTargetBox: function(box) { ++ this._targetBox = box; ++ this._checkOverlap(); ++ }, ++ ++ forceUpdate: function() { ++ this._status = OverlapStatus.UNDEFINED; ++ this._doCheckOverlap(); ++ }, ++ ++ getOverlapStatus: function() { ++ return (this._status == OverlapStatus.TRUE); ++ }, ++ ++ _checkOverlap: function() { ++ if (!this._isEnabled || (this._targetBox == null)) ++ return; ++ ++ /* Limit the number of calls to the doCheckOverlap function */ ++ if (this._checkOverlapTimeoutId) { ++ this._checkOverlapTimeoutContinue = true; ++ return ++ } ++ ++ this._doCheckOverlap(); ++ ++ this._checkOverlapTimeoutId = Mainloop.timeout_add(INTELLIHIDE_CHECK_INTERVAL, Lang.bind(this, function() { ++ this._doCheckOverlap(); ++ if (this._checkOverlapTimeoutContinue) { ++ this._checkOverlapTimeoutContinue = false; ++ return GLib.SOURCE_CONTINUE; ++ } else { ++ this._checkOverlapTimeoutId = 0; ++ return GLib.SOURCE_REMOVE; ++ } ++ })); ++ }, ++ ++ _doCheckOverlap: function() { ++ ++ if (!this._isEnabled || (this._targetBox == null)) ++ return; ++ ++ let overlaps = OverlapStatus.FALSE; ++ let windows = global.get_window_actors(); ++ ++ if (windows.length > 0) { ++ /* ++ * Get the top window on the monitor where the dock is placed. ++ * The idea is that we dont want to overlap with the windows of the topmost application, ++ * event is it's not the focused app -- for instance because in multimonitor the user ++ * select a window in the secondary monitor. ++ */ ++ ++ let topWindow = null; ++ for (let i = windows.length - 1; i >= 0; i--) { ++ let meta_win = windows[i].get_meta_window(); ++ if (this._handledWindow(windows[i]) && (meta_win.get_monitor() == this._monitorIndex)) { ++ topWindow = meta_win; ++ break; ++ } ++ } ++ ++ if (topWindow !== null) { ++ this._topApp = this._tracker.get_window_app(topWindow); ++ // If there isn't a focused app, use that of the window on top ++ this._focusApp = this._tracker.focus_app || this._topApp ++ ++ windows = windows.filter(this._intellihideFilterInteresting, this); ++ ++ for (let i = 0; i < windows.length; i++) { ++ let win = windows[i].get_meta_window(); ++ ++ if (win) { ++ let rect = win.get_frame_rect(); ++ ++ let test = (rect.x < this._targetBox.x2) && ++ (rect.x + rect.width > this._targetBox.x1) && ++ (rect.y < this._targetBox.y2) && ++ (rect.y + rect.height > this._targetBox.y1); ++ ++ if (test) { ++ overlaps = OverlapStatus.TRUE; ++ break; ++ } ++ } ++ } ++ } ++ } ++ ++ if (this._status !== overlaps) { ++ this._status = overlaps; ++ this.emit('status-changed', this._status); ++ } ++ ++ }, ++ ++ // Filter interesting windows to be considered for intellihide. ++ // Consider all windows visible on the current workspace. ++ // Optionally skip windows of other applications ++ _intellihideFilterInteresting: function(wa) { ++ let meta_win = wa.get_meta_window(); ++ if (!this._handledWindow(wa)) ++ return false; ++ ++ let currentWorkspace = global.screen.get_active_workspace_index(); ++ let wksp = meta_win.get_workspace(); ++ let wksp_index = wksp.index(); ++ ++ // Depending on the intellihide mode, exclude non-relevent windows ++ switch (this._settings.get_enum('intellihide-mode')) { ++ case IntellihideMode.ALL_WINDOWS: ++ // Do nothing ++ break; ++ ++ case IntellihideMode.FOCUS_APPLICATION_WINDOWS: ++ // Skip windows of other apps ++ if (this._focusApp) { ++ // The DropDownTerminal extension is not an application per se ++ // so we match its window by wm class instead ++ if (meta_win.get_wm_class() == 'DropDownTerminalWindow') ++ return true; ++ ++ let currentApp = this._tracker.get_window_app(meta_win); ++ let focusWindow = global.display.get_focus_window() ++ ++ // Consider half maximized windows side by side ++ // and windows which are alwayson top ++ if((currentApp != this._focusApp) && (currentApp != this._topApp) ++ && !((focusWindow && focusWindow.maximized_vertically && !focusWindow.maximized_horizontally) ++ && (meta_win.maximized_vertically && !meta_win.maximized_horizontally) ++ && meta_win.get_monitor() == focusWindow.get_monitor()) ++ && !meta_win.is_above()) ++ return false; ++ } ++ break; ++ ++ case IntellihideMode.MAXIMIZED_WINDOWS: ++ // Skip unmaximized windows ++ if (!meta_win.maximized_vertically && !meta_win.maximized_horizontally) ++ return false; ++ break; ++ } ++ ++ if ( wksp_index == currentWorkspace && meta_win.showing_on_its_workspace() ) ++ return true; ++ else ++ return false; ++ ++ }, ++ ++ // Filter windows by type ++ // inspired by Opacify@gnome-shell.localdomain.pl ++ _handledWindow: function(wa) { ++ let metaWindow = wa.get_meta_window(); ++ ++ if (!metaWindow) ++ return false; ++ ++ // The DropDownTerminal extension uses the POPUP_MENU window type hint ++ // so we match its window by wm class instead ++ if (metaWindow.get_wm_class() == 'DropDownTerminalWindow') ++ return true; ++ ++ let wtype = metaWindow.get_window_type(); ++ for (let i = 0; i < handledWindowTypes.length; i++) { ++ var hwtype = handledWindowTypes[i]; ++ if (hwtype == wtype) ++ return true; ++ else if (hwtype > wtype) ++ return false; ++ } ++ return false; ++ } ++}); ++ ++Signals.addSignalMethods(Intellihide.prototype); +diff --git a/extensions/dash-to-dock/launcherAPI.js b/extensions/dash-to-dock/launcherAPI.js +new file mode 100644 +index 0000000..d051a70 +--- /dev/null ++++ b/extensions/dash-to-dock/launcherAPI.js +@@ -0,0 +1,244 @@ ++// -*- mode: js; js-indent-level: 4; indent-tabs-mode: nil -*- ++ ++const Gio = imports.gi.Gio; ++const Lang = imports.lang; ++const Signals = imports.signals; ++ ++var LauncherEntryRemoteModel = new Lang.Class({ ++ Name: 'DashToDock.LauncherEntryRemoteModel', ++ ++ _init: function () { ++ this._entriesByDBusName = {}; ++ ++ this._launcher_entry_dbus_signal_id = ++ Gio.DBus.session.signal_subscribe(null, // sender ++ 'com.canonical.Unity.LauncherEntry', // iface ++ null, // member ++ null, // path ++ null, // arg0 ++ Gio.DBusSignalFlags.NONE, ++ Lang.bind(this, this._onEntrySignalReceived)); ++ ++ this._dbus_name_owner_changed_signal_id = ++ Gio.DBus.session.signal_subscribe('org.freedesktop.DBus', // sender ++ 'org.freedesktop.DBus', // interface ++ 'NameOwnerChanged', // member ++ '/org/freedesktop/DBus', // path ++ null, // arg0 ++ Gio.DBusSignalFlags.NONE, ++ Lang.bind(this, this._onDBusNameOwnerChanged)); ++ ++ this._acquireUnityDBus(); ++ }, ++ ++ destroy: function () { ++ if (this._launcher_entry_dbus_signal_id) { ++ Gio.DBus.session.signal_unsubscribe(this._launcher_entry_dbus_signal_id); ++ } ++ ++ if (this._dbus_name_owner_changed_signal_id) { ++ Gio.DBus.session.signal_unsubscribe(this._dbus_name_owner_changed_signal_id); ++ } ++ ++ this._releaseUnityDBus(); ++ }, ++ ++ size: function () { ++ return Object.keys(this._entriesByDBusName).length; ++ }, ++ ++ lookupByDBusName: function (dbusName) { ++ return this._entriesByDBusName.hasOwnProperty(dbusName) ? this._entriesByDBusName[dbusName] : null; ++ }, ++ ++ lookupById: function (appId) { ++ let ret = []; ++ for (let dbusName in this._entriesByDBusName) { ++ let entry = this._entriesByDBusName[dbusName]; ++ if (entry && entry.appId() == appId) { ++ ret.push(entry); ++ } ++ } ++ ++ return ret; ++ }, ++ ++ addEntry: function (entry) { ++ let existingEntry = this.lookupByDBusName(entry.dbusName()); ++ if (existingEntry) { ++ existingEntry.update(entry); ++ } else { ++ this._entriesByDBusName[entry.dbusName()] = entry; ++ this.emit('entry-added', entry); ++ } ++ }, ++ ++ removeEntry: function (entry) { ++ delete this._entriesByDBusName[entry.dbusName()] ++ this.emit('entry-removed', entry); ++ }, ++ ++ _acquireUnityDBus: function () { ++ if (!this._unity_bus_id) { ++ Gio.DBus.session.own_name('com.canonical.Unity', ++ Gio.BusNameOwnerFlags.ALLOW_REPLACEMENT, null, null); ++ } ++ }, ++ ++ _releaseUnityDBus: function () { ++ if (this._unity_bus_id) { ++ Gio.DBus.session.unown_name(this._unity_bus_id); ++ this._unity_bus_id = 0; ++ } ++ }, ++ ++ _onEntrySignalReceived: function (connection, sender_name, object_path, ++ interface_name, signal_name, parameters, user_data) { ++ if (!parameters || !signal_name) ++ return; ++ ++ if (signal_name == 'Update') { ++ if (!sender_name) { ++ return; ++ } ++ ++ this._handleUpdateRequest(sender_name, parameters); ++ } ++ }, ++ ++ _onDBusNameOwnerChanged: function (connection, sender_name, object_path, ++ interface_name, signal_name, parameters, user_data) { ++ if (!parameters || !this.size()) ++ return; ++ ++ let [name, before, after] = parameters.deep_unpack(); ++ ++ if (!after) { ++ if (this._entriesByDBusName.hasOwnProperty(before)) { ++ this.removeEntry(this._entriesByDBusName[before]); ++ } ++ } ++ }, ++ ++ _handleUpdateRequest: function (senderName, parameters) { ++ if (!senderName || !parameters) { ++ return; ++ } ++ ++ let [appUri, properties] = parameters.deep_unpack(); ++ let appId = appUri.replace(/(^\w+:|^)\/\//, ''); ++ let entry = this.lookupByDBusName(senderName); ++ ++ if (entry) { ++ entry.setDBusName(senderName); ++ entry.update(properties); ++ } else { ++ let entry = new LauncherEntryRemote(senderName, appId, properties); ++ this.addEntry(entry); ++ } ++ }, ++}); ++ ++Signals.addSignalMethods(LauncherEntryRemoteModel.prototype); ++ ++var LauncherEntryRemote = new Lang.Class({ ++ Name: 'DashToDock.LauncherEntryRemote', ++ ++ _init: function (dbusName, appId, properties) { ++ this._dbusName = dbusName; ++ this._appId = appId; ++ this._count = 0; ++ this._countVisible = false; ++ this._progress = 0.0; ++ this._progressVisible = false; ++ this.update(properties); ++ }, ++ ++ appId: function () { ++ return this._appId; ++ }, ++ ++ dbusName: function () { ++ return this._dbusName; ++ }, ++ ++ count: function () { ++ return this._count; ++ }, ++ ++ setCount: function (count) { ++ if (this._count != count) { ++ this._count = count; ++ this.emit('count-changed', this._count); ++ } ++ }, ++ ++ countVisible: function () { ++ return this._countVisible; ++ }, ++ ++ setCountVisible: function (countVisible) { ++ if (this._countVisible != countVisible) { ++ this._countVisible = countVisible; ++ this.emit('count-visible-changed', this._countVisible); ++ } ++ }, ++ ++ progress: function () { ++ return this._progress; ++ }, ++ ++ setProgress: function (progress) { ++ if (this._progress != progress) { ++ this._progress = progress; ++ this.emit('progress-changed', this._progress); ++ } ++ }, ++ ++ progressVisible: function () { ++ return this._progressVisible; ++ }, ++ ++ setProgressVisible: function (progressVisible) { ++ if (this._progressVisible != progressVisible) { ++ this._progressVisible = progressVisible; ++ this.emit('progress-visible-changed', this._progressVisible); ++ } ++ }, ++ ++ setDBusName: function (dbusName) { ++ if (this._dbusName != dbusName) { ++ let oldName = this._dbusName; ++ this._dbusName = dbusName; ++ this.emit('dbus-name-changed', oldName); ++ } ++ }, ++ ++ update: function (other) { ++ if (other instanceof LauncherEntryRemote) { ++ this.setDBusName(other.dbusName()) ++ this.setCount(other.count()); ++ this.setCountVisible(other.countVisible()); ++ this.setProgress(other.progress()); ++ this.setProgressVisible(other.progressVisible()) ++ } else { ++ for (let property in other) { ++ if (other.hasOwnProperty(property)) { ++ if (property == 'count') { ++ this.setCount(other[property].get_int64()); ++ } else if (property == 'count-visible') { ++ this.setCountVisible(other[property].get_boolean()); ++ } if (property == 'progress') { ++ this.setProgress(other[property].get_double()); ++ } else if (property == 'progress-visible') { ++ this.setProgressVisible(other[property].get_boolean()); ++ } else { ++ // Not implemented yet ++ } ++ } ++ } ++ } ++ }, ++}); ++ ++Signals.addSignalMethods(LauncherEntryRemote.prototype); +diff --git a/extensions/dash-to-dock/media/glossy.svg b/extensions/dash-to-dock/media/glossy.svg +new file mode 100644 +index 0000000..55b71ba +--- /dev/null ++++ b/extensions/dash-to-dock/media/glossy.svg +@@ -0,0 +1,139 @@ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ image/svg+xml ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ +diff --git a/extensions/dash-to-dock/media/logo.svg b/extensions/dash-to-dock/media/logo.svg +new file mode 100644 +index 0000000..eebd0b1 +--- /dev/null ++++ b/extensions/dash-to-dock/media/logo.svg +@@ -0,0 +1,528 @@ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ image/svg+xml ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ Dash to Dock ++ Michele ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ +diff --git a/extensions/dash-to-dock/meson.build b/extensions/dash-to-dock/meson.build +new file mode 100644 +index 0000000..e0906fa +--- /dev/null ++++ b/extensions/dash-to-dock/meson.build +@@ -0,0 +1,23 @@ ++extension_data += configure_file( ++ input: metadata_name + '.in', ++ output: metadata_name, ++ configuration: metadata_conf ++) ++ ++extension_sources += files( ++ 'appIconIndicators.js', ++ 'appIcons.js', ++ 'convenience.js', ++ 'dash.js', ++ 'docking.js', ++ 'intellihide.js', ++ 'launcherAPI.js', ++ 'prefs.js', ++ 'Settings.ui', ++ 'theming.js', ++ 'utils.js', ++ 'windowPreview.js' ++) ++extension_schemas += files(metadata_conf.get('gschemaname') + '.gschema.xml') ++ ++install_data(['media/logo.svg', 'media/glossy.svg'], install_dir: join_paths(extensiondir, uuid, 'media')) +diff --git a/extensions/dash-to-dock/metadata.json.in b/extensions/dash-to-dock/metadata.json.in +new file mode 100644 +index 0000000..90eddb5 +--- /dev/null ++++ b/extensions/dash-to-dock/metadata.json.in +@@ -0,0 +1,12 @@ ++{ ++"extension-id": "@extension_id@", ++"uuid": "@uuid@", ++"settings-schema": "@gschemaname@", ++"gettext-domain": "@gettext_domain@", ++"original-author": "micxgx@gmail.com", ++"name": "Dash to Dock", ++"description": "A dock for the Gnome Shell. This extension moves the dash out of the overview transforming it in a dock for an easier launching of applications and a faster switching between windows and desktops. Side and bottom placement options are available.", ++"shell-version": [ "@shell_current@" ], ++"version": 45, ++"url": "https://micheleg.github.io/dash-to-dock/" ++} +diff --git a/extensions/dash-to-dock/org.gnome.shell.extensions.dash-to-dock.gschema.xml b/extensions/dash-to-dock/org.gnome.shell.extensions.dash-to-dock.gschema.xml +new file mode 100644 +index 0000000..3e4f68a +--- /dev/null ++++ b/extensions/dash-to-dock/org.gnome.shell.extensions.dash-to-dock.gschema.xml +@@ -0,0 +1,540 @@ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ 'LEFT' ++ Dock position ++ Dock is shown on the Left, Right, Top or Bottom side of the screen. ++ ++ ++ 0.2 ++ Animation time ++ Sets the time duration of the autohide effect. ++ ++ ++ 0.25 ++ Show delay ++ Sets the delay after the mouse reaches the screen border before showing the dock. ++ ++ ++ 0.20 ++ Show delay ++ Sets the delay after the mouse left the dock before hiding it. ++ ++ ++ false ++ Set a custom dash background background color ++ Sets the color for the dash background. ++ ++ ++ "#ffffff" ++ Dash background color. ++ Customize the background color of the dash. ++ ++ ++ 'DEFAULT' ++ Transparency mode for the dock ++ FIXED: constant transparency. ADAPTIVE: lock state with the top panel when not hidden. DYNAMIC: dock takes the opaque style only when windows are close to it. ++ ++ ++ 'DEFAULT' ++ ... ++ DEFAULT: .... DOTS: .... ++ ++ ++ false ++ Use application icon dominant color for the indicator color ++ ++ ++ ++ false ++ Manually set the min and max opacity ++ For Adaptive and Dynamic modes, the min/max opacity values will be given by 'min-alpha' and 'max-alpha'. ++ ++ ++ 0.2 ++ Opacity of the dash background when free-floating ++ Sets the opacity of the dash background when no windows are close. ++ ++ ++ 0.8 ++ Opacity of the dash background when windows are close. ++ Sets the opacity of the dash background when windows are close. ++ ++ ++ 0.8 ++ Opacity of the dash background ++ Sets the opacity of the dash background when in autohide mode. ++ ++ ++ true ++ Dock dodges windows ++ Enable or disable intellihide mode ++ ++ ++ 'FOCUS_APPLICATION_WINDOWS' ++ Define which windows are considered for intellihide. ++ ++ ++ ++ true ++ Dock shown on mouse over ++ Enable or disable autohide mode ++ ++ ++ true ++ Require pressure to show dash ++ Enable or disable requiring pressure to show the dash ++ ++ ++ 100 ++ Pressure threshold ++ Sets how much pressure is needed to show the dash. ++ ++ ++ false ++ Enable autohide in fullscreen mode. ++ Enable autohide in fullscreen mode. ++ ++ ++ false ++ Dock always visible ++ Dock is always visible ++ ++ ++ true ++ Switch workspace by scrolling over the dock ++ Add the possibility to switch workspace by mouse scrolling over the dock. ++ ++ ++ 48 ++ Maximum dash icon size ++ Set the allowed maximum dash icon size. Allowed range: 16..64. ++ ++ ++ false ++ Fixed icon size ++ Keep the icon size fived by scrolling the dock. ++ ++ ++ false ++ Apply custom theme ++ Apply customization to the dash appearance ++ ++ ++ false ++ TODO ++ TODO ++ ++ ++ false ++ Customize the style of the running application indicators. ++ Customize the style of the running application indicators. ++ ++ ++ "#ffffff" ++ Running application indicators color ++ Customize the color of the running application indicators. ++ ++ ++ "#ffffff" ++ Running application indicators border color. ++ Customize the border color of the running application indicators. ++ ++ ++ 0 ++ Running application indicators border width. ++ Customize the border width of the running application indicators. ++ ++ ++ true ++ Show running apps ++ Show or hide running appplications icons in the dash ++ ++ ++ false ++ Provide workspace isolation ++ Dash shows only windows from the currentworkspace ++ ++ ++ false ++ Provide monitor isolation ++ Dash shows only windows from the monitor ++ ++ ++ true ++ Show preview of the open windows ++ Replace open windows list with windows previews ++ ++ ++ true ++ Show favorites apps ++ Show or hide favorite appplications icons in the dash ++ ++ ++ true ++ Show applications button ++ Show appplications button in the dash ++ ++ ++ false ++ Show application button at top ++ Show appplication button at top of the dash ++ ++ ++ true ++ Animate Show Applications from the desktop ++ Animate Show Applications from the desktop ++ ++ ++ true ++ Basic compatibility with bolt extensions ++ Make the extension work properly when bolt extensions is enabled ++ ++ ++ 0.90 ++ Dock max height (fraction of available space) ++ ++ ++ false ++ Extend the dock container to all the available height ++ ++ ++ -1 ++ Monitor on which putting the dock ++ Set on which monitor to put the dock, use -1 for the primary one ++ ++ ++ false ++ Enable multi-monitor docks ++ Show a dock on every monitor ++ ++ ++ true ++ Minimize on shift+click ++ ++ ++ true ++ Activate only one window ++ ++ ++ 'cycle-windows' ++ Action when clicking on a running app ++ Set the action that is executed when clicking on the icon of a running application ++ ++ ++ 'do-nothing' ++ Action when scrolling app ++ Set the action that is executed when scrolling on the application icon ++ ++ ++ 'minimize' ++ Action when shit+clicking on a running app ++ Set the action that is executed when shift+clicking on the icon of a running application ++ ++ ++ 'launch' ++ Action when clicking on a running app ++ Set the action that is executed when middle-clicking on the icon of a running application ++ ++ ++ 'launch' ++ Action when clicking on a running app ++ Set the action that is executed when shift+middle-clicking on the icon of a running application ++ ++ ++ true ++ Super Hot-Keys ++ Launch and switch between dash items using Super+(0-9) ++ ++ ++ true ++ Show the dock when using the hotkeys ++ The dock will be quickly shown so that the number-overlay is visible and app activation is easier ++ ++ ++ "<Super>q" ++ Keybinding to show the dock and the number overlay. ++ Behavior depends on hotkeys-show-dock and hotkeys-overlay. ++ ++ ++ q']]]> ++ Keybinding to show the dock and the number overlay. ++ Behavior depends on hotkeys-show-dock and hotkeys-overlay. ++ ++ ++ 2 ++ Timeout to hide the dock ++ Sets the time duration before the dock is hidden again. ++ ++ ++ true ++ Show the dock when using the hotkeys ++ The dock will be quickly shown so that the number-overlay is visible and app activation is easier ++ ++ ++ 1']]]> ++ Keybinding to launch 1st dash app ++ ++ Keybinding to launch 1st app. ++ ++ ++ ++ 2']]]> ++ Keybinding to launch 2nd dash app ++ ++ Keybinding to launch 2nd app. ++ ++ ++ ++ 3']]]> ++ Keybinding to launch 3rd dash app ++ ++ Keybinding to launch 3rd app. ++ ++ ++ ++ 4']]]> ++ Keybinding to launch 4th dash app ++ ++ Keybinding to launch 4th app. ++ ++ ++ ++ 5']]]> ++ Keybinding to launch 5th dash app ++ ++ Keybinding to launch 5th app. ++ ++ ++ ++ 6']]]> ++ Keybinding to launch 6th dash app ++ ++ Keybinding to launch 6th app. ++ ++ ++ ++ 7']]]> ++ Keybinding to launch 7th dash app ++ ++ Keybinding to launch 7th app. ++ ++ ++ ++ 8']]]> ++ Keybinding to launch 8th dash app ++ ++ Keybinding to launch 8th app. ++ ++ ++ ++ 9']]]> ++ Keybinding to launch 9th dash app ++ ++ Keybinding to launch 9th app. ++ ++ ++ ++ 0']]]> ++ Keybinding to launch 10th dash app ++ ++ Keybinding to launch 10th app. ++ ++ ++ ++ 1']]]> ++ Keybinding to trigger 1st dash app with shift behavior ++ ++ Keybinding to trigger 1st app with shift behavior. ++ ++ ++ ++ 2']]]> ++ Keybinding to trigger 2nd dash app with shift behavior ++ ++ Keybinding to trigger 2nd app with shift behavior. ++ ++ ++ ++ 3']]]> ++ Keybinding to trigger 3rd dash app with shift behavior ++ ++ Keybinding to trigger 3rd app with shift behavior. ++ ++ ++ ++ 4']]]> ++ Keybinding to trigger 4th dash app with shift behavior ++ ++ Keybinding to trigger 4th app with shift behavior. ++ ++ ++ ++ 5']]]> ++ Keybinding to trigger 5th dash app with shift behavior ++ ++ Keybinding to trigger 5th app with shift behavior. ++ ++ ++ ++ 6']]]> ++ Keybinding to trigger 6th dash app with shift behavior ++ ++ Keybinding to trigger 6th app with shift behavior. ++ ++ ++ ++ 7']]]> ++ Keybinding to trigger 7th dash app with shift behavior ++ ++ Keybinding to trigger 7th app with shift behavior. ++ ++ ++ ++ 8']]]> ++ Keybinding to trigger 8th dash app with shift behavior ++ ++ Keybinding to trigger 8th app with shift behavior. ++ ++ ++ ++ 9']]]> ++ Keybinding to trigger 9th dash app with shift behavior ++ ++ Keybinding to trigger 9th app with shift behavior. ++ ++ ++ ++ 0']]]> ++ Keybinding to trigger 10th dash app with shift behavior ++ ++ Keybinding to trigger 10th app with shift behavior. ++ ++ ++ ++ 1']]]> ++ Keybinding to trigger 1st dash app ++ ++ Keybinding to either show or launch the 1st application in the dash. ++ ++ ++ ++ 2']]]> ++ Keybinding to trigger 2nd dash app ++ ++ Keybinding to either show or launch the 2nd application in the dash. ++ ++ ++ ++ 3']]]> ++ Keybinding to trigger 3rd dash app ++ ++ Keybinding to either show or launch the 3rd application in the dash. ++ ++ ++ ++ 4']]]> ++ Keybinding to trigger 4th dash app ++ ++ Keybinding to either show or launch the 4th application in the dash. ++ ++ ++ ++ 5']]]> ++ Keybinding to trigger 5th dash app ++ ++ Keybinding to either show or launch the 5th application in the dash. ++ ++ ++ ++ 6']]]> ++ Keybinding to trigger 6th dash app ++ ++ Keybinding to either show or launch the 6th application in the dash. ++ ++ ++ ++ 7']]]> ++ Keybinding to trigger 7th dash app ++ ++ Keybinding to either show or launch the 7th application in the dash. ++ ++ ++ ++ 8']]]> ++ Keybinding to trigger 8th dash app ++ ++ Keybinding to either show or launch the 8th application in the dash. ++ ++ ++ ++ 9']]]> ++ Keybinding to trigger 9th dash app ++ ++ Keybinding to either show or launch the 9th application in the dash. ++ ++ ++ ++ 0']]]> ++ Keybinding to trigger 10th dash app ++ ++ Keybinding to either show or launch the 10th application in the dash. ++ ++ ++ ++ false ++ Force straight corners in dash ++ Make the borders in the dash non rounded ++ ++ ++ false ++ Enable unity7 like glossy backlit items ++ Emulate the unity7 backlit glossy items behaviour ++ ++ ++ +diff --git a/extensions/dash-to-dock/prefs.js b/extensions/dash-to-dock/prefs.js +new file mode 100644 +index 0000000..d8d8b94 +--- /dev/null ++++ b/extensions/dash-to-dock/prefs.js +@@ -0,0 +1,868 @@ ++// -*- mode: js; js-indent-level: 4; indent-tabs-mode: nil -*- ++ ++const Gio = imports.gi.Gio; ++const GLib = imports.gi.GLib; ++const GObject = imports.gi.GObject; ++const Gtk = imports.gi.Gtk; ++const Gdk = imports.gi.Gdk; ++const Lang = imports.lang; ++const Mainloop = imports.mainloop; ++ ++// Use __ () and N__() for the extension gettext domain, and reuse ++// the shell domain with the default _() and N_() ++const Gettext = imports.gettext.domain('dashtodock'); ++const __ = Gettext.gettext; ++const N__ = function(e) { return e }; ++ ++const ExtensionUtils = imports.misc.extensionUtils; ++const Me = ExtensionUtils.getCurrentExtension(); ++const Convenience = Me.imports.convenience; ++ ++const SCALE_UPDATE_TIMEOUT = 500; ++const DEFAULT_ICONS_SIZES = [ 128, 96, 64, 48, 32, 24, 16 ]; ++ ++const TransparencyMode = { ++ DEFAULT: 0, ++ FIXED: 1, ++ ADAPTIVE: 2, ++ DYNAMIC: 3 ++}; ++ ++const RunningIndicatorStyle = { ++ DEFAULT: 0, ++ DOTS: 1, ++ SQUARES: 2, ++ DASHES: 3, ++ SEGMENTED: 4, ++ SOLID: 5, ++ CILIORA: 6, ++ METRO: 7 ++}; ++ ++/** ++ * This function was copied from the activities-config extension ++ * https://github.com/nls1729/acme-code/tree/master/activities-config ++ * by Norman L. Smith. ++ */ ++function cssHexString(css) { ++ let rrggbb = '#'; ++ let start; ++ for (let loop = 0; loop < 3; loop++) { ++ let end = 0; ++ let xx = ''; ++ for (let loop = 0; loop < 2; loop++) { ++ while (true) { ++ let x = css.slice(end, end + 1); ++ if ((x == '(') || (x == ',') || (x == ')')) ++ break; ++ end++; ++ } ++ if (loop == 0) { ++ end++; ++ start = end; ++ } ++ } ++ xx = parseInt(css.slice(start, end)).toString(16); ++ if (xx.length == 1) ++ xx = '0' + xx; ++ rrggbb += xx; ++ css = css.slice(end); ++ } ++ return rrggbb; ++} ++ ++function setShortcut(settings) { ++ let shortcut_text = settings.get_string('shortcut-text'); ++ let [key, mods] = Gtk.accelerator_parse(shortcut_text); ++ ++ if (Gtk.accelerator_valid(key, mods)) { ++ let shortcut = Gtk.accelerator_name(key, mods); ++ settings.set_strv('shortcut', [shortcut]); ++ } ++ else { ++ settings.set_strv('shortcut', []); ++ } ++} ++ ++const Settings = new Lang.Class({ ++ Name: 'DashToDock.Settings', ++ ++ _init: function() { ++ this._settings = Convenience.getSettings('org.gnome.shell.extensions.dash-to-dock'); ++ ++ this._rtl = (Gtk.Widget.get_default_direction() == Gtk.TextDirection.RTL); ++ ++ this._builder = new Gtk.Builder(); ++ this._builder.set_translation_domain(Me.metadata['gettext-domain']); ++ this._builder.add_from_file(Me.path + '/Settings.ui'); ++ ++ this.widget = new Gtk.ScrolledWindow({ hscrollbar_policy: Gtk.PolicyType.NEVER }); ++ this._notebook = this._builder.get_object('settings_notebook'); ++ this.widget.add(this._notebook); ++ ++ // Set a reasonable initial window height ++ this.widget.connect('realize', Lang.bind(this, function() { ++ let window = this.widget.get_toplevel(); ++ let [default_width, default_height] = window.get_default_size(); ++ window.resize(default_width, 650); ++ })); ++ ++ // Timeout to delay the update of the settings ++ this._dock_size_timeout = 0; ++ this._icon_size_timeout = 0; ++ this._opacity_timeout = 0; ++ ++ this._bindSettings(); ++ ++ this._builder.connect_signals_full(Lang.bind(this, this._connector)); ++ }, ++ ++ /** ++ * Connect signals ++ */ ++ _connector: function(builder, object, signal, handler) { ++ object.connect(signal, Lang.bind(this, this._SignalHandler[handler])); ++ }, ++ ++ _bindSettings: function() { ++ // Position and size panel ++ ++ // Monitor options ++ ++ this._monitors = []; ++ // Build options based on the number of monitors and the current settings. ++ let n_monitors = Gdk.Screen.get_default().get_n_monitors(); ++ let primary_monitor = Gdk.Screen.get_default().get_primary_monitor(); ++ ++ let monitor = this._settings.get_int('preferred-monitor'); ++ ++ // Add primary monitor with index 0, because in GNOME Shell the primary monitor is always 0 ++ this._builder.get_object('dock_monitor_combo').append_text(__('Primary monitor')); ++ this._monitors.push(0); ++ ++ // Add connected monitors ++ let ctr = 0; ++ for (let i = 0; i < n_monitors; i++) { ++ if (i !== primary_monitor) { ++ ctr++; ++ this._monitors.push(ctr); ++ this._builder.get_object('dock_monitor_combo').append_text(__('Secondary monitor ') + ctr); ++ } ++ } ++ ++ // If one of the external monitor is set as preferred, show it even if not attached ++ if ((monitor >= n_monitors) && (monitor !== primary_monitor)) { ++ this._monitors.push(monitor) ++ this._builder.get_object('dock_monitor_combo').append_text(__('Secondary monitor ') + ++ctr); ++ } ++ ++ this._builder.get_object('dock_monitor_combo').set_active(this._monitors.indexOf(monitor)); ++ ++ // Position option ++ let position = this._settings.get_enum('dock-position'); ++ ++ switch (position) { ++ case 0: ++ this._builder.get_object('position_top_button').set_active(true); ++ break; ++ case 1: ++ this._builder.get_object('position_right_button').set_active(true); ++ break; ++ case 2: ++ this._builder.get_object('position_bottom_button').set_active(true); ++ break; ++ case 3: ++ this._builder.get_object('position_left_button').set_active(true); ++ break; ++ } ++ ++ if (this._rtl) { ++ /* Left is Right in rtl as a setting */ ++ this._builder.get_object('position_left_button').set_label(__('Right')); ++ this._builder.get_object('position_right_button').set_label(__('Left')); ++ } ++ ++ // Intelligent autohide options ++ this._settings.bind('dock-fixed', ++ this._builder.get_object('intelligent_autohide_switch'), ++ 'active', ++ Gio.SettingsBindFlags.INVERT_BOOLEAN); ++ this._settings.bind('dock-fixed', ++ this._builder.get_object('intelligent_autohide_button'), ++ 'sensitive', ++ Gio.SettingsBindFlags.INVERT_BOOLEAN); ++ this._settings.bind('autohide', ++ this._builder.get_object('autohide_switch'), ++ 'active', ++ Gio.SettingsBindFlags.DEFAULT); ++ this._settings.bind('autohide-in-fullscreen', ++ this._builder.get_object('autohide_enable_in_fullscreen_checkbutton'), ++ 'active', ++ Gio.SettingsBindFlags.DEFAULT); ++ this._settings.bind('require-pressure-to-show', ++ this._builder.get_object('require_pressure_checkbutton'), ++ 'active', ++ Gio.SettingsBindFlags.DEFAULT); ++ this._settings.bind('intellihide', ++ this._builder.get_object('intellihide_switch'), ++ 'active', ++ Gio.SettingsBindFlags.DEFAULT); ++ this._settings.bind('animation-time', ++ this._builder.get_object('animation_duration_spinbutton'), ++ 'value', ++ Gio.SettingsBindFlags.DEFAULT); ++ this._settings.bind('hide-delay', ++ this._builder.get_object('hide_timeout_spinbutton'), ++ 'value', ++ Gio.SettingsBindFlags.DEFAULT); ++ this._settings.bind('show-delay', ++ this._builder.get_object('show_timeout_spinbutton'), ++ 'value', ++ Gio.SettingsBindFlags.DEFAULT); ++ this._settings.bind('pressure-threshold', ++ this._builder.get_object('pressure_threshold_spinbutton'), ++ 'value', ++ Gio.SettingsBindFlags.DEFAULT); ++ ++ //this._builder.get_object('animation_duration_spinbutton').set_value(this._settings.get_double('animation-time')); ++ ++ // Create dialog for intelligent autohide advanced settings ++ this._builder.get_object('intelligent_autohide_button').connect('clicked', Lang.bind(this, function() { ++ ++ let dialog = new Gtk.Dialog({ title: __('Intelligent autohide customization'), ++ transient_for: this.widget.get_toplevel(), ++ use_header_bar: true, ++ modal: true }); ++ ++ // GTK+ leaves positive values for application-defined response ids. ++ // Use +1 for the reset action ++ dialog.add_button(__('Reset to defaults'), 1); ++ ++ let box = this._builder.get_object('intelligent_autohide_advanced_settings_box'); ++ dialog.get_content_area().add(box); ++ ++ this._settings.bind('intellihide', ++ this._builder.get_object('intellihide_mode_box'), ++ 'sensitive', ++ Gio.SettingsBindFlags.GET); ++ ++ // intellihide mode ++ ++ let intellihideModeRadioButtons = [ ++ this._builder.get_object('all_windows_radio_button'), ++ this._builder.get_object('focus_application_windows_radio_button'), ++ this._builder.get_object('maximized_windows_radio_button') ++ ]; ++ ++ intellihideModeRadioButtons[this._settings.get_enum('intellihide-mode')].set_active(true); ++ ++ this._settings.bind('autohide', ++ this._builder.get_object('require_pressure_checkbutton'), ++ 'sensitive', ++ Gio.SettingsBindFlags.GET); ++ ++ this._settings.bind('autohide', ++ this._builder.get_object('autohide_enable_in_fullscreen_checkbutton'), ++ 'sensitive', ++ Gio.SettingsBindFlags.GET); ++ ++ this._settings.bind('require-pressure-to-show', ++ this._builder.get_object('show_timeout_spinbutton'), ++ 'sensitive', ++ Gio.SettingsBindFlags.INVERT_BOOLEAN); ++ this._settings.bind('require-pressure-to-show', ++ this._builder.get_object('show_timeout_label'), ++ 'sensitive', ++ Gio.SettingsBindFlags.INVERT_BOOLEAN); ++ this._settings.bind('require-pressure-to-show', ++ this._builder.get_object('pressure_threshold_spinbutton'), ++ 'sensitive', ++ Gio.SettingsBindFlags.DEFAULT); ++ this._settings.bind('require-pressure-to-show', ++ this._builder.get_object('pressure_threshold_label'), ++ 'sensitive', ++ Gio.SettingsBindFlags.DEFAULT); ++ ++ dialog.connect('response', Lang.bind(this, function(dialog, id) { ++ if (id == 1) { ++ // restore default settings for the relevant keys ++ let keys = ['intellihide', 'autohide', 'intellihide-mode', 'autohide-in-fullscreen', 'require-pressure-to-show', ++ 'animation-time', 'show-delay', 'hide-delay', 'pressure-threshold']; ++ keys.forEach(function(val) { ++ this._settings.set_value(val, this._settings.get_default_value(val)); ++ }, this); ++ intellihideModeRadioButtons[this._settings.get_enum('intellihide-mode')].set_active(true); ++ } else { ++ // remove the settings box so it doesn't get destroyed; ++ dialog.get_content_area().remove(box); ++ dialog.destroy(); ++ } ++ return; ++ })); ++ ++ dialog.show_all(); ++ ++ })); ++ ++ // size options ++ this._builder.get_object('dock_size_scale').set_value(this._settings.get_double('height-fraction')); ++ this._builder.get_object('dock_size_scale').add_mark(0.9, Gtk.PositionType.TOP, null); ++ let icon_size_scale = this._builder.get_object('icon_size_scale'); ++ icon_size_scale.set_range(8, DEFAULT_ICONS_SIZES[0]); ++ icon_size_scale.set_value(this._settings.get_int('dash-max-icon-size')); ++ DEFAULT_ICONS_SIZES.forEach(function(val) { ++ icon_size_scale.add_mark(val, Gtk.PositionType.TOP, val.toString()); ++ }); ++ ++ // Corrent for rtl languages ++ if (this._rtl) { ++ // Flip value position: this is not done automatically ++ this._builder.get_object('dock_size_scale').set_value_pos(Gtk.PositionType.LEFT); ++ icon_size_scale.set_value_pos(Gtk.PositionType.LEFT); ++ // I suppose due to a bug, having a more than one mark and one above a value of 100 ++ // makes the rendering of the marks wrong in rtl. This doesn't happen setting the scale as not flippable ++ // and then manually inverting it ++ icon_size_scale.set_flippable(false); ++ icon_size_scale.set_inverted(true); ++ } ++ ++ this._settings.bind('icon-size-fixed', this._builder.get_object('icon_size_fixed_checkbutton'), 'active', Gio.SettingsBindFlags.DEFAULT); ++ this._settings.bind('extend-height', this._builder.get_object('dock_size_extend_checkbutton'), 'active', Gio.SettingsBindFlags.DEFAULT); ++ this._settings.bind('extend-height', this._builder.get_object('dock_size_scale'), 'sensitive', Gio.SettingsBindFlags.INVERT_BOOLEAN); ++ ++ ++ // Apps panel ++ ++ this._settings.bind('show-running', ++ this._builder.get_object('show_running_switch'), ++ 'active', ++ Gio.SettingsBindFlags.DEFAULT); ++ this._settings.bind('isolate-workspaces', ++ this._builder.get_object('application_button_isolation_button'), ++ 'active', ++ Gio.SettingsBindFlags.DEFAULT); ++ this._settings.bind('isolate-monitors', ++ this._builder.get_object('application_button_monitor_isolation_button'), ++ 'active', ++ Gio.SettingsBindFlags.DEFAULT); ++ this._settings.bind('show-windows-preview', ++ this._builder.get_object('windows_preview_button'), ++ 'active', ++ Gio.SettingsBindFlags.DEFAULT); ++ this._settings.bind('multi-monitor', ++ this._builder.get_object('multi_monitor_button'), ++ 'active', ++ Gio.SettingsBindFlags.DEFAULT); ++ this._settings.bind('show-favorites', ++ this._builder.get_object('show_favorite_switch'), ++ 'active', ++ Gio.SettingsBindFlags.DEFAULT); ++ this._settings.bind('show-show-apps-button', ++ this._builder.get_object('show_applications_button_switch'), ++ 'active', ++ Gio.SettingsBindFlags.DEFAULT); ++ this._settings.bind('show-apps-at-top', ++ this._builder.get_object('application_button_first_button'), ++ 'active', ++ Gio.SettingsBindFlags.DEFAULT); ++ this._settings.bind('show-show-apps-button', ++ this._builder.get_object('application_button_first_button'), ++ 'sensitive', ++ Gio.SettingsBindFlags.DEFAULT); ++ this._settings.bind('animate-show-apps', ++ this._builder.get_object('application_button_animation_button'), ++ 'active', ++ Gio.SettingsBindFlags.DEFAULT); ++ this._settings.bind('show-show-apps-button', ++ this._builder.get_object('application_button_animation_button'), ++ 'sensitive', ++ Gio.SettingsBindFlags.DEFAULT); ++ ++ ++ // Behavior panel ++ ++ this._settings.bind('hot-keys', ++ this._builder.get_object('hot_keys_switch'), ++ 'active', ++ Gio.SettingsBindFlags.DEFAULT); ++ this._settings.bind('hot-keys', ++ this._builder.get_object('overlay_button'), ++ 'sensitive', ++ Gio.SettingsBindFlags.DEFAULT); ++ ++ this._builder.get_object('click_action_combo').set_active(this._settings.get_enum('click-action')); ++ this._builder.get_object('click_action_combo').connect('changed', Lang.bind (this, function(widget) { ++ this._settings.set_enum('click-action', widget.get_active()); ++ })); ++ ++ this._builder.get_object('scroll_action_combo').set_active(this._settings.get_enum('scroll-action')); ++ this._builder.get_object('scroll_action_combo').connect('changed', Lang.bind (this, function(widget) { ++ this._settings.set_enum('scroll-action', widget.get_active()); ++ })); ++ ++ this._builder.get_object('shift_click_action_combo').connect('changed', Lang.bind (this, function(widget) { ++ this._settings.set_enum('shift-click-action', widget.get_active()); ++ })); ++ ++ this._builder.get_object('middle_click_action_combo').connect('changed', Lang.bind (this, function(widget) { ++ this._settings.set_enum('middle-click-action', widget.get_active()); ++ })); ++ this._builder.get_object('shift_middle_click_action_combo').connect('changed', Lang.bind (this, function(widget) { ++ this._settings.set_enum('shift-middle-click-action', widget.get_active()); ++ })); ++ ++ // Create dialog for number overlay options ++ this._builder.get_object('overlay_button').connect('clicked', Lang.bind(this, function() { ++ ++ let dialog = new Gtk.Dialog({ title: __('Show dock and application numbers'), ++ transient_for: this.widget.get_toplevel(), ++ use_header_bar: true, ++ modal: true }); ++ ++ // GTK+ leaves positive values for application-defined response ids. ++ // Use +1 for the reset action ++ dialog.add_button(__('Reset to defaults'), 1); ++ ++ let box = this._builder.get_object('box_overlay_shortcut'); ++ dialog.get_content_area().add(box); ++ ++ this._builder.get_object('overlay_switch').set_active(this._settings.get_boolean('hotkeys-overlay')); ++ this._builder.get_object('show_dock_switch').set_active(this._settings.get_boolean('hotkeys-show-dock')); ++ ++ // We need to update the shortcut 'strv' when the text is modified ++ this._settings.connect('changed::shortcut-text', Lang.bind(this, function() {setShortcut(this._settings);})); ++ this._settings.bind('shortcut-text', ++ this._builder.get_object('shortcut_entry'), ++ 'text', ++ Gio.SettingsBindFlags.DEFAULT); ++ ++ this._settings.bind('hotkeys-overlay', ++ this._builder.get_object('overlay_switch'), ++ 'active', ++ Gio.SettingsBindFlags.DEFAULT); ++ this._settings.bind('hotkeys-show-dock', ++ this._builder.get_object('show_dock_switch'), ++ 'active', ++ Gio.SettingsBindFlags.DEFAULT); ++ this._settings.bind('shortcut-timeout', ++ this._builder.get_object('timeout_spinbutton'), ++ 'value', ++ Gio.SettingsBindFlags.DEFAULT); ++ ++ dialog.connect('response', Lang.bind(this, function(dialog, id) { ++ if (id == 1) { ++ // restore default settings for the relevant keys ++ let keys = ['shortcut-text', 'hotkeys-overlay', 'hotkeys-show-dock', 'shortcut-timeout']; ++ keys.forEach(function(val) { ++ this._settings.set_value(val, this._settings.get_default_value(val)); ++ }, this); ++ } else { ++ // remove the settings box so it doesn't get destroyed; ++ dialog.get_content_area().remove(box); ++ dialog.destroy(); ++ } ++ return; ++ })); ++ ++ dialog.show_all(); ++ ++ })); ++ ++ // Create dialog for middle-click options ++ this._builder.get_object('middle_click_options_button').connect('clicked', Lang.bind(this, function() { ++ ++ let dialog = new Gtk.Dialog({ title: __('Customize middle-click behavior'), ++ transient_for: this.widget.get_toplevel(), ++ use_header_bar: true, ++ modal: true }); ++ ++ // GTK+ leaves positive values for application-defined response ids. ++ // Use +1 for the reset action ++ dialog.add_button(__('Reset to defaults'), 1); ++ ++ let box = this._builder.get_object('box_middle_click_options'); ++ dialog.get_content_area().add(box); ++ ++ this._builder.get_object('shift_click_action_combo').set_active(this._settings.get_enum('shift-click-action')); ++ ++ this._builder.get_object('middle_click_action_combo').set_active(this._settings.get_enum('middle-click-action')); ++ ++ this._builder.get_object('shift_middle_click_action_combo').set_active(this._settings.get_enum('shift-middle-click-action')); ++ ++ this._settings.bind('shift-click-action', ++ this._builder.get_object('shift_click_action_combo'), ++ 'active-id', ++ Gio.SettingsBindFlags.DEFAULT); ++ this._settings.bind('middle-click-action', ++ this._builder.get_object('middle_click_action_combo'), ++ 'active-id', ++ Gio.SettingsBindFlags.DEFAULT); ++ this._settings.bind('shift-middle-click-action', ++ this._builder.get_object('shift_middle_click_action_combo'), ++ 'active-id', ++ Gio.SettingsBindFlags.DEFAULT); ++ ++ dialog.connect('response', Lang.bind(this, function(dialog, id) { ++ if (id == 1) { ++ // restore default settings for the relevant keys ++ let keys = ['shift-click-action', 'middle-click-action', 'shift-middle-click-action']; ++ keys.forEach(function(val) { ++ this._settings.set_value(val, this._settings.get_default_value(val)); ++ }, this); ++ this._builder.get_object('shift_click_action_combo').set_active(this._settings.get_enum('shift-click-action')); ++ this._builder.get_object('middle_click_action_combo').set_active(this._settings.get_enum('middle-click-action')); ++ this._builder.get_object('shift_middle_click_action_combo').set_active(this._settings.get_enum('shift-middle-click-action')); ++ } else { ++ // remove the settings box so it doesn't get destroyed; ++ dialog.get_content_area().remove(box); ++ dialog.destroy(); ++ } ++ return; ++ })); ++ ++ dialog.show_all(); ++ ++ })); ++ ++ // Appearance Panel ++ ++ this._settings.bind('apply-custom-theme', this._builder.get_object('customize_theme'), 'sensitive', Gio.SettingsBindFlags.INVERT_BOOLEAN | Gio.SettingsBindFlags.GET); ++ this._settings.bind('apply-custom-theme', this._builder.get_object('builtin_theme_switch'), 'active', Gio.SettingsBindFlags.DEFAULT); ++ this._settings.bind('custom-theme-shrink', this._builder.get_object('shrink_dash_switch'), 'active', Gio.SettingsBindFlags.DEFAULT); ++ ++ // Running indicators ++ this._builder.get_object('running_indicators_combo').set_active( ++ this._settings.get_enum('running-indicator-style') ++ ); ++ this._builder.get_object('running_indicators_combo').connect( ++ 'changed', ++ Lang.bind (this, function(widget) { ++ this._settings.set_enum('running-indicator-style', widget.get_active()); ++ }) ++ ); ++ ++ if (this._settings.get_enum('running-indicator-style') == RunningIndicatorStyle.DEFAULT) ++ this._builder.get_object('running_indicators_advance_settings_button').set_sensitive(false); ++ ++ this._settings.connect('changed::running-indicator-style', Lang.bind(this, function() { ++ if (this._settings.get_enum('running-indicator-style') == RunningIndicatorStyle.DEFAULT) ++ this._builder.get_object('running_indicators_advance_settings_button').set_sensitive(false); ++ else ++ this._builder.get_object('running_indicators_advance_settings_button').set_sensitive(true); ++ })); ++ ++ // Create dialog for running indicators advanced settings ++ this._builder.get_object('running_indicators_advance_settings_button').connect('clicked', Lang.bind(this, function() { ++ ++ let dialog = new Gtk.Dialog({ title: __('Customize running indicators'), ++ transient_for: this.widget.get_toplevel(), ++ use_header_bar: true, ++ modal: true }); ++ ++ let box = this._builder.get_object('running_dots_advance_settings_box'); ++ dialog.get_content_area().add(box); ++ ++ this._settings.bind('running-indicator-dominant-color', ++ this._builder.get_object('dominant_color_switch'), ++ 'active', ++ Gio.SettingsBindFlags.DEFAULT); ++ ++ this._settings.bind('custom-theme-customize-running-dots', ++ this._builder.get_object('dot_style_switch'), ++ 'active', ++ Gio.SettingsBindFlags.DEFAULT); ++ this._settings.bind('custom-theme-customize-running-dots', ++ this._builder.get_object('dot_style_settings_box'), ++ 'sensitive', Gio.SettingsBindFlags.DEFAULT); ++ ++ let rgba = new Gdk.RGBA(); ++ rgba.parse(this._settings.get_string('custom-theme-running-dots-color')); ++ this._builder.get_object('dot_color_colorbutton').set_rgba(rgba); ++ ++ this._builder.get_object('dot_color_colorbutton').connect('notify::color', Lang.bind(this, function(button) { ++ let rgba = button.get_rgba(); ++ let css = rgba.to_string(); ++ let hexString = cssHexString(css); ++ this._settings.set_string('custom-theme-running-dots-color', hexString); ++ })); ++ ++ rgba.parse(this._settings.get_string('custom-theme-running-dots-border-color')); ++ this._builder.get_object('dot_border_color_colorbutton').set_rgba(rgba); ++ ++ this._builder.get_object('dot_border_color_colorbutton').connect('notify::color', Lang.bind(this, function(button) { ++ let rgba = button.get_rgba(); ++ let css = rgba.to_string(); ++ let hexString = cssHexString(css); ++ this._settings.set_string('custom-theme-running-dots-border-color', hexString); ++ })); ++ ++ this._settings.bind('custom-theme-running-dots-border-width', ++ this._builder.get_object('dot_border_width_spin_button'), ++ 'value', ++ Gio.SettingsBindFlags.DEFAULT); ++ ++ ++ dialog.connect('response', Lang.bind(this, function(dialog, id) { ++ // remove the settings box so it doesn't get destroyed; ++ dialog.get_content_area().remove(box); ++ dialog.destroy(); ++ return; ++ })); ++ ++ dialog.show_all(); ++ ++ })); ++ ++ this._settings.bind('custom-background-color', this._builder.get_object('custom_background_color_switch'), 'active', Gio.SettingsBindFlags.DEFAULT); ++ this._settings.bind('custom-background-color', this._builder.get_object('custom_background_color'), 'sensitive', Gio.SettingsBindFlags.DEFAULT); ++ ++ let rgba = new Gdk.RGBA(); ++ rgba.parse(this._settings.get_string('background-color')); ++ this._builder.get_object('custom_background_color').set_rgba(rgba); ++ ++ this._builder.get_object('custom_background_color').connect('notify::color', Lang.bind(this, function(button) { ++ let rgba = button.get_rgba(); ++ let css = rgba.to_string(); ++ let hexString = cssHexString(css); ++ this._settings.set_string('background-color', hexString); ++ })); ++ ++ // Opacity ++ this._builder.get_object('customize_opacity_combo').set_active( ++ this._settings.get_enum('transparency-mode') ++ ); ++ this._builder.get_object('customize_opacity_combo').connect( ++ 'changed', ++ Lang.bind (this, function(widget) { ++ this._settings.set_enum('transparency-mode', widget.get_active()); ++ }) ++ ); ++ ++ this._builder.get_object('custom_opacity_scale').set_value(this._settings.get_double('background-opacity')); ++ ++ if (this._settings.get_enum('transparency-mode') !== TransparencyMode.FIXED) ++ this._builder.get_object('custom_opacity_scale').set_sensitive(false); ++ ++ this._settings.connect('changed::transparency-mode', Lang.bind(this, function() { ++ if (this._settings.get_enum('transparency-mode') !== TransparencyMode.FIXED) ++ this._builder.get_object('custom_opacity_scale').set_sensitive(false); ++ else ++ this._builder.get_object('custom_opacity_scale').set_sensitive(true); ++ })); ++ ++ if (this._settings.get_enum('transparency-mode') !== TransparencyMode.ADAPTIVE && ++ this._settings.get_enum('transparency-mode') !== TransparencyMode.DYNAMIC) { ++ this._builder.get_object('dynamic_opacity_button').set_sensitive(false); ++ } ++ ++ this._settings.connect('changed::transparency-mode', Lang.bind(this, function() { ++ if (this._settings.get_enum('transparency-mode') !== TransparencyMode.ADAPTIVE && ++ this._settings.get_enum('transparency-mode') !== TransparencyMode.DYNAMIC) { ++ this._builder.get_object('dynamic_opacity_button').set_sensitive(false); ++ } ++ else { ++ this._builder.get_object('dynamic_opacity_button').set_sensitive(true); ++ } ++ })); ++ ++ // Create dialog for transparency advanced settings ++ this._builder.get_object('dynamic_opacity_button').connect('clicked', Lang.bind(this, function() { ++ ++ let dialog = new Gtk.Dialog({ title: __('Cutomize opacity'), ++ transient_for: this.widget.get_toplevel(), ++ use_header_bar: true, ++ modal: true }); ++ ++ let box = this._builder.get_object('advanced_transparency_dialog'); ++ dialog.get_content_area().add(box); ++ ++ this._settings.bind( ++ 'customize-alphas', ++ this._builder.get_object('customize_alphas_switch'), ++ 'active', ++ Gio.SettingsBindFlags.DEFAULT ++ ); ++ this._settings.bind( ++ 'customize-alphas', ++ this._builder.get_object('min_alpha_scale'), ++ 'sensitive', ++ Gio.SettingsBindFlags.DEFAULT ++ ); ++ this._settings.bind( ++ 'customize-alphas', ++ this._builder.get_object('max_alpha_scale'), ++ 'sensitive', ++ Gio.SettingsBindFlags.DEFAULT ++ ); ++ ++ this._builder.get_object('min_alpha_scale').set_value( ++ this._settings.get_double('min-alpha') ++ ); ++ this._builder.get_object('max_alpha_scale').set_value( ++ this._settings.get_double('max-alpha') ++ ); ++ ++ dialog.connect('response', Lang.bind(this, function(dialog, id) { ++ // remove the settings box so it doesn't get destroyed; ++ dialog.get_content_area().remove(box); ++ dialog.destroy(); ++ return; ++ })); ++ ++ dialog.show_all(); ++ })); ++ ++ ++ this._settings.bind('unity-backlit-items', ++ this._builder.get_object('unity_backlit_items_switch'), ++ 'active', Gio.SettingsBindFlags.DEFAULT ++ ); ++ ++ this._settings.bind('force-straight-corner', ++ this._builder.get_object('force_straight_corner_switch'), ++ 'active', Gio.SettingsBindFlags.DEFAULT); ++ ++ // About Panel ++ ++ this._builder.get_object('extension_version').set_label(Me.metadata.version.toString()); ++ }, ++ ++ /** ++ * Object containing all signals defined in the glade file ++ */ ++ _SignalHandler: { ++ dock_display_combo_changed_cb: function(combo) { ++ this._settings.set_int('preferred-monitor', this._monitors[combo.get_active()]); ++ }, ++ ++ position_top_button_toggled_cb: function(button) { ++ if (button.get_active()) ++ this._settings.set_enum('dock-position', 0); ++ }, ++ ++ position_right_button_toggled_cb: function(button) { ++ if (button.get_active()) ++ this._settings.set_enum('dock-position', 1); ++ }, ++ ++ position_bottom_button_toggled_cb: function(button) { ++ if (button.get_active()) ++ this._settings.set_enum('dock-position', 2); ++ }, ++ ++ position_left_button_toggled_cb: function(button) { ++ if (button.get_active()) ++ this._settings.set_enum('dock-position', 3); ++ }, ++ ++ icon_size_combo_changed_cb: function(combo) { ++ this._settings.set_int('dash-max-icon-size', this._allIconSizes[combo.get_active()]); ++ }, ++ ++ dock_size_scale_format_value_cb: function(scale, value) { ++ return Math.round(value*100)+ ' %'; ++ }, ++ ++ dock_size_scale_value_changed_cb: function(scale) { ++ // Avoid settings the size consinuosly ++ if (this._dock_size_timeout > 0) ++ Mainloop.source_remove(this._dock_size_timeout); ++ ++ this._dock_size_timeout = Mainloop.timeout_add(SCALE_UPDATE_TIMEOUT, Lang.bind(this, function() { ++ this._settings.set_double('height-fraction', scale.get_value()); ++ this._dock_size_timeout = 0; ++ return GLib.SOURCE_REMOVE; ++ })); ++ }, ++ ++ icon_size_scale_format_value_cb: function(scale, value) { ++ return value+ ' px'; ++ }, ++ ++ icon_size_scale_value_changed_cb: function(scale) { ++ // Avoid settings the size consinuosly ++ if (this._icon_size_timeout > 0) ++ Mainloop.source_remove(this._icon_size_timeout); ++ ++ this._icon_size_timeout = Mainloop.timeout_add(SCALE_UPDATE_TIMEOUT, Lang.bind(this, function() { ++ this._settings.set_int('dash-max-icon-size', scale.get_value()); ++ this._icon_size_timeout = 0; ++ return GLib.SOURCE_REMOVE; ++ })); ++ }, ++ ++ custom_opacity_scale_value_changed_cb: function(scale) { ++ // Avoid settings the opacity consinuosly as it's change is animated ++ if (this._opacity_timeout > 0) ++ Mainloop.source_remove(this._opacity_timeout); ++ ++ this._opacity_timeout = Mainloop.timeout_add(SCALE_UPDATE_TIMEOUT, Lang.bind(this, function() { ++ this._settings.set_double('background-opacity', scale.get_value()); ++ this._opacity_timeout = 0; ++ return GLib.SOURCE_REMOVE; ++ })); ++ }, ++ ++ min_opacity_scale_value_changed_cb: function(scale) { ++ // Avoid settings the opacity consinuosly as it's change is animated ++ if (this._opacity_timeout > 0) ++ Mainloop.source_remove(this._opacity_timeout); ++ ++ this._opacity_timeout = Mainloop.timeout_add(SCALE_UPDATE_TIMEOUT, Lang.bind(this, function() { ++ this._settings.set_double('min-alpha', scale.get_value()); ++ this._opacity_timeout = 0; ++ return GLib.SOURCE_REMOVE; ++ })); ++ }, ++ ++ max_opacity_scale_value_changed_cb: function(scale) { ++ // Avoid settings the opacity consinuosly as it's change is animated ++ if (this._opacity_timeout > 0) ++ Mainloop.source_remove(this._opacity_timeout); ++ ++ this._opacity_timeout = Mainloop.timeout_add(SCALE_UPDATE_TIMEOUT, Lang.bind(this, function() { ++ this._settings.set_double('max-alpha', scale.get_value()); ++ this._opacity_timeout = 0; ++ return GLib.SOURCE_REMOVE; ++ })); ++ }, ++ ++ custom_opacity_scale_format_value_cb: function(scale, value) { ++ return Math.round(value*100) + ' %'; ++ }, ++ ++ min_opacity_scale_format_value_cb: function(scale, value) { ++ return Math.round(value*100) + ' %'; ++ }, ++ ++ max_opacity_scale_format_value_cb: function(scale, value) { ++ return Math.round(value*100) + ' %'; ++ }, ++ ++ all_windows_radio_button_toggled_cb: function(button) { ++ if (button.get_active()) ++ this._settings.set_enum('intellihide-mode', 0); ++ }, ++ ++ focus_application_windows_radio_button_toggled_cb: function(button) { ++ if (button.get_active()) ++ this._settings.set_enum('intellihide-mode', 1); ++ }, ++ ++ maximized_windows_radio_button_toggled_cb: function(button) { ++ if (button.get_active()) ++ this._settings.set_enum('intellihide-mode', 2); ++ } ++ } ++}); ++ ++function init() { ++ Convenience.initTranslations(); ++} ++ ++function buildPrefsWidget() { ++ let settings = new Settings(); ++ let widget = settings.widget; ++ widget.show_all(); ++ return widget; ++} +diff --git a/extensions/dash-to-dock/stylesheet.css b/extensions/dash-to-dock/stylesheet.css +new file mode 100644 +index 0000000..6e9bf38 +--- /dev/null ++++ b/extensions/dash-to-dock/stylesheet.css +@@ -0,0 +1,109 @@ ++/* Shrink the dash by reducing padding and border radius */ ++#dashtodockContainer.shrink #dash, ++#dashtodockContainer.dashtodock #dash { ++ border:1px; ++ padding:0px; ++} ++ ++#dashtodockContainer.shrink.left #dash, ++#dashtodockContainer.dashtodock.left #dash { ++ border-left: 0px; ++ border-radius: 0px 9px 9px 0px; ++} ++ ++ ++#dashtodockContainer.shrink.right #dash, ++#dashtodockContainer.dashtodock.right #dash { ++ border-right: 0px; ++ border-radius: 9px 0px 0px 9px; ++} ++ ++ ++#dashtodockContainer.shrink.top #dash, ++#dashtodockContainer.dashtodock.top #dash { ++ border-top: 0px; ++ border-radius: 0px 0px 9px 9px; ++} ++ ++#dashtodockContainer.shrink.bottom #dash, ++#dashtodockContainer.dashtodock.bottom #dash { ++ border-bottom: 0px; ++ border-radius: 9px 9px 0px 0px; ++} ++ ++#dashtodockContainer.straight-corner #dash, ++#dashtodockContainer.shrink.straight-corner #dash { ++ border-radius: 0px; ++} ++ ++/* Scrollview style */ ++.bottom #dashtodockDashScrollview, ++.top #dashtodockDashScrollview { ++ -st-hfade-offset: 24px; ++} ++ ++.left #dashtodockDashScrollview, ++.right #dashtodockDashScrollview { ++ -st-vfade-offset: 24px; ++} ++ ++#dashtodockContainer.running-dots .dash-item-container > StButton, ++#dashtodockContainer.dashtodock .dash-item-container > StButton { ++ transition-duration: 250; ++ background-size: contain; ++} ++ ++#dashtodockContainer.shrink .dash-item-container > StButton, ++#dashtodockContainer.dashtodock .dash-item-container > StButton { ++ padding: 1px 2px; ++} ++ ++/* Dash height extended to the whole available vertical space */ ++#dashtodockContainer.extended.top #dash, ++#dashtodockContainer.extended.right #dash, ++#dashtodockContainer.extended.bottom #dash, ++#dashtodockContainer.extended.left #dash { ++ border-radius: 0; ++} ++ ++#dashtodockContainer.extended.top #dash, ++#dashtodockContainer.extended.bottom #dash { ++ border-left:0px; ++ border-right:0px; ++} ++ ++#dashtodockContainer.extended.right #dash, ++#dashtodockContainer.extended.left #dash { ++ border-top:0px; ++ border-bottom:0px; ++} ++ ++/* Running and focused application style */ ++ ++#dashtodockContainer.running-dots .app-well-app.running > .overview-icon, ++#dashtodockContainer.dashtodock .app-well-app.running > .overview-icon { ++ background-image:none; ++} ++ ++ ++#dashtodockContainer.running-dots .app-well-app.focused .overview-icon, ++#dashtodockContainer.dashtodock .app-well-app.focused .overview-icon { ++ background-color: rgba(238, 238, 236, 0.1); ++} ++ ++#dashtodockContainer.dashtodock #dash { ++ background: #2e3436; ++} ++ ++#dashtodockContainer .number-overlay { ++ color: rgba(255,255,255,1); ++ background-color: rgba(0,0,0,0.8); ++ text-align: center; ++} ++ ++#dashtodockPreviewSeparator.popup-separator-menu-item-horizontal { ++ width: 1px; ++ height: auto; ++ border-right-width: 1px; ++ margin: 32px 0px; ++} +diff --git a/extensions/dash-to-dock/theming.js b/extensions/dash-to-dock/theming.js +new file mode 100644 +index 0000000..4b18d1a +--- /dev/null ++++ b/extensions/dash-to-dock/theming.js +@@ -0,0 +1,672 @@ ++// -*- mode: js; js-indent-level: 4; indent-tabs-mode: nil -*- ++ ++const Clutter = imports.gi.Clutter; ++const Gio = imports.gi.Gio; ++const GLib = imports.gi.GLib; ++const Gtk = imports.gi.Gtk; ++const Signals = imports.signals; ++const Lang = imports.lang; ++const Meta = imports.gi.Meta; ++const Shell = imports.gi.Shell; ++const St = imports.gi.St; ++const Mainloop = imports.mainloop; ++ ++const AppDisplay = imports.ui.appDisplay; ++const AppFavorites = imports.ui.appFavorites; ++const Dash = imports.ui.dash; ++const DND = imports.ui.dnd; ++const IconGrid = imports.ui.iconGrid; ++const Main = imports.ui.main; ++const PopupMenu = imports.ui.popupMenu; ++const Tweener = imports.ui.tweener; ++const Util = imports.misc.util; ++const Workspace = imports.ui.workspace; ++ ++const Me = imports.misc.extensionUtils.getCurrentExtension(); ++const Dock = Me.imports.docking; ++const Utils = Me.imports.utils; ++ ++/* ++ * DEFAULT: transparency given by theme ++ * FIXED: constant transparency chosen by user ++ * ADAPTIVE: apply 'transparent' style to dock AND panel when ++ * no windows are close to the dock OR panel. ++ * When dock is hidden, the dock 'transparent' style only ++ * apply to itself. ++ * DYNAMIC: apply 'transparent' style when no windows are close to the dock ++ * */ ++const TransparencyMode = { ++ DEFAULT: 0, ++ FIXED: 1, ++ ADAPTIVE: 2, ++ DYNAMIC: 3 ++}; ++ ++/** ++ * Manage theme customization and custom theme support ++ */ ++var ThemeManager = new Lang.Class({ ++ Name: 'DashToDock.ThemeManager', ++ ++ _init: function(settings, dock) { ++ this._settings = settings; ++ this._signalsHandler = new Utils.GlobalSignalsHandler(); ++ this._bindSettingsChanges(); ++ this._actor = dock.actor; ++ this._dash = dock.dash; ++ ++ // initialize colors with generic values ++ this._customizedBackground = {red: 0, green: 0, blue: 0, alpha: 0}; ++ this._customizedBorder = {red: 0, green: 0, blue: 0, alpha: 0}; ++ this._transparency = new Transparency(this._settings, dock); ++ ++ this._signalsHandler.add([ ++ // When theme changes re-obtain default background color ++ St.ThemeContext.get_for_stage (global.stage), ++ 'changed', ++ Lang.bind(this, this.updateCustomTheme) ++ ], [ ++ // update :overview pseudoclass ++ Main.overview, ++ 'showing', ++ Lang.bind(this, this._onOverviewShowing) ++ ], [ ++ Main.overview, ++ 'hiding', ++ Lang.bind(this, this._onOverviewHiding) ++ ]); ++ ++ this._updateCustomStyleClasses(); ++ ++ // destroy themeManager when the managed actor is destroyed (e.g. extension unload) ++ // in order to disconnect signals ++ this._actor.connect('destroy', Lang.bind(this, this.destroy)); ++ ++ }, ++ ++ destroy: function() { ++ this._signalsHandler.destroy(); ++ this._transparency.destroy(); ++ }, ++ ++ _onOverviewShowing: function() { ++ this._actor.add_style_pseudo_class('overview'); ++ }, ++ ++ _onOverviewHiding: function() { ++ this._actor.remove_style_pseudo_class('overview'); ++ }, ++ ++ _updateDashOpacity: function() { ++ let newAlpha = this._settings.get_double('background-opacity'); ++ ++ let [backgroundColor, borderColor] = this._getDefaultColors(); ++ ++ if (backgroundColor==null) ++ return; ++ ++ // Get the background and border alphas. We check the background alpha ++ // for a minimum of .001 to prevent division by 0 errors ++ let backgroundAlpha = Math.max(Math.round(backgroundColor.alpha/2.55)/100, .001); ++ let borderAlpha = Math.round(borderColor.alpha/2.55)/100; ++ ++ // The border and background alphas should remain in sync ++ // We also limit the borderAlpha to a maximum of 1 (full opacity) ++ borderAlpha = Math.min((borderAlpha/backgroundAlpha)*newAlpha, 1); ++ ++ this._customizedBackground = 'rgba(' + ++ backgroundColor.red + ',' + ++ backgroundColor.green + ',' + ++ backgroundColor.blue + ',' + ++ newAlpha + ')'; ++ ++ this._customizedBorder = 'rgba(' + ++ borderColor.red + ',' + ++ borderColor.green + ',' + ++ borderColor.blue + ',' + ++ borderAlpha + ')'; ++ ++ }, ++ ++ _getDefaultColors: function() { ++ // Prevent shell crash if the actor is not on the stage. ++ // It happens enabling/disabling repeatedly the extension ++ if (!this._dash._container.get_stage()) ++ return [null, null]; ++ ++ // Remove custom style ++ let oldStyle = this._dash._container.get_style(); ++ this._dash._container.set_style(null); ++ ++ let themeNode = this._dash._container.get_theme_node(); ++ this._dash._container.set_style(oldStyle); ++ ++ let backgroundColor = themeNode.get_background_color(); ++ ++ // Just in case the theme has different border colors .. ++ // We want to find the inside border-color of the dock because it is ++ // the side most visible to the user. We do this by finding the side ++ // opposite the position ++ let position = Utils.getPosition(this._settings); ++ let side = position + 2; ++ if (side > 3) ++ side = Math.abs(side - 4); ++ ++ let borderColor = themeNode.get_border_color(side); ++ ++ return [backgroundColor, borderColor]; ++ }, ++ ++ _updateDashColor: function() { ++ // Retrieve the color. If needed we will adjust it before passing it to ++ // this._transparency. ++ let [backgroundColor, borderColor] = this._getDefaultColors(); ++ ++ if (backgroundColor==null) ++ return; ++ ++ if (this._settings.get_boolean('custom-background-color')) { ++ // When applying a custom color, we need to check the alpha value, ++ // if not the opacity will always be overridden by the color below. ++ // Note that if using 'adaptive' or 'dynamic' transparency modes, ++ // the opacity will be set by the opaque/transparent styles anyway. ++ let newAlpha = Math.round(backgroundColor.alpha/2.55)/100; ++ if (this._settings.get_enum('transparency-mode') == TransparencyMode.FIXED) ++ newAlpha = this._settings.get_double('background-opacity'); ++ ++ backgroundColor = Clutter.color_from_string(this._settings.get_string('background-color'))[1]; ++ this._customizedBackground = 'rgba(' + ++ backgroundColor.red + ',' + ++ backgroundColor.green + ',' + ++ backgroundColor.blue + ',' + ++ newAlpha + ')'; ++ ++ this._customizedBorder = this._customizedBackground; ++ } ++ this._transparency.setColor(backgroundColor); ++ }, ++ ++ _updateCustomStyleClasses: function() { ++ if (this._settings.get_boolean('apply-custom-theme')) ++ this._actor.add_style_class_name('dashtodock'); ++ else ++ this._actor.remove_style_class_name('dashtodock'); ++ ++ if (this._settings.get_boolean('custom-theme-shrink')) ++ this._actor.add_style_class_name('shrink'); ++ else ++ this._actor.remove_style_class_name('shrink'); ++ ++ if (this._settings.get_enum('running-indicator-style') !== 0) ++ this._actor.add_style_class_name('running-dots'); ++ else ++ this._actor.remove_style_class_name('running-dots'); ++ ++ // If not the built-in theme option is not selected ++ if (!this._settings.get_boolean('apply-custom-theme')) { ++ if (this._settings.get_boolean('force-straight-corner')) ++ this._actor.add_style_class_name('straight-corner'); ++ else ++ this._actor.remove_style_class_name('straight-corner'); ++ } else { ++ this._actor.remove_style_class_name('straight-corner'); ++ } ++ }, ++ ++ updateCustomTheme: function() { ++ this._updateCustomStyleClasses(); ++ this._updateDashOpacity(); ++ this._updateDashColor(); ++ this._adjustTheme(); ++ this._dash._redisplay(); ++ }, ++ ++ /** ++ * Reimported back and adapted from atomdock ++ */ ++ _adjustTheme: function() { ++ // Prevent shell crash if the actor is not on the stage. ++ // It happens enabling/disabling repeatedly the extension ++ if (!this._dash._container.get_stage()) ++ return; ++ ++ // Remove prior style edits ++ this._dash._container.set_style(null); ++ this._transparency.disable(); ++ ++ // If built-in theme is enabled do nothing else ++ if (this._settings.get_boolean('apply-custom-theme')) ++ return; ++ ++ let newStyle = ''; ++ let position = Utils.getPosition(this._settings); ++ ++ if (!this._settings.get_boolean('custom-theme-shrink')) { ++ // obtain theme border settings ++ let themeNode = this._dash._container.get_theme_node(); ++ let borderColor = themeNode.get_border_color(St.Side.TOP); ++ let borderWidth = themeNode.get_border_width(St.Side.TOP); ++ let borderRadius = themeNode.get_border_radius(St.Corner.TOPRIGHT); ++ ++ // We're copying border and corner styles to left border and top-left ++ // corner, also removing bottom border and bottom-right corner styles ++ let borderInner = ''; ++ let borderRadiusValue = ''; ++ let borderMissingStyle = ''; ++ ++ if (this._rtl && (position != St.Side.RIGHT)) ++ borderMissingStyle = 'border-right: ' + borderWidth + 'px solid ' + ++ borderColor.to_string() + ';'; ++ else if (!this._rtl && (position != St.Side.LEFT)) ++ borderMissingStyle = 'border-left: ' + borderWidth + 'px solid ' + ++ borderColor.to_string() + ';'; ++ ++ switch (position) { ++ case St.Side.LEFT: ++ borderInner = 'border-left'; ++ borderRadiusValue = '0 ' + borderRadius + 'px ' + borderRadius + 'px 0;'; ++ break; ++ case St.Side.RIGHT: ++ borderInner = 'border-right'; ++ borderRadiusValue = borderRadius + 'px 0 0 ' + borderRadius + 'px;'; ++ break; ++ case St.Side.TOP: ++ borderInner = 'border-top'; ++ borderRadiusValue = '0 0 ' + borderRadius + 'px ' + borderRadius + 'px;'; ++ break; ++ case St.Side.BOTTOM: ++ borderInner = 'border-bottom'; ++ borderRadiusValue = borderRadius + 'px ' + borderRadius + 'px 0 0;'; ++ break; ++ } ++ ++ newStyle = borderInner + ': none;' + ++ 'border-radius: ' + borderRadiusValue + ++ borderMissingStyle; ++ ++ // I do call set_style possibly twice so that only the background gets the transition. ++ // The transition-property css rules seems to be unsupported ++ this._dash._container.set_style(newStyle); ++ } ++ ++ // Customize background ++ let fixedTransparency = this._settings.get_enum('transparency-mode') == TransparencyMode.FIXED; ++ let defaultTransparency = this._settings.get_enum('transparency-mode') == TransparencyMode.DEFAULT; ++ if (!defaultTransparency && !fixedTransparency) { ++ this._transparency.enable(); ++ } ++ else if (!defaultTransparency || this._settings.get_boolean('custom-background-color')) { ++ newStyle = newStyle + 'background-color:'+ this._customizedBackground + '; ' + ++ 'border-color:'+ this._customizedBorder + '; ' + ++ 'transition-delay: 0s; transition-duration: 0.250s;'; ++ this._dash._container.set_style(newStyle); ++ } ++ }, ++ ++ _bindSettingsChanges: function() { ++ let keys = ['transparency-mode', ++ 'customize-alphas', ++ 'min-alpha', ++ 'max-alpha', ++ 'background-opacity', ++ 'custom-background-color', ++ 'background-color', ++ 'apply-custom-theme', ++ 'custom-theme-shrink', ++ 'custom-theme-running-dots', ++ 'extend-height', ++ 'force-straight-corner']; ++ ++ keys.forEach(function(key) { ++ this._signalsHandler.add([ ++ this._settings, ++ 'changed::' + key, ++ Lang.bind(this, this.updateCustomTheme) ++ ]); ++ }, this); ++ } ++}); ++ ++/** ++ * The following class is based on the following upstream commit: ++ * https://git.gnome.org/browse/gnome-shell/commit/?id=447bf55e45b00426ed908b1b1035f472c2466956 ++ * Transparency when free-floating ++ */ ++const Transparency = new Lang.Class({ ++ Name: 'DashToDock.Transparency', ++ ++ _init: function(settings, dock) { ++ this._settings = settings; ++ this._dash = dock.dash; ++ this._actor = this._dash._container; ++ this._dockActor = dock.actor; ++ this._dock = dock; ++ this._panel = Main.panel; ++ this._position = Utils.getPosition(this._settings); ++ ++ this._backgroundColor = '0,0,0'; ++ this._transparentAlpha = '0.2'; ++ this._opaqueAlpha = '1'; ++ this._transparentAlphaBorder = '0.1'; ++ this._opaqueAlphaBorder = '0.5'; ++ this._transparentTransition = '0ms'; ++ this._opaqueTransition = '0ms'; ++ ++ this._updateStyles(); ++ ++ this._signalsHandler = new Utils.GlobalSignalsHandler(); ++ this._injectionsHandler = new Utils.InjectionsHandler(); ++ this._trackedWindows = new Map(); ++ }, ++ ++ enable: function() { ++ // ensure I never double-register/inject ++ // although it should never happen ++ this.disable(); ++ ++ this._signalsHandler.addWithLabel('transparency', [ ++ global.window_group, ++ 'actor-added', ++ Lang.bind(this, this._onWindowActorAdded) ++ ], [ ++ global.window_group, ++ 'actor-removed', ++ Lang.bind(this, this._onWindowActorRemoved) ++ ], [ ++ global.window_manager, ++ 'switch-workspace', ++ Lang.bind(this, this._updateSolidStyle) ++ ], [ ++ Main.overview, ++ 'hiding', ++ Lang.bind(this, this._updateSolidStyle) ++ ], [ ++ Main.overview, ++ 'showing', ++ Lang.bind(this, this._updateSolidStyle) ++ ]); ++ ++ // Window signals ++ global.get_window_actors().forEach(function(win) { ++ // An irrelevant window actor ('Gnome-shell') produces an error when the signals are ++ // disconnected, therefore do not add signals to it. ++ if (win.get_meta_window().get_wm_class() !== 'Gnome-shell') ++ this._onWindowActorAdded(null, win); ++ }, this); ++ ++ if (this._settings.get_enum('transparency-mode') === TransparencyMode.ADAPTIVE) ++ this._enableAdaptive(); ++ ++ if (this._actor.get_stage()) ++ this._updateSolidStyle(); ++ ++ this.emit('transparency-enabled'); ++ }, ++ ++ disable: function() { ++ this._disableAdaptive(); ++ ++ // ensure I never double-register/inject ++ // although it should never happen ++ this._signalsHandler.removeWithLabel('transparency'); ++ ++ for (let key of this._trackedWindows.keys()) ++ this._trackedWindows.get(key).forEach(id => { ++ key.disconnect(id); ++ }); ++ this._trackedWindows.clear(); ++ ++ this.emit('transparency-disabled'); ++ }, ++ ++ destroy: function() { ++ this.disable(); ++ this._signalsHandler.destroy(); ++ this._injectionsHandler.destroy(); ++ }, ++ ++ _onWindowActorAdded: function(container, metaWindowActor) { ++ let signalIds = []; ++ ['allocation-changed', 'notify::visible'].forEach(s => { ++ signalIds.push(metaWindowActor.connect(s, Lang.bind(this, this._updateSolidStyle))); ++ }); ++ this._trackedWindows.set(metaWindowActor, signalIds); ++ }, ++ ++ _onWindowActorRemoved: function(container, metaWindowActor) { ++ if (!this._trackedWindows.get(metaWindowActor)) ++ return; ++ ++ this._trackedWindows.get(metaWindowActor).forEach(id => { ++ metaWindowActor.disconnect(id); ++ }); ++ this._trackedWindows.delete(metaWindowActor); ++ this._updateSolidStyle(); ++ }, ++ ++ _updateSolidStyle: function() { ++ let isNear = this._dockIsNear() || this._panelIsNear(); ++ if (isNear) { ++ this._actor.set_style(this._opaque_style); ++ if (this._panel._updateSolidStyle && this._adaptiveEnabled) { ++ if (this._settings.get_boolean('dock-fixed') || this._panelIsNear()) ++ this._panel._addStyleClassName('solid'); ++ else ++ this._panel._removeStyleClassName('solid'); ++ } ++ } ++ else { ++ this._actor.set_style(this._transparent_style); ++ if (this._panel._updateSolidStyle && this._adaptiveEnabled) ++ this._panel._removeStyleClassName('solid'); ++ } ++ ++ this.emit('solid-style-updated', isNear); ++ }, ++ ++ _dockIsNear: function() { ++ if (this._dockActor.has_style_pseudo_class('overview')) ++ return false; ++ /* Get all the windows in the active workspace that are in the primary monitor and visible */ ++ let activeWorkspace = global.screen.get_active_workspace(); ++ let dash = this._dash; ++ let windows = activeWorkspace.list_windows().filter(function(metaWindow) { ++ return metaWindow.get_monitor() === dash._monitorIndex && ++ metaWindow.showing_on_its_workspace() && ++ metaWindow.get_window_type() != Meta.WindowType.DESKTOP; ++ }); ++ ++ /* Check if at least one window is near enough to the panel. ++ * If the dock is hidden, we need to account for the space it would take ++ * up when it slides out. This is avoid an ugly transition. ++ * */ ++ let factor = 0; ++ if (!this._settings.get_boolean('dock-fixed') && ++ this._dock.getDockState() == Dock.State.HIDDEN) ++ factor = 1; ++ let [leftCoord, topCoord] = this._actor.get_transformed_position(); ++ let threshold; ++ if (this._position === St.Side.LEFT) ++ threshold = leftCoord + this._actor.get_width() * (factor + 1); ++ else if (this._position === St.Side.RIGHT) ++ threshold = leftCoord - this._actor.get_width() * factor; ++ else if (this._position === St.Side.TOP) ++ threshold = topCoord + this._actor.get_height() * (factor + 1); ++ else ++ threshold = topCoord - this._actor.get_height() * factor; ++ ++ let scale = St.ThemeContext.get_for_stage(global.stage).scale_factor; ++ let isNearEnough = windows.some(Lang.bind(this, function(metaWindow) { ++ let coord; ++ if (this._position === St.Side.LEFT) { ++ coord = metaWindow.get_frame_rect().x; ++ return coord < threshold + 5 * scale; ++ } ++ else if (this._position === St.Side.RIGHT) { ++ coord = metaWindow.get_frame_rect().x + metaWindow.get_frame_rect().width; ++ return coord > threshold - 5 * scale; ++ } ++ else if (this._position === St.Side.TOP) { ++ coord = metaWindow.get_frame_rect().y; ++ return coord < threshold + 5 * scale; ++ } ++ else { ++ coord = metaWindow.get_frame_rect().y + metaWindow.get_frame_rect().height; ++ return coord > threshold - 5 * scale; ++ } ++ })); ++ ++ return isNearEnough; ++ }, ++ ++ _panelIsNear: function() { ++ if (!this._panel._updateSolidStyle || ++ this._settings.get_enum('transparency-mode') !== TransparencyMode.ADAPTIVE) ++ return false; ++ ++ if (this._panel.actor.has_style_pseudo_class('overview') || !Main.sessionMode.hasWindows) { ++ this._panel._removeStyleClassName('solid'); ++ return false; ++ } ++ ++ /* Get all the windows in the active workspace that are in the ++ * primary monitor and visible */ ++ let activeWorkspace = global.screen.get_active_workspace(); ++ let windows = activeWorkspace.list_windows().filter(function(metaWindow) { ++ return metaWindow.is_on_primary_monitor() && ++ metaWindow.showing_on_its_workspace() && ++ metaWindow.get_window_type() != Meta.WindowType.DESKTOP; ++ }); ++ ++ /* Check if at least one window is near enough to the panel */ ++ let [, panelTop] = this._panel.actor.get_transformed_position(); ++ let panelBottom = panelTop + this._panel.actor.get_height(); ++ let scale = St.ThemeContext.get_for_stage(global.stage).scale_factor; ++ let isNearEnough = windows.some(Lang.bind(this._panel, function(metaWindow) { ++ let verticalPosition = metaWindow.get_frame_rect().y; ++ return verticalPosition < panelBottom + 5 * scale; ++ })); ++ ++ return isNearEnough; ++ }, ++ ++ _updateStyles: function() { ++ this._getAlphas(); ++ ++ this._transparent_style = ++ 'background-color: rgba(' + ++ this._backgroundColor + ', ' + this._transparentAlpha + ');' + ++ 'border-color: rgba(' + ++ this._backgroundColor + ', ' + this._transparentAlphaBorder + ');' + ++ 'transition-duration: ' + this._transparentTransition + 'ms;'; ++ ++ this._opaque_style = ++ 'background-color: rgba(' + ++ this._backgroundColor + ', ' + this._opaqueAlpha + ');' + ++ 'border-color: rgba(' + ++ this._backgroundColor + ',' + this._opaqueAlphaBorder + ');' + ++ 'transition-duration: ' + this._opaqueTransition + 'ms;'; ++ ++ this.emit('styles-updated'); ++ }, ++ ++ setColor: function(color) { ++ this._backgroundColor = color.red + ',' + color.green + ',' + color.blue; ++ this._updateStyles(); ++ }, ++ ++ _getAlphas: function() { ++ // Create dummy object and add to the uiGroup to get it to the stage ++ let dummyObject = new St.Bin({ ++ name: 'dashtodockContainer', ++ }); ++ Main.uiGroup.add_child(dummyObject); ++ ++ dummyObject.add_style_class_name('opaque'); ++ let themeNode = dummyObject.get_theme_node(); ++ this._opaqueAlpha = themeNode.get_background_color().alpha / 255; ++ this._opaqueAlphaBorder = themeNode.get_border_color(0).alpha / 255; ++ this._opaqueTransition = themeNode.get_transition_duration(); ++ ++ dummyObject.add_style_class_name('transparent'); ++ themeNode = dummyObject.get_theme_node(); ++ this._transparentAlpha = themeNode.get_background_color().alpha / 255; ++ this._transparentAlphaBorder = themeNode.get_border_color(0).alpha / 255; ++ this._transparentTransition = themeNode.get_transition_duration(); ++ ++ Main.uiGroup.remove_child(dummyObject); ++ ++ if (this._settings.get_boolean('customize-alphas')) { ++ this._opaqueAlpha = this._settings.get_double('max-alpha'); ++ this._opaqueAlphaBorder = this._opaqueAlpha / 2; ++ this._transparentAlpha = this._settings.get_double('min-alpha'); ++ this._transparentAlphaBorder = this._transparentAlpha / 2; ++ } ++ ++ if (this._settings.get_enum('transparency-mode') === TransparencyMode.ADAPTIVE && ++ this._panel._updateSolidStyle) { ++ themeNode = this._panel.actor.get_theme_node(); ++ if (this._panel.actor.has_style_class_name('solid')) { ++ this._opaqueTransition = themeNode.get_transition_duration(); ++ this._panel._removeStyleClassName('solid'); ++ themeNode = this._panel.actor.get_theme_node(); ++ this._transparentTransition = themeNode.get_transition_duration(); ++ this._panel._addStyleClassName('solid'); ++ } ++ else { ++ this._transparentTransition = themeNode.get_transition_duration(); ++ this._panel._addStyleClassName('solid'); ++ themeNode = this._panel.actor.get_theme_node(); ++ this._opaqueTransition = themeNode.get_transition_duration(); ++ this._panel._removeStyleClassName('solid'); ++ } ++ } ++ }, ++ ++ _enableAdaptive: function() { ++ if (!this._panel._updateSolidStyle || ++ this._dash._monitorIndex !== Main.layoutManager.primaryIndex) ++ return; ++ ++ this._adaptiveEnabled = true; ++ ++ function UpdateSolidStyle() { ++ return; ++ } ++ ++ this._injectionsHandler.addWithLabel('adaptive', [ ++ this._panel, ++ '_updateSolidStyle', ++ UpdateSolidStyle ++ ]); ++ ++ // Once we injected the new function, we need to disconnect and ++ // reconnect all window signals. ++ for (let key of this._panel._trackedWindows.keys()) ++ this._panel._trackedWindows.get(key).forEach(id => { ++ key.disconnect(id); ++ }); ++ ++ for (let win of this._panel._trackedWindows.keys()) ++ this._panel._onWindowActorAdded(null, win); ++ }, ++ ++ _disableAdaptive: function() { ++ if (!this._adaptiveEnabled) ++ return; ++ ++ this._injectionsHandler.removeWithLabel('adaptive'); ++ this._adaptiveEnabled = false; ++ ++ // Once we removed the injection, we need to disconnect and ++ // reconnect all window signals. ++ for (let key of this._panel._trackedWindows.keys()) ++ this._panel._trackedWindows.get(key).forEach(id => { ++ key.disconnect(id); ++ }); ++ ++ for (let win of this._panel._trackedWindows.keys()) ++ this._panel._onWindowActorAdded(null, win); ++ } ++}); ++Signals.addSignalMethods(Transparency.prototype); +diff --git a/extensions/dash-to-dock/utils.js b/extensions/dash-to-dock/utils.js +new file mode 100644 +index 0000000..6514649 +--- /dev/null ++++ b/extensions/dash-to-dock/utils.js +@@ -0,0 +1,255 @@ ++const Clutter = imports.gi.Clutter; ++const Lang = imports.lang; ++const St = imports.gi.St; ++ ++/** ++ * Simplify global signals and function injections handling ++ * abstract class ++ */ ++const BasicHandler = new Lang.Class({ ++ Name: 'DashToDock.BasicHandler', ++ ++ _init: function() { ++ this._storage = new Object(); ++ }, ++ ++ add: function(/* unlimited 3-long array arguments */) { ++ // Convert arguments object to array, concatenate with generic ++ let args = Array.concat('generic', Array.slice(arguments)); ++ // Call addWithLabel with ags as if they were passed arguments ++ this.addWithLabel.apply(this, args); ++ }, ++ ++ destroy: function() { ++ for( let label in this._storage ) ++ this.removeWithLabel(label); ++ }, ++ ++ addWithLabel: function(label /* plus unlimited 3-long array arguments*/) { ++ if (this._storage[label] == undefined) ++ this._storage[label] = new Array(); ++ ++ // Skip first element of the arguments ++ for (let i = 1; i < arguments.length; i++) { ++ let item = this._storage[label]; ++ item.push(this._create(arguments[i])); ++ } ++ }, ++ ++ removeWithLabel: function(label) { ++ if (this._storage[label]) { ++ for (let i = 0; i < this._storage[label].length; i++) ++ this._remove(this._storage[label][i]); ++ ++ delete this._storage[label]; ++ } ++ }, ++ ++ // Virtual methods to be implemented by subclass ++ ++ /** ++ * Create single element to be stored in the storage structure ++ */ ++ _create: function(item) { ++ throw new Error('no implementation of _create in ' + this); ++ }, ++ ++ /** ++ * Correctly delete single element ++ */ ++ _remove: function(item) { ++ throw new Error('no implementation of _remove in ' + this); ++ } ++}); ++ ++/** ++ * Manage global signals ++ */ ++var GlobalSignalsHandler = new Lang.Class({ ++ Name: 'DashToDock.GlobalSignalHandler', ++ Extends: BasicHandler, ++ ++ _create: function(item) { ++ let object = item[0]; ++ let event = item[1]; ++ let callback = item[2] ++ let id = object.connect(event, callback); ++ ++ return [object, id]; ++ }, ++ ++ _remove: function(item) { ++ item[0].disconnect(item[1]); ++ } ++}); ++ ++/** ++ * Color manipulation utilities ++ */ ++var ColorUtils = { ++ ++ // Darken or brigthen color by a fraction dlum ++ // Each rgb value is modified by the same fraction. ++ // Return "#rrggbb" string ++ ColorLuminance: function(r, g, b, dlum) { ++ let rgbString = '#'; ++ ++ rgbString += Math.round(Math.min(Math.max(r*(1+dlum), 0), 255)).toString(16); ++ rgbString += Math.round(Math.min(Math.max(g*(1+dlum), 0), 255)).toString(16); ++ rgbString += Math.round(Math.min(Math.max(b*(1+dlum), 0), 255)).toString(16); ++ ++ return rgbString; ++ }, ++ ++ // Convert hsv ([0-1, 0-1, 0-1]) to rgb ([0-255, 0-255, 0-255]). ++ // Following algorithm in https://en.wikipedia.org/wiki/HSL_and_HSV ++ // here with h = [0,1] instead of [0, 360] ++ // Accept either (h,s,v) independently or {h:h, s:s, v:v} object. ++ // Return {r:r, g:g, b:b} object. ++ HSVtoRGB: function(h, s, v) { ++ if (arguments.length === 1) { ++ s = h.s; ++ v = h.v; ++ h = h.h; ++ } ++ ++ let r,g,b; ++ let c = v*s; ++ let h1 = h*6; ++ let x = c*(1 - Math.abs(h1 % 2 - 1)); ++ let m = v - c; ++ ++ if (h1 <=1) ++ r = c + m, g = x + m, b = m; ++ else if (h1 <=2) ++ r = x + m, g = c + m, b = m; ++ else if (h1 <=3) ++ r = m, g = c + m, b = x + m; ++ else if (h1 <=4) ++ r = m, g = x + m, b = c + m; ++ else if (h1 <=5) ++ r = x + m, g = m, b = c + m; ++ else ++ r = c + m, g = m, b = x + m; ++ ++ return { ++ r: Math.round(r * 255), ++ g: Math.round(g * 255), ++ b: Math.round(b * 255) ++ }; ++ }, ++ ++ // Convert rgb ([0-255, 0-255, 0-255]) to hsv ([0-1, 0-1, 0-1]). ++ // Following algorithm in https://en.wikipedia.org/wiki/HSL_and_HSV ++ // here with h = [0,1] instead of [0, 360] ++ // Accept either (r,g,b) independently or {r:r, g:g, b:b} object. ++ // Return {h:h, s:s, v:v} object. ++ RGBtoHSV: function (r, g, b) { ++ if (arguments.length === 1) { ++ r = r.r; ++ g = r.g; ++ b = r.b; ++ } ++ ++ let h,s,v; ++ ++ let M = Math.max(r, g, b); ++ let m = Math.min(r, g, b); ++ let c = M - m; ++ ++ if (c == 0) ++ h = 0; ++ else if (M == r) ++ h = ((g-b)/c) % 6; ++ else if (M == g) ++ h = (b-r)/c + 2; ++ else ++ h = (r-g)/c + 4; ++ ++ h = h/6; ++ v = M/255; ++ if (M !== 0) ++ s = c/M; ++ else ++ s = 0; ++ ++ return { ++ h: h, ++ s: s, ++ v: v ++ }; ++ } ++}; ++ ++/** ++ * Manage function injection: both instances and prototype can be overridden ++ * and restored ++ */ ++var InjectionsHandler = new Lang.Class({ ++ Name: 'DashToDock.InjectionsHandler', ++ Extends: BasicHandler, ++ ++ _create: function(item) { ++ let object = item[0]; ++ let name = item[1]; ++ let injectedFunction = item[2]; ++ let original = object[name]; ++ ++ object[name] = injectedFunction; ++ return [object, name, injectedFunction, original]; ++ }, ++ ++ _remove: function(item) { ++ let object = item[0]; ++ let name = item[1]; ++ let original = item[3]; ++ object[name] = original; ++ } ++}); ++ ++/** ++ * Return the actual position reverseing left and right in rtl ++ */ ++function getPosition(settings) { ++ let position = settings.get_enum('dock-position'); ++ if (Clutter.get_default_text_direction() == Clutter.TextDirection.RTL) { ++ if (position == St.Side.LEFT) ++ position = St.Side.RIGHT; ++ else if (position == St.Side.RIGHT) ++ position = St.Side.LEFT; ++ } ++ return position; ++} ++ ++function drawRoundedLine(cr, x, y, width, height, isRoundLeft, isRoundRight, stroke, fill) { ++ if (height > width) { ++ y += Math.floor((height - width) / 2.0); ++ height = width; ++ } ++ ++ height = 2.0 * Math.floor(height / 2.0); ++ ++ var leftRadius = isRoundLeft ? height / 2.0 : 0.0; ++ var rightRadius = isRoundRight ? height / 2.0 : 0.0; ++ ++ cr.moveTo(x + width - rightRadius, y); ++ cr.lineTo(x + leftRadius, y); ++ if (isRoundLeft) ++ cr.arcNegative(x + leftRadius, y + leftRadius, leftRadius, -Math.PI/2, Math.PI/2); ++ else ++ cr.lineTo(x, y + height); ++ cr.lineTo(x + width - rightRadius, y + height); ++ if (isRoundRight) ++ cr.arcNegative(x + width - rightRadius, y + rightRadius, rightRadius, Math.PI/2, -Math.PI/2); ++ else ++ cr.lineTo(x + width, y); ++ cr.closePath(); ++ ++ if (fill != null) { ++ cr.setSource(fill); ++ cr.fillPreserve(); ++ } ++ if (stroke != null) ++ cr.setSource(stroke); ++ cr.stroke(); ++} +diff --git a/extensions/dash-to-dock/windowPreview.js b/extensions/dash-to-dock/windowPreview.js +new file mode 100644 +index 0000000..4b99aa8 +--- /dev/null ++++ b/extensions/dash-to-dock/windowPreview.js +@@ -0,0 +1,630 @@ ++/* ++ * Credits: ++ * This file is based on code from the Dash to Panel extension by Jason DeRose ++ * and code from the Taskbar extension by Zorin OS ++ * Some code was also adapted from the upstream Gnome Shell source code. ++ */ ++const Clutter = imports.gi.Clutter; ++const GLib = imports.gi.GLib; ++const Lang = imports.lang; ++const St = imports.gi.St; ++const Mainloop = imports.mainloop; ++const Main = imports.ui.main; ++const Gtk = imports.gi.Gtk; ++ ++const Params = imports.misc.params; ++const PopupMenu = imports.ui.popupMenu; ++const Tweener = imports.ui.tweener; ++const Workspace = imports.ui.workspace; ++ ++const Me = imports.misc.extensionUtils.getCurrentExtension(); ++const Utils = Me.imports.utils; ++ ++const PREVIEW_MAX_WIDTH = 250; ++const PREVIEW_MAX_HEIGHT = 150; ++ ++const WindowPreviewMenu = new Lang.Class({ ++ Name: 'WindowPreviewMenu', ++ Extends: PopupMenu.PopupMenu, ++ ++ _init: function(source, settings) { ++ this._dtdSettings = settings; ++ ++ let side = Utils.getPosition(settings); ++ ++ this.parent(source.actor, 0.5, side); ++ ++ // We want to keep the item hovered while the menu is up ++ this.blockSourceEvents = true; ++ ++ this._source = source; ++ this._app = this._source.app; ++ let monitorIndex = this._source.monitorIndex; ++ ++ this.actor.add_style_class_name('app-well-menu'); ++ this.actor.set_style('max-width: ' + (Main.layoutManager.monitors[monitorIndex].width - 22) + 'px; ' + ++ 'max-height: ' + (Main.layoutManager.monitors[monitorIndex].height - 22) + 'px;'); ++ this.actor.hide(); ++ ++ // Chain our visibility and lifecycle to that of the source ++ this._mappedId = this._source.actor.connect('notify::mapped', Lang.bind(this, function () { ++ if (!this._source.actor.mapped) ++ this.close(); ++ })); ++ this._destroyId = this._source.actor.connect('destroy', Lang.bind(this, this.destroy)); ++ ++ Main.uiGroup.add_actor(this.actor); ++ ++ // Change the initialized side where required. ++ this._arrowSide = side; ++ this._boxPointer._arrowSide = side; ++ this._boxPointer._userArrowSide = side; ++ ++ this._previewBox = new WindowPreviewList(this._source, this._dtdSettings); ++ this.addMenuItem(this._previewBox); ++ }, ++ ++ _redisplay: function() { ++ this._previewBox._shownInitially = false; ++ this._previewBox._redisplay(); ++ }, ++ ++ popup: function() { ++ let windows = this._source.getInterestingWindows(); ++ if (windows.length > 0) { ++ this._redisplay(); ++ this.open(); ++ this.actor.navigate_focus(null, Gtk.DirectionType.TAB_FORWARD, false); ++ this._source.emit('sync-tooltip'); ++ } ++ }, ++ ++ destroy: function () { ++ if (this._mappedId) ++ this._source.actor.disconnect(this._mappedId); ++ ++ if (this._destroyId) ++ this._source.actor.disconnect(this._destroyId); ++ ++ this.parent(); ++ } ++ ++}); ++ ++const WindowPreviewList = new Lang.Class({ ++ Name: 'WindowPreviewMenuSection', ++ Extends: PopupMenu.PopupMenuSection, ++ ++ _init: function(source, settings) { ++ this._dtdSettings = settings; ++ ++ this.parent(); ++ ++ this.actor = new St.ScrollView({ name: 'dashtodockWindowScrollview', ++ hscrollbar_policy: Gtk.PolicyType.NEVER, ++ vscrollbar_policy: Gtk.PolicyType.NEVER, ++ enable_mouse_scrolling: true }); ++ ++ this.actor.connect('scroll-event', Lang.bind(this, this._onScrollEvent )); ++ ++ let position = Utils.getPosition(this._dtdSettings); ++ this.isHorizontal = position == St.Side.BOTTOM || position == St.Side.TOP; ++ this.box.set_vertical(!this.isHorizontal); ++ this.box.set_name('dashtodockWindowList'); ++ this.actor.add_actor(this.box); ++ this.actor._delegate = this; ++ ++ this._shownInitially = false; ++ ++ this._source = source; ++ this.app = source.app; ++ ++ this._redisplayId = Main.initializeDeferredWork(this.actor, Lang.bind(this, this._redisplay)); ++ ++ this.actor.connect('destroy', Lang.bind(this, this._onDestroy)); ++ this._stateChangedId = this.app.connect('windows-changed', ++ Lang.bind(this, ++ this._queueRedisplay)); ++ }, ++ ++ _queueRedisplay: function () { ++ Main.queueDeferredWork(this._redisplayId); ++ }, ++ ++ _onScrollEvent: function(actor, event) { ++ // Event coordinates are relative to the stage but can be transformed ++ // as the actor will only receive events within his bounds. ++ let stage_x, stage_y, ok, event_x, event_y, actor_w, actor_h; ++ [stage_x, stage_y] = event.get_coords(); ++ [ok, event_x, event_y] = actor.transform_stage_point(stage_x, stage_y); ++ [actor_w, actor_h] = actor.get_size(); ++ ++ // If the scroll event is within a 1px margin from ++ // the relevant edge of the actor, let the event propagate. ++ if (event_y >= actor_h - 2) ++ return Clutter.EVENT_PROPAGATE; ++ ++ // Skip to avoid double events mouse ++ if (event.is_pointer_emulated()) ++ return Clutter.EVENT_STOP; ++ ++ let adjustment, delta; ++ ++ if (this.isHorizontal) ++ adjustment = this.actor.get_hscroll_bar().get_adjustment(); ++ else ++ adjustment = this.actor.get_vscroll_bar().get_adjustment(); ++ ++ let increment = adjustment.step_increment; ++ ++ switch ( event.get_scroll_direction() ) { ++ case Clutter.ScrollDirection.UP: ++ delta = -increment; ++ break; ++ case Clutter.ScrollDirection.DOWN: ++ delta = +increment; ++ break; ++ case Clutter.ScrollDirection.SMOOTH: ++ let [dx, dy] = event.get_scroll_delta(); ++ delta = dy*increment; ++ delta += dx*increment; ++ break; ++ ++ } ++ ++ adjustment.set_value(adjustment.get_value() + delta); ++ ++ return Clutter.EVENT_STOP; ++ }, ++ ++ _onDestroy: function() { ++ this.app.disconnect(this._stateChangedId); ++ this._stateChangedId = 0; ++ }, ++ ++ _createPreviewItem: function(window) { ++ let preview = new WindowPreviewMenuItem(window); ++ return preview; ++ }, ++ ++ _redisplay: function () { ++ // Remove separator ++ let nonWinItem = this._getMenuItems().filter(function(actor) { ++ return !actor._window; ++ }); ++ for (let i = 0; i < nonWinItem.length; i++) { ++ let item = nonWinItem[i]; ++ item.destroy(); ++ } ++ ++ let children = this._getMenuItems().filter(function(actor) { ++ return actor._window; ++ }); ++ ++ // Windows currently on the menu ++ let oldWin = children.map(function(actor) { ++ return actor._window; ++ }); ++ ++ // All app windows ++ let newWin = this._source.getInterestingWindows().sort(this.sortWindowsCompareFunction); ++ ++ let addedItems = []; ++ let removedActors = []; ++ ++ let newIndex = 0; ++ let oldIndex = 0; ++ ++ while (newIndex < newWin.length || oldIndex < oldWin.length) { ++ // No change at oldIndex/newIndex ++ if (oldWin[oldIndex] && ++ oldWin[oldIndex] == newWin[newIndex]) { ++ oldIndex++; ++ newIndex++; ++ continue; ++ } ++ ++ // Window removed at oldIndex ++ if (oldWin[oldIndex] && ++ newWin.indexOf(oldWin[oldIndex]) == -1) { ++ removedActors.push(children[oldIndex]); ++ oldIndex++; ++ continue; ++ } ++ ++ // Window added at newIndex ++ if (newWin[newIndex] && ++ oldWin.indexOf(newWin[newIndex]) == -1) { ++ addedItems.push({ item: this._createPreviewItem(newWin[newIndex]), ++ pos: newIndex }); ++ newIndex++; ++ continue; ++ } ++ ++ // Window moved ++ let insertHere = newWin[newIndex + 1] && ++ newWin[newIndex + 1] == oldWin[oldIndex]; ++ let alreadyRemoved = removedActors.reduce(function(result, actor) { ++ let removedWin = actor._window; ++ return result || removedWin == newWin[newIndex]; ++ }, false); ++ ++ if (insertHere || alreadyRemoved) { ++ addedItems.push({ item: this._createPreviewItem(newWin[newIndex]), ++ pos: newIndex + removedActors.length }); ++ newIndex++; ++ } else { ++ removedActors.push(children[oldIndex]); ++ oldIndex++; ++ } ++ } ++ ++ for (let i = 0; i < addedItems.length; i++) ++ this.addMenuItem(addedItems[i].item, ++ addedItems[i].pos); ++ ++ for (let i = 0; i < removedActors.length; i++) { ++ let item = removedActors[i]; ++ if (this._shownInitially) ++ item._animateOutAndDestroy(); ++ else ++ item.actor.destroy(); ++ } ++ ++ // Separate windows from other workspaces ++ let ws_index = global.screen.get_active_workspace_index(); ++ let separator_index = 0; ++ for (let i = 0; i < newWin.length; i++) ++ if (newWin[i].get_workspace().index() == ws_index) ++ separator_index++; ++ ++ if (separator_index > 0 && separator_index !== newWin.length) { ++ let separatorItem = new PopupMenu.PopupSeparatorMenuItem(); ++ if (this.isHorizontal) { ++ separatorItem._separator.set_x_expand(false); ++ separatorItem._separator.set_y_expand(true); ++ separatorItem._separator.set_name('dashtodockPreviewSeparator'); ++ separatorItem._separator.add_style_class_name('popup-separator-menu-item-horizontal'); ++ separatorItem._separator.set_x_align(Clutter.ActorAlign.CENTER); ++ separatorItem._separator.set_y_align(Clutter.ActorAlign.FILL); ++ } ++ this.addMenuItem(separatorItem, separator_index); ++ } ++ ++ // Skip animations on first run when adding the initial set ++ // of items, to avoid all items zooming in at once ++ let animate = this._shownInitially; ++ ++ if (!this._shownInitially) ++ this._shownInitially = true; ++ ++ for (let i = 0; i < addedItems.length; i++) ++ addedItems[i].item.show(animate); ++ ++ // Workaround for https://bugzilla.gnome.org/show_bug.cgi?id=692744 ++ // Without it, StBoxLayout may use a stale size cache ++ this.box.queue_relayout(); ++ ++ if (newWin.length < 1) ++ this._getTopMenu().close(~0); ++ ++ // As for upstream: ++ // St.ScrollView always requests space horizontally for a possible vertical ++ // scrollbar if in AUTOMATIC mode. Doing better would require implementation ++ // of width-for-height in St.BoxLayout and St.ScrollView. This looks bad ++ // when we *don't* need it, so turn off the scrollbar when that's true. ++ // Dynamic changes in whether we need it aren't handled properly. ++ let needsScrollbar = this._needsScrollbar(); ++ let scrollbar_policy = needsScrollbar ? Gtk.PolicyType.AUTOMATIC : Gtk.PolicyType.NEVER; ++ if (this.isHorizontal) ++ this.actor.hscrollbar_policy = scrollbar_policy; ++ else ++ this.actor.vscrollbar_policy = scrollbar_policy; ++ ++ if (needsScrollbar) ++ this.actor.add_style_pseudo_class('scrolled'); ++ else ++ this.actor.remove_style_pseudo_class('scrolled'); ++ }, ++ ++ _needsScrollbar: function() { ++ let topMenu = this._getTopMenu(); ++ let topThemeNode = topMenu.actor.get_theme_node(); ++ if (this.isHorizontal) { ++ let [topMinWidth, topNaturalWidth] = topMenu.actor.get_preferred_width(-1); ++ let topMaxWidth = topThemeNode.get_max_width(); ++ return topMaxWidth >= 0 && topNaturalWidth >= topMaxWidth; ++ } else { ++ let [topMinHeight, topNaturalHeight] = topMenu.actor.get_preferred_height(-1); ++ let topMaxHeight = topThemeNode.get_max_height(); ++ return topMaxHeight >= 0 && topNaturalHeight >= topMaxHeight; ++ } ++ ++ }, ++ ++ isAnimatingOut: function() { ++ return this.actor.get_children().reduce(function(result, actor) { ++ return result || actor.animatingOut; ++ }, false); ++ }, ++ ++ sortWindowsCompareFunction: function(windowA, windowB) { ++ let ws_index = global.screen.get_active_workspace_index(); ++ let winA_inActiveWS = windowA.get_workspace().index() == ws_index; ++ let winB_inActiveWS = windowB.get_workspace().index() == ws_index; ++ ++ // Only change the order if winA is not in the current WS, while winB is ++ if (!winA_inActiveWS && winB_inActiveWS) ++ return 1; ++ ++ return 0; ++ } ++}); ++ ++const WindowPreviewMenuItem = new Lang.Class({ ++ Name: 'WindowPreviewMenuItem', ++ Extends: PopupMenu.PopupBaseMenuItem, ++ ++ _init: function(window, params) { ++ this._window = window; ++ this._destroyId = 0; ++ this._windowAddedId = 0; ++ this.parent(params); ++ ++ // We don't want this: it adds spacing on the left of the item. ++ this.actor.remove_child(this._ornamentLabel); ++ this.actor.add_style_class_name('dashtodock-app-well-preview-menu-item'); ++ ++ this._cloneBin = new St.Bin(); ++ this._cloneBin.set_size(PREVIEW_MAX_WIDTH, PREVIEW_MAX_HEIGHT); ++ ++ // TODO: improve the way the closebutton is layout. Just use some padding ++ // for the moment. ++ this._cloneBin.set_style('padding-bottom: 0.5em'); ++ ++ this.closeButton = new St.Button({ style_class: 'window-close', ++ x_expand: true, ++ y_expand: true}); ++ this.closeButton.set_x_align(Clutter.ActorAlign.END); ++ this.closeButton.set_y_align(Clutter.ActorAlign.START); ++ ++ ++ this.closeButton.opacity = 0; ++ this.closeButton.connect('clicked', Lang.bind(this, this._closeWindow)); ++ ++ let overlayGroup = new Clutter.Actor({layout_manager: new Clutter.BinLayout() }); ++ ++ overlayGroup.add_actor(this._cloneBin); ++ overlayGroup.add_actor(this.closeButton); ++ ++ let label = new St.Label({ text: window.get_title()}); ++ label.set_style('max-width: '+PREVIEW_MAX_WIDTH +'px'); ++ let labelBin = new St.Bin({ child: label, ++ x_align: St.Align.MIDDLE}); ++ ++ this._windowTitleId = this._window.connect('notify::title', Lang.bind(this, function() { ++ label.set_text(this._window.get_title()); ++ })); ++ ++ let box = new St.BoxLayout({ vertical: true, ++ reactive:true, ++ x_expand:true }); ++ box.add(overlayGroup); ++ box.add(labelBin); ++ this.actor.add_actor(box); ++ ++ this.actor.connect('enter-event', ++ Lang.bind(this, this._onEnter)); ++ this.actor.connect('leave-event', ++ Lang.bind(this, this._onLeave)); ++ this.actor.connect('key-focus-in', ++ Lang.bind(this, this._onEnter)); ++ this.actor.connect('key-focus-out', ++ Lang.bind(this, this._onLeave)); ++ ++ this._cloneTexture(window); ++ ++ }, ++ ++ _cloneTexture: function(metaWin){ ++ ++ let mutterWindow = metaWin.get_compositor_private(); ++ ++ // Newly-created windows are added to a workspace before ++ // the compositor finds out about them... ++ // Moreover sometimes they return an empty texture, thus as a workarounf also check for it size ++ if (!mutterWindow || !mutterWindow.get_texture() || !mutterWindow.get_texture().get_size()[0]) { ++ let id = Mainloop.idle_add(Lang.bind(this, ++ function () { ++ // Check if there's still a point in getting the texture, ++ // otherwise this could go on indefinitely ++ if (this.actor && metaWin.get_workspace()) ++ this._cloneTexture(metaWin); ++ return GLib.SOURCE_REMOVE; ++ })); ++ GLib.Source.set_name_by_id(id, '[dash-to-dock] this._cloneTexture'); ++ return; ++ } ++ ++ let windowTexture = mutterWindow.get_texture(); ++ let [width, height] = windowTexture.get_size(); ++ ++ let scale = Math.min(1.0, PREVIEW_MAX_WIDTH/width, PREVIEW_MAX_HEIGHT/height); ++ ++ let clone = new Clutter.Clone ({ source: windowTexture, ++ reactive: true, ++ width: width * scale, ++ height: height * scale }); ++ ++ // when the source actor is destroyed, i.e. the window closed, first destroy the clone ++ // and then destroy the menu item (do this animating out) ++ this._destroyId = mutterWindow.connect('destroy', Lang.bind(this, function() { ++ clone.destroy(); ++ this._destroyId = 0; // avoid to try to disconnect this signal from mutterWindow in _onDestroy(), ++ // as the object was just destroyed ++ this._animateOutAndDestroy(); ++ })); ++ ++ this._clone = clone; ++ this._mutterWindow = mutterWindow; ++ this._cloneBin.set_child(this._clone); ++ }, ++ ++ _windowCanClose: function() { ++ return this._window.can_close() && ++ !this._hasAttachedDialogs(); ++ }, ++ ++ _closeWindow: function(actor) { ++ this._workspace = this._window.get_workspace(); ++ ++ // This mechanism is copied from the workspace.js upstream code ++ // It forces window activation if the windows don't get closed, ++ // for instance because asking user confirmation, by monitoring the opening of ++ // such additional confirmation window ++ this._windowAddedId = this._workspace.connect('window-added', ++ Lang.bind(this, ++ this._onWindowAdded)); ++ ++ this.deleteAllWindows(); ++ }, ++ ++ deleteAllWindows: function() { ++ // Delete all windows, starting from the bottom-most (most-modal) one ++ //let windows = this._window.get_compositor_private().get_children(); ++ let windows = this._clone.get_children(); ++ for (let i = windows.length - 1; i >= 1; i--) { ++ let realWindow = windows[i].source; ++ let metaWindow = realWindow.meta_window; ++ ++ metaWindow.delete(global.get_current_time()); ++ } ++ ++ this._window.delete(global.get_current_time()); ++ }, ++ ++ _onWindowAdded: function(workspace, win) { ++ let metaWindow = this._window; ++ ++ if (win.get_transient_for() == metaWindow) { ++ workspace.disconnect(this._windowAddedId); ++ this._windowAddedId = 0; ++ ++ // use an idle handler to avoid mapping problems - ++ // see comment in Workspace._windowAdded ++ let id = Mainloop.idle_add(Lang.bind(this, ++ function() { ++ this.emit('activate'); ++ return GLib.SOURCE_REMOVE; ++ })); ++ GLib.Source.set_name_by_id(id, '[dash-to-dock] this.emit'); ++ } ++ }, ++ ++ _hasAttachedDialogs: function() { ++ // count trasient windows ++ let n=0; ++ this._window.foreach_transient(function(){n++;}); ++ return n>0; ++ }, ++ ++ _onEnter: function() { ++ this._showCloseButton(); ++ return Clutter.EVENT_PROPAGATE; ++ }, ++ ++ _onLeave: function() { ++ if (!this._cloneBin.has_pointer && ++ !this.closeButton.has_pointer) ++ this._hideCloseButton(); ++ ++ return Clutter.EVENT_PROPAGATE; ++ }, ++ ++ _idleToggleCloseButton: function() { ++ this._idleToggleCloseId = 0; ++ ++ if (!this._cloneBin.has_pointer && ++ !this.closeButton.has_pointer) ++ this._hideCloseButton(); ++ ++ return GLib.SOURCE_REMOVE; ++ }, ++ ++ _showCloseButton: function() { ++ ++ if (this._windowCanClose()) { ++ this.closeButton.show(); ++ Tweener.addTween(this.closeButton, ++ { opacity: 255, ++ time: Workspace.CLOSE_BUTTON_FADE_TIME, ++ transition: 'easeOutQuad' }); ++ } ++ }, ++ ++ _hideCloseButton: function() { ++ Tweener.addTween(this.closeButton, ++ { opacity: 0, ++ time: Workspace.CLOSE_BUTTON_FADE_TIME, ++ transition: 'easeInQuad' }); ++ }, ++ ++ show: function(animate) { ++ let fullWidth = this.actor.get_width(); ++ ++ this.actor.opacity = 0; ++ this.actor.set_width(0); ++ ++ let time = animate ? 0.25 : 0; ++ Tweener.addTween(this.actor, ++ { opacity: 255, ++ width: fullWidth, ++ time: time, ++ transition: 'easeInOutQuad' ++ }); ++ }, ++ ++ _animateOutAndDestroy: function() { ++ Tweener.addTween(this.actor, ++ { opacity: 0, ++ time: 0.25, ++ }); ++ ++ Tweener.addTween(this.actor, ++ { height: 0, ++ width: 0, ++ time: 0.25, ++ delay: 0.25, ++ onCompleteScope: this, ++ onComplete: function() { ++ this.actor.destroy(); ++ } ++ }); ++ }, ++ ++ activate: function() { ++ this._getTopMenu().close(); ++ Main.activateWindow(this._window); ++ }, ++ ++ _onDestroy: function() { ++ ++ this.parent(); ++ ++ if (this._windowAddedId > 0) { ++ this._workspace.disconnect(this._windowAddedId); ++ this._windowAddedId = 0; ++ } ++ ++ if (this._destroyId > 0) { ++ this._mutterWindow.disconnect(this._destroyId); ++ this._destroyId = 0; ++ } ++ ++ if (this._windowTitleId > 0) { ++ this._window.disconnect(this._windowTitleId); ++ this._windowTitleId = 0; ++ } ++ } ++ ++}); +diff --git a/meson.build b/meson.build +index c16bde1..f9b56cf 100644 +--- a/meson.build ++++ b/meson.build +@@ -52,6 +52,7 @@ default_extensions += [ + all_extensions = default_extensions + all_extensions += [ + 'auto-move-windows', ++ 'dash-to-dock', + 'example', + 'native-window-placement', + 'top-icons', +-- +2.20.1 + + +From b60180a015533e1f56cde52b853c9384f5532f53 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/6] Add panel-favorites extension + +--- + extensions/panel-favorites/extension.js | 267 ++++++++++++++++++++ + extensions/panel-favorites/meson.build | 5 + + extensions/panel-favorites/metadata.json.in | 10 + + extensions/panel-favorites/stylesheet.css | 14 + + meson.build | 1 + + 5 files changed, 297 insertions(+) + create mode 100644 extensions/panel-favorites/extension.js + create mode 100644 extensions/panel-favorites/meson.build + create mode 100644 extensions/panel-favorites/metadata.json.in + create mode 100644 extensions/panel-favorites/stylesheet.css + +diff --git a/extensions/panel-favorites/extension.js b/extensions/panel-favorites/extension.js +new file mode 100644 +index 0000000..b817dbb +--- /dev/null ++++ b/extensions/panel-favorites/extension.js +@@ -0,0 +1,267 @@ ++// Copyright (C) 2011-2013 R M Yorston ++// Licence: GPLv2+ ++ ++const Clutter = imports.gi.Clutter; ++const Gio = imports.gi.Gio; ++const GLib = imports.gi.GLib; ++const Lang = imports.lang; ++const Shell = imports.gi.Shell; ++const Signals = imports.signals; ++const St = imports.gi.St; ++const Mainloop = imports.mainloop; ++ ++const AppFavorites = imports.ui.appFavorites; ++const Main = imports.ui.main; ++const Panel = imports.ui.panel; ++const Tweener = imports.ui.tweener; ++ ++const PANEL_LAUNCHER_LABEL_SHOW_TIME = 0.15; ++const PANEL_LAUNCHER_LABEL_HIDE_TIME = 0.1; ++const PANEL_LAUNCHER_HOVER_TIMEOUT = 300; ++ ++const PanelLauncher = new Lang.Class({ ++ Name: 'PanelLauncher', ++ ++ _init: function(app) { ++ this.actor = new St.Button({ style_class: 'panel-button', ++ reactive: true }); ++ this.iconSize = 24; ++ let icon = app.create_icon_texture(this.iconSize); ++ this.actor.set_child(icon); ++ this.actor._delegate = this; ++ let text = app.get_name(); ++ if ( app.get_description() ) { ++ text += '\n' + app.get_description(); ++ } ++ ++ this.label = new St.Label({ style_class: 'panel-launcher-label'}); ++ this.label.set_text(text); ++ Main.layoutManager.addChrome(this.label); ++ this.label.hide(); ++ this.actor.label_actor = this.label; ++ ++ this._app = app; ++ this.actor.connect('clicked', Lang.bind(this, function() { ++ this._app.open_new_window(-1); ++ })); ++ this.actor.connect('notify::hover', ++ Lang.bind(this, this._onHoverChanged)); ++ this.actor.opacity = 207; ++ ++ this.actor.connect('notify::allocation', Lang.bind(this, this._alloc)); ++ }, ++ ++ _onHoverChanged: function(actor) { ++ actor.opacity = actor.hover ? 255 : 207; ++ }, ++ ++ _alloc: function() { ++ let size = this.actor.allocation.y2 - this.actor.allocation.y1 - 3; ++ if ( size >= 24 && size != this.iconSize ) { ++ this.actor.get_child().destroy(); ++ this.iconSize = size; ++ let icon = this._app.create_icon_texture(this.iconSize); ++ this.actor.set_child(icon); ++ } ++ }, ++ ++ showLabel: function() { ++ this.label.opacity = 0; ++ this.label.show(); ++ ++ let [stageX, stageY] = this.actor.get_transformed_position(); ++ ++ let itemHeight = this.actor.allocation.y2 - this.actor.allocation.y1; ++ let itemWidth = this.actor.allocation.x2 - this.actor.allocation.x1; ++ let labelWidth = this.label.get_width(); ++ ++ let node = this.label.get_theme_node(); ++ let yOffset = node.get_length('-y-offset'); ++ ++ let y = stageY + itemHeight + yOffset; ++ let x = Math.floor(stageX + itemWidth/2 - labelWidth/2); ++ ++ let parent = this.label.get_parent(); ++ let parentWidth = parent.allocation.x2 - parent.allocation.x1; ++ ++ if ( Clutter.get_default_text_direction() == Clutter.TextDirection.LTR ) { ++ // stop long tooltips falling off the right of the screen ++ x = Math.min(x, parentWidth-labelWidth-6); ++ // but whatever happens don't let them fall of the left ++ x = Math.max(x, 6); ++ } ++ else { ++ x = Math.max(x, 6); ++ x = Math.min(x, parentWidth-labelWidth-6); ++ } ++ ++ this.label.set_position(x, y); ++ Tweener.addTween(this.label, ++ { opacity: 255, ++ time: PANEL_LAUNCHER_LABEL_SHOW_TIME, ++ transition: 'easeOutQuad', ++ }); ++ }, ++ ++ hideLabel: function() { ++ this.label.opacity = 255; ++ Tweener.addTween(this.label, ++ { opacity: 0, ++ time: PANEL_LAUNCHER_LABEL_HIDE_TIME, ++ transition: 'easeOutQuad', ++ onComplete: Lang.bind(this, function() { ++ this.label.hide(); ++ }) ++ }); ++ }, ++ ++ destroy: function() { ++ this.label.destroy(); ++ this.actor.destroy(); ++ } ++}); ++ ++const PanelFavorites = new Lang.Class({ ++ Name: 'PanelFavorites', ++ ++ _init: function() { ++ this._showLabelTimeoutId = 0; ++ this._resetHoverTimeoutId = 0; ++ this._labelShowing = false; ++ ++ this.actor = new St.BoxLayout({ name: 'panelFavorites', ++ x_expand: true, y_expand: true, ++ style_class: 'panel-favorites' }); ++ this._display(); ++ ++ this.container = new St.Bin({ y_fill: true, ++ x_fill: true, ++ child: this.actor }); ++ ++ this.actor.connect('destroy', Lang.bind(this, this._onDestroy)); ++ this._installChangedId = Shell.AppSystem.get_default().connect('installed-changed', Lang.bind(this, this._redisplay)); ++ this._changedId = AppFavorites.getAppFavorites().connect('changed', Lang.bind(this, this._redisplay)); ++ }, ++ ++ _redisplay: function() { ++ for ( let i=0; i 0) { ++ Mainloop.source_remove(this._resetHoverTimeoutId); ++ this._resetHoverTimeoutId = 0; ++ } ++ } ++ } else { ++ if (this._showLabelTimeoutId > 0) { ++ Mainloop.source_remove(this._showLabelTimeoutId); ++ this._showLabelTimeoutId = 0; ++ } ++ launcher.hideLabel(); ++ if (this._labelShowing) { ++ this._resetHoverTimeoutId = Mainloop.timeout_add( ++ PANEL_LAUNCHER_HOVER_TIMEOUT, ++ Lang.bind(this, function() { ++ this._labelShowing = false; ++ this._resetHoverTimeoutId = 0; ++ return GLib.SOURCE_REMOVE; ++ })); ++ } ++ } ++ }, ++ ++ _onDestroy: function() { ++ if ( this._installChangedId != 0 ) { ++ Shell.AppSystem.get_default().disconnect(this._installChangedId); ++ this._installChangedId = 0; ++ } ++ ++ if ( this._changedId != 0 ) { ++ AppFavorites.getAppFavorites().disconnect(this._changedId); ++ this._changedId = 0; ++ } ++ } ++}); ++Signals.addSignalMethods(PanelFavorites.prototype); ++ ++let myAddToStatusArea; ++let panelFavorites; ++ ++function enable() { ++ Panel.Panel.prototype.myAddToStatusArea = myAddToStatusArea; ++ ++ // place panel to left of app menu, or failing that at right end of box ++ let siblings = Main.panel._leftBox.get_children(); ++ let appMenu = Main.panel.statusArea['appMenu']; ++ let pos = appMenu ? siblings.indexOf(appMenu.container) : siblings.length; ++ ++ panelFavorites = new PanelFavorites(); ++ Main.panel.myAddToStatusArea('panel-favorites', panelFavorites, ++ pos, 'left'); ++} ++ ++function disable() { ++ delete Panel.Panel.prototype.myAddToStatusArea; ++ ++ panelFavorites.actor.destroy(); ++ panelFavorites.emit('destroy'); ++ panelFavorites = null; ++} ++ ++function init() { ++ myAddToStatusArea = function(role, indicator, position, box) { ++ if (this.statusArea[role]) ++ throw new Error('Extension point conflict: there is already a status indicator for role ' + role); ++ ++ position = position || 0; ++ let boxes = { ++ left: this._leftBox, ++ center: this._centerBox, ++ right: this._rightBox ++ }; ++ let boxContainer = boxes[box] || this._rightBox; ++ this.statusArea[role] = indicator; ++ this._addToPanelBox(role, indicator, position, boxContainer); ++ return indicator; ++ }; ++} +diff --git a/extensions/panel-favorites/meson.build b/extensions/panel-favorites/meson.build +new file mode 100644 +index 0000000..48504f6 +--- /dev/null ++++ b/extensions/panel-favorites/meson.build +@@ -0,0 +1,5 @@ ++extension_data += configure_file( ++ input: metadata_name + '.in', ++ output: metadata_name, ++ configuration: metadata_conf ++) +diff --git a/extensions/panel-favorites/metadata.json.in b/extensions/panel-favorites/metadata.json.in +new file mode 100644 +index 0000000..037f281 +--- /dev/null ++++ b/extensions/panel-favorites/metadata.json.in +@@ -0,0 +1,10 @@ ++{ ++"extension-id": "@extension_id@", ++"uuid": "@uuid@", ++"settings-schema": "@gschemaname@", ++"gettext-domain": "@gettext_domain@", ++"name": "Frippery Panel Favorites", ++"description": "Add launchers for Favorites to the panel", ++"shell-version": [ "@shell_current@" ], ++"url": "http://intgat.tigress.co.uk/rmy/extensions/index.html" ++} +diff --git a/extensions/panel-favorites/stylesheet.css b/extensions/panel-favorites/stylesheet.css +new file mode 100644 +index 0000000..120adac +--- /dev/null ++++ b/extensions/panel-favorites/stylesheet.css +@@ -0,0 +1,14 @@ ++.panel-favorites { ++ spacing: 6px; ++} ++ ++.panel-launcher-label { ++ border-radius: 7px; ++ padding: 4px 12px; ++ background-color: rgba(0,0,0,0.9); ++ color: white; ++ text-align: center; ++ font-size: 9pt; ++ font-weight: bold; ++ -y-offset: 6px; ++} +diff --git a/meson.build b/meson.build +index f9b56cf..3451585 100644 +--- a/meson.build ++++ b/meson.build +@@ -55,6 +55,7 @@ all_extensions += [ + 'dash-to-dock', + 'example', + 'native-window-placement', ++ 'panel-favorites', + 'top-icons', + 'user-theme' + ] +-- +2.20.1 + + +From 8f79f11a30d3e1bcbb441b50ac1ef0ea3ed2dc47 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/6] Add updates-dialog extension + +--- + extensions/updates-dialog/extension.js | 490 ++++++++++++++++++ + extensions/updates-dialog/meson.build | 7 + + extensions/updates-dialog/metadata.json.in | 10 + + ...hell.extensions.updates-dialog.gschema.xml | 30 ++ + extensions/updates-dialog/stylesheet.css | 1 + + meson.build | 1 + + po/POTFILES.in | 2 + + 7 files changed, 541 insertions(+) + create mode 100644 extensions/updates-dialog/extension.js + create mode 100644 extensions/updates-dialog/meson.build + create mode 100644 extensions/updates-dialog/metadata.json.in + create mode 100644 extensions/updates-dialog/org.gnome.shell.extensions.updates-dialog.gschema.xml + create mode 100644 extensions/updates-dialog/stylesheet.css + +diff --git a/extensions/updates-dialog/extension.js b/extensions/updates-dialog/extension.js +new file mode 100644 +index 0000000..2fa62a5 +--- /dev/null ++++ b/extensions/updates-dialog/extension.js +@@ -0,0 +1,490 @@ ++/* ++ * Copyright (c) 2015 Red Hat, Inc. ++ * ++ * This program is free software; you can redistribute it and/or ++ * modify it under the terms of the GNU General Public License as ++ * published by the Free Software Foundation; either version 2 of the ++ * License, or (at your option) any later version. ++ * ++ * This program is distributed in the hope that it will be useful, but ++ * WITHOUT ANY WARRANTY; without even the implied warranty of ++ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU ++ * General Public License for more details. ++ * ++ * You should have received a copy of the GNU General Public License ++ * along with this program; if not, see . ++ */ ++ ++const Clutter = imports.gi.Clutter; ++const Gio = imports.gi.Gio; ++const GLib = imports.gi.GLib; ++const Lang = imports.lang; ++const Pango = imports.gi.Pango; ++const PkgKit = imports.gi.PackageKitGlib; ++const Polkit = imports.gi.Polkit; ++const Signals = imports.signals; ++const St = imports.gi.St; ++ ++const EndSessionDialog = imports.ui.endSessionDialog; ++const Main = imports.ui.main; ++const ModalDialog = imports.ui.modalDialog; ++ ++const ExtensionUtils = imports.misc.extensionUtils; ++const Me = ExtensionUtils.getCurrentExtension(); ++const Convenience = Me.imports.convenience; ++ ++const PkIface = ' \ ++ \ ++ \ ++ \ ++ \ ++ \ ++ \ ++'; ++ ++const PkOfflineIface = ' \ ++ \ ++ \ ++ \ ++ \ ++ \ ++ \ ++ \ ++ \ ++'; ++ ++const PkTransactionIface = ' \ ++ \ ++ \ ++ \ ++ \ ++ \ ++ \ ++ \ ++ \ ++ \ ++ \ ++ \ ++ \ ++ \ ++ \ ++ \ ++ \ ++ \ ++ \ ++ \ ++ \ ++ \ ++'; ++ ++const LoginManagerIface = ' \ ++ \ ++ \ ++ \ ++ \ ++ \ ++ \ ++ \ ++ \ ++'; ++ ++const PkProxy = Gio.DBusProxy.makeProxyWrapper(PkIface); ++const PkOfflineProxy = Gio.DBusProxy.makeProxyWrapper(PkOfflineIface); ++const PkTransactionProxy = Gio.DBusProxy.makeProxyWrapper(PkTransactionIface); ++const LoginManagerProxy = Gio.DBusProxy.makeProxyWrapper(LoginManagerIface); ++ ++let pkProxy = null; ++let pkOfflineProxy = null; ++let loginManagerProxy = null; ++let updatesDialog = null; ++let extensionSettings = null; ++let cancellable = null; ++ ++let updatesCheckInProgress = false; ++let updatesCheckRequested = false; ++let securityUpdates = []; ++ ++function getDetailText(period) { ++ let text = _("Important security updates need to be installed.\n"); ++ if (period < 60) ++ text += ngettext("You can close this dialog and get %d minute to finish your work.", ++ "You can close this dialog and get %d minutes to finish your work.", ++ period).format(period); ++ else ++ text += ngettext("You can close this dialog and get %d hour to finish your work.", ++ "You can close this dialog and get %d hours to finish your work.", ++ Math.floor(period / 60)).format(Math.floor(period / 60)); ++ return text; ++} ++ ++const UpdatesDialog = new Lang.Class({ ++ Name: 'UpdatesDialog', ++ Extends: ModalDialog.ModalDialog, ++ ++ _init: function(settings) { ++ this.parent({ styleClass: 'end-session-dialog', ++ destroyOnClose: false }); ++ ++ this._gracePeriod = settings.get_uint('grace-period'); ++ this._gracePeriod = Math.min(Math.max(10, this._gracePeriod), 24*60); ++ this._lastWarningPeriod = settings.get_uint('last-warning-period'); ++ this._lastWarningPeriod = Math.min(Math.max(1, this._lastWarningPeriod), this._gracePeriod - 1); ++ this._lastWarnings = settings.get_uint('last-warnings'); ++ this._lastWarnings = Math.min(Math.max(1, this._lastWarnings), ++ Math.floor((this._gracePeriod - 1) / this._lastWarningPeriod)); ++ ++ let messageLayout = new St.BoxLayout({ vertical: true, ++ style_class: 'end-session-dialog-layout' }); ++ this.contentLayout.add(messageLayout, ++ { x_fill: true, ++ y_fill: true, ++ y_expand: true }); ++ ++ let subjectLabel = new St.Label({ style_class: 'end-session-dialog-subject', ++ style: 'padding-bottom: 1em;', ++ text: _("Important security updates") }); ++ messageLayout.add(subjectLabel, ++ { x_fill: false, ++ y_fill: false, ++ x_align: St.Align.START, ++ y_align: St.Align.START }); ++ ++ this._detailLabel = new St.Label({ style_class: 'end-session-dialog-description', ++ style: 'padding-bottom: 0em;', ++ text: getDetailText(this._gracePeriod) }); ++ this._detailLabel.clutter_text.ellipsize = Pango.EllipsizeMode.NONE; ++ this._detailLabel.clutter_text.line_wrap = true; ++ ++ messageLayout.add(this._detailLabel, ++ { y_fill: true, ++ y_align: St.Align.START }); ++ ++ let buttons = [{ action: Lang.bind(this, this.close), ++ label: _("Close"), ++ key: Clutter.Escape }, ++ { action: Lang.bind(this, this._done), ++ label: _("Restart & Install") }]; ++ ++ this.setButtons(buttons); ++ ++ this._openTimeoutId = 0; ++ this.connect('destroy', Lang.bind(this, this._clearOpenTimeout)); ++ ++ this._startTimer(); ++ }, ++ ++ _clearOpenTimeout: function() { ++ if (this._openTimeoutId > 0) { ++ GLib.source_remove(this._openTimeoutId); ++ this._openTimeoutId = 0; ++ } ++ }, ++ ++ tryOpen: function() { ++ if (this._openTimeoutId > 0 || this.open()) ++ return; ++ ++ this._openTimeoutId = GLib.timeout_add_seconds(GLib.PRIORITY_DEFAULT, 1, ++ Lang.bind(this, function() { ++ if (!this.open()) ++ return GLib.SOURCE_CONTINUE; ++ ++ this._clearOpenTimeout(); ++ return GLib.SOURCE_REMOVE; ++ })); ++ }, ++ ++ _startTimer: function() { ++ this._secondsLeft = this._gracePeriod*60; ++ ++ this._timerId = GLib.timeout_add_seconds(GLib.PRIORITY_DEFAULT, 1, Lang.bind(this, ++ function() { ++ this._secondsLeft -= 1; ++ let minutesLeft = this._secondsLeft / 60; ++ let periodLeft = Math.floor(minutesLeft); ++ ++ if (this._secondsLeft == 60 || ++ (periodLeft > 0 && periodLeft <= this._lastWarningPeriod * this._lastWarnings && ++ minutesLeft % this._lastWarningPeriod == 0)) { ++ this.tryOpen(); ++ this._detailLabel.text = getDetailText(periodLeft); ++ } ++ ++ if (this._secondsLeft > 0) { ++ if (this._secondsLeft < 60) { ++ let seconds = EndSessionDialog._roundSecondsToInterval(this._gracePeriod*60, this._secondsLeft, 10); ++ this._detailLabel.text = ++ _("Important security updates need to be installed now.\n") + ++ ngettext("This computer will restart in %d second.", ++ "This computer will restart in %d seconds.", ++ seconds).format(seconds); ++ } ++ return GLib.SOURCE_CONTINUE; ++ } ++ ++ this._done(); ++ return GLib.SOURCE_REMOVE; ++ })); ++ this.connect('destroy', Lang.bind(this, function() { ++ if (this._timerId > 0) { ++ GLib.source_remove(this._timerId); ++ this._timerId = 0; ++ } ++ })); ++ }, ++ ++ _done: function() { ++ this.emit('done'); ++ this.destroy(); ++ }, ++ ++ getState: function() { ++ return [this._gracePeriod, this._lastWarningPeriod, this._lastWarnings, this._secondsLeft]; ++ }, ++ ++ setState: function(state) { ++ [this._gracePeriod, this._lastWarningPeriod, this._lastWarnings, this._secondsLeft] = state; ++ }, ++}); ++Signals.addSignalMethods(UpdatesDialog.prototype); ++ ++function showDialog() { ++ if (updatesDialog) ++ return; ++ ++ updatesDialog = new UpdatesDialog(extensionSettings); ++ updatesDialog.tryOpen(); ++ updatesDialog.connect('destroy', function() { updatesDialog = null; }); ++ updatesDialog.connect('done', function() { ++ if (pkOfflineProxy.TriggerAction == 'power-off' || ++ pkOfflineProxy.TriggerAction == 'reboot') { ++ loginManagerProxy.RebootRemote(false); ++ } else { ++ pkOfflineProxy.TriggerRemote('reboot', function(result, error) { ++ if (!error) ++ loginManagerProxy.RebootRemote(false); ++ else ++ log('Failed to trigger offline update: %s'.format(error.message)); ++ }); ++ } ++ }); ++} ++ ++function cancelDialog(save) { ++ if (!updatesDialog) ++ return; ++ ++ if (save) { ++ let state = GLib.Variant.new('(uuuu)', updatesDialog.getState()); ++ global.set_runtime_state(Me.uuid, state); ++ } ++ updatesDialog.destroy(); ++} ++ ++function restoreExistingState() { ++ let state = global.get_runtime_state('(uuuu)', Me.uuid); ++ if (state === null) ++ return false; ++ ++ global.set_runtime_state(Me.uuid, null); ++ showDialog(); ++ updatesDialog.setState(state.deep_unpack()); ++ return true; ++} ++ ++function syncState() { ++ if (!pkOfflineProxy || !loginManagerProxy) ++ return; ++ ++ if (restoreExistingState()) ++ return; ++ ++ if (!updatesCheckInProgress && ++ securityUpdates.length > 0 && ++ pkOfflineProxy.UpdatePrepared) ++ showDialog(); ++ else ++ cancelDialog(); ++} ++ ++function doPkTransaction(callback) { ++ if (!pkProxy) ++ return; ++ ++ pkProxy.CreateTransactionRemote(function(result, error) { ++ if (error) { ++ log('Error creating PackageKit transaction: %s'.format(error.message)); ++ checkUpdatesDone(); ++ return; ++ } ++ ++ new PkTransactionProxy(Gio.DBus.system, ++ 'org.freedesktop.PackageKit', ++ String(result), ++ function(proxy, error) { ++ if (!error) { ++ proxy.SetHintsRemote( ++ ['background=true', 'interactive=false'], ++ function(result, error) { ++ if (error) { ++ log('Error connecting to PackageKit: %s'.format(error.message)); ++ checkUpdatesDone(); ++ return; ++ } ++ callback(proxy); ++ }); ++ } else { ++ log('Error connecting to PackageKit: %s'.format(error.message)); ++ } ++ }); ++ }); ++} ++ ++function pkUpdatePackages(proxy) { ++ proxy.connectSignal('Finished', function(p, e, params) { ++ let [exit, runtime] = params; ++ ++ if (exit == PkgKit.ExitEnum.CANCELLED_PRIORITY) { ++ // try again ++ checkUpdates(); ++ } else if (exit != PkgKit.ExitEnum.SUCCESS) { ++ log('UpdatePackages failed: %s'.format(PkgKit.ExitEnum.to_string(exit))); ++ } ++ ++ checkUpdatesDone(); ++ }); ++ proxy.UpdatePackagesRemote(1 << PkgKit.TransactionFlagEnum.ONLY_DOWNLOAD, securityUpdates); ++} ++ ++function pkGetUpdates(proxy) { ++ proxy.connectSignal('Package', function(p, e, params) { ++ let [info, packageId, summary] = params; ++ ++ if (info == PkgKit.InfoEnum.SECURITY) ++ securityUpdates.push(packageId); ++ }); ++ proxy.connectSignal('Finished', function(p, e, params) { ++ let [exit, runtime] = params; ++ ++ if (exit == PkgKit.ExitEnum.SUCCESS) { ++ if (securityUpdates.length > 0) { ++ doPkTransaction(pkUpdatePackages); ++ return; ++ } ++ } else if (exit == PkgKit.ExitEnum.CANCELLED_PRIORITY) { ++ // try again ++ checkUpdates(); ++ } else { ++ log('GetUpdates failed: %s'.format(PkgKit.ExitEnum.to_string(exit))); ++ } ++ ++ checkUpdatesDone(); ++ }); ++ proxy.GetUpdatesRemote(0); ++} ++ ++function checkUpdatesDone() { ++ updatesCheckInProgress = false; ++ if (updatesCheckRequested) { ++ updatesCheckRequested = false; ++ checkUpdates(); ++ } else { ++ syncState(); ++ } ++} ++ ++function checkUpdates() { ++ if (updatesCheckInProgress) { ++ updatesCheckRequested = true; ++ return; ++ } ++ updatesCheckInProgress = true; ++ securityUpdates = []; ++ doPkTransaction(pkGetUpdates); ++} ++ ++function initSystemProxies() { ++ new PkProxy(Gio.DBus.system, ++ 'org.freedesktop.PackageKit', ++ '/org/freedesktop/PackageKit', ++ function(proxy, error) { ++ if (!error) { ++ pkProxy = proxy; ++ let id = pkProxy.connectSignal('UpdatesChanged', checkUpdates); ++ pkProxy._signalId = id; ++ checkUpdates(); ++ } else { ++ log('Error connecting to PackageKit: %s'.format(error.message)); ++ } ++ }, ++ cancellable); ++ new PkOfflineProxy(Gio.DBus.system, ++ 'org.freedesktop.PackageKit', ++ '/org/freedesktop/PackageKit', ++ function(proxy, error) { ++ if (!error) { ++ pkOfflineProxy = proxy; ++ let id = pkOfflineProxy.connect('g-properties-changed', syncState); ++ pkOfflineProxy._signalId = id; ++ syncState(); ++ } else { ++ log('Error connecting to PackageKit: %s'.format(error.message)); ++ } ++ }, ++ cancellable); ++ new LoginManagerProxy(Gio.DBus.system, ++ 'org.freedesktop.login1', ++ '/org/freedesktop/login1', ++ function(proxy, error) { ++ if (!error) { ++ proxy.CanRebootRemote(cancellable, function(result, error) { ++ if (!error && result == 'yes') { ++ loginManagerProxy = proxy; ++ syncState(); ++ } else { ++ log('Reboot is not available'); ++ } ++ }); ++ } else { ++ log('Error connecting to Login manager: %s'.format(error.message)); ++ } ++ }, ++ cancellable); ++} ++ ++function init(metadata) { ++} ++ ++function enable() { ++ cancellable = new Gio.Cancellable(); ++ extensionSettings = Convenience.getSettings(); ++ Polkit.Permission.new("org.freedesktop.packagekit.trigger-offline-update", ++ null, cancellable, function(p, result) { ++ try { ++ let permission = Polkit.Permission.new_finish(result); ++ if (permission && permission.allowed) ++ initSystemProxies(); ++ else ++ throw(new Error('not allowed')); ++ } catch(e) { ++ log('No permission to trigger offline updates: %s'.format(e.toString())); ++ } ++ }); ++} ++ ++function disable() { ++ cancelDialog(true); ++ cancellable.cancel(); ++ cancellable = null; ++ extensionSettings = null; ++ updatesDialog = null; ++ loginManagerProxy = null; ++ if (pkOfflineProxy) { ++ pkOfflineProxy.disconnect(pkOfflineProxy._signalId); ++ pkOfflineProxy = null; ++ } ++ if (pkProxy) { ++ pkProxy.disconnectSignal(pkProxy._signalId); ++ pkProxy = null; ++ } ++} +diff --git a/extensions/updates-dialog/meson.build b/extensions/updates-dialog/meson.build +new file mode 100644 +index 0000000..585c02d +--- /dev/null ++++ b/extensions/updates-dialog/meson.build +@@ -0,0 +1,7 @@ ++extension_data += configure_file( ++ input: metadata_name + '.in', ++ output: metadata_name, ++ configuration: metadata_conf ++) ++ ++extension_schemas += files(metadata_conf.get('gschemaname') + '.gschema.xml') +diff --git a/extensions/updates-dialog/metadata.json.in b/extensions/updates-dialog/metadata.json.in +new file mode 100644 +index 0000000..9946abb +--- /dev/null ++++ b/extensions/updates-dialog/metadata.json.in +@@ -0,0 +1,10 @@ ++{ ++"extension-id": "@extension_id@", ++"uuid": "@uuid@", ++"settings-schema": "@gschemaname@", ++"gettext-domain": "@gettext_domain@", ++"name": "Updates Dialog", ++"description": "Shows a modal dialog when there are software updates.", ++"shell-version": [ "@shell_current@" ], ++"url": "http://rtcm.fedorapeople.org/updates-dialog" ++} +diff --git a/extensions/updates-dialog/org.gnome.shell.extensions.updates-dialog.gschema.xml b/extensions/updates-dialog/org.gnome.shell.extensions.updates-dialog.gschema.xml +new file mode 100644 +index 0000000..c08d33c +--- /dev/null ++++ b/extensions/updates-dialog/org.gnome.shell.extensions.updates-dialog.gschema.xml +@@ -0,0 +1,30 @@ ++ ++ ++ ++ ++ 300 ++ Grace period in minutes ++ ++ When the grace period is over, the computer will automatically ++ reboot and install security updates. ++ ++ ++ ++ 10 ++ Last warning dialog period ++ ++ A last warning dialog is displayed this many minutes before ++ the automatic reboot. ++ ++ ++ ++ 1 ++ Number of last warning dialogs ++ ++ How many warning dialogs are displayed. Each is displayed at ++ 'last-warning-period' minute intervals. ++ ++ ++ ++ +diff --git a/extensions/updates-dialog/stylesheet.css b/extensions/updates-dialog/stylesheet.css +new file mode 100644 +index 0000000..25134b6 +--- /dev/null ++++ b/extensions/updates-dialog/stylesheet.css +@@ -0,0 +1 @@ ++/* This extensions requires no special styling */ +diff --git a/meson.build b/meson.build +index 3451585..08a243e 100644 +--- a/meson.build ++++ b/meson.build +@@ -57,6 +57,7 @@ all_extensions += [ + 'native-window-placement', + 'panel-favorites', + 'top-icons', ++ 'updates-dialog', + 'user-theme' + ] + +diff --git a/po/POTFILES.in b/po/POTFILES.in +index d98ca1b..43a817f 100644 +--- a/po/POTFILES.in ++++ b/po/POTFILES.in +@@ -15,6 +15,8 @@ extensions/native-window-placement/org.gnome.shell.extensions.native-window-plac + extensions/places-menu/extension.js + extensions/places-menu/placeDisplay.js + extensions/screenshot-window-sizer/org.gnome.shell.extensions.screenshot-window-sizer.gschema.xml ++extensions/updates-dialog/extension.js ++extensions/updates-dialog/org.gnome.shell.extensions.updates-dialog.gschema.xml + extensions/user-theme/extension.js + extensions/user-theme/org.gnome.shell.extensions.user-theme.gschema.xml + extensions/window-list/extension.js +-- +2.20.1 + + +From 3602940b74b9b138d9eb51b4ad2fe6cdacacbfd0 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/6] Add no-hot-corner extension + +--- + extensions/no-hot-corner/extension.js | 31 +++++++++++++++++++++++ + extensions/no-hot-corner/meson.build | 5 ++++ + extensions/no-hot-corner/metadata.json.in | 9 +++++++ + extensions/no-hot-corner/stylesheet.css | 1 + + meson.build | 1 + + 5 files changed, 47 insertions(+) + create mode 100644 extensions/no-hot-corner/extension.js + create mode 100644 extensions/no-hot-corner/meson.build + create mode 100644 extensions/no-hot-corner/metadata.json.in + create mode 100644 extensions/no-hot-corner/stylesheet.css + +diff --git a/extensions/no-hot-corner/extension.js b/extensions/no-hot-corner/extension.js +new file mode 100644 +index 0000000..e7a0d63 +--- /dev/null ++++ b/extensions/no-hot-corner/extension.js +@@ -0,0 +1,31 @@ ++const Main = imports.ui.main; ++ ++let _id; ++ ++function _disableHotCorners() { ++ // Disables all hot corners ++ Main.layoutManager.hotCorners.forEach(function(hotCorner) { ++ if (!hotCorner) { ++ return; ++ } ++ ++ hotCorner._toggleOverview = function() {}; ++ hotCorner._pressureBarrier._trigger = function() {}; ++ }); ++} ++ ++function init() { ++} ++ ++function enable() { ++ _disableHotCorners(); ++ // Hot corners may be re-created afterwards (for example, If there's a monitor change). ++ // So we catch all changes. ++ _id = Main.layoutManager.connect('hot-corners-changed', _disableHotCorners); ++} ++ ++function disable() { ++ // Disconnects the callback and re-creates the hot corners ++ Main.layoutManager.disconnect(_id); ++ Main.layoutManager._updateHotCorners(); ++} +diff --git a/extensions/no-hot-corner/meson.build b/extensions/no-hot-corner/meson.build +new file mode 100644 +index 0000000..48504f6 +--- /dev/null ++++ b/extensions/no-hot-corner/meson.build +@@ -0,0 +1,5 @@ ++extension_data += configure_file( ++ input: metadata_name + '.in', ++ output: metadata_name, ++ configuration: metadata_conf ++) +diff --git a/extensions/no-hot-corner/metadata.json.in b/extensions/no-hot-corner/metadata.json.in +new file mode 100644 +index 0000000..406d83b +--- /dev/null ++++ b/extensions/no-hot-corner/metadata.json.in +@@ -0,0 +1,9 @@ ++{ ++"extension-id": "@extension_id@", ++"uuid": "@uuid@", ++"name": "No Topleft Hot Corner", ++"description": "Disable the hot corner in the top left; you can still reach the overview by clicking the Activities button or pressing the dedicated key.", ++"shell-version": [ "@shell_current@" ], ++"url": "https://github.com/HROMANO/nohotcorner/", ++"version": 15 ++} +diff --git a/extensions/no-hot-corner/stylesheet.css b/extensions/no-hot-corner/stylesheet.css +new file mode 100644 +index 0000000..25134b6 +--- /dev/null ++++ b/extensions/no-hot-corner/stylesheet.css +@@ -0,0 +1 @@ ++/* This extensions requires no special styling */ +diff --git a/meson.build b/meson.build +index 08a243e..201c484 100644 +--- a/meson.build ++++ b/meson.build +@@ -55,6 +55,7 @@ all_extensions += [ + 'dash-to-dock', + 'example', + 'native-window-placement', ++ 'no-hot-corner', + 'panel-favorites', + 'top-icons', + 'updates-dialog', +-- +2.20.1 + + +From 74d8ce6acd4c1a61de25d796f63e4f1f79180ed4 Mon Sep 17 00:00:00 2001 +From: Carlos Soriano +Date: Mon, 13 Aug 2018 17:28:41 +0200 +Subject: [PATCH 6/6] Add desktop icons extension + +--- + extensions/desktop-icons/createThumbnail.js | 35 + + extensions/desktop-icons/dbusUtils.js | 74 ++ + extensions/desktop-icons/desktopGrid.js | 664 +++++++++++++++ + extensions/desktop-icons/desktopIconsUtil.js | 60 ++ + extensions/desktop-icons/desktopManager.js | 701 ++++++++++++++++ + extensions/desktop-icons/extension.js | 71 ++ + extensions/desktop-icons/fileItem.js | 771 ++++++++++++++++++ + extensions/desktop-icons/meson.build | 18 + + extensions/desktop-icons/metadata.json.in | 11 + + extensions/desktop-icons/po/LINGUAS | 12 + + extensions/desktop-icons/po/POTFILES.in | 4 + + extensions/desktop-icons/po/cs.po | 136 +++ + extensions/desktop-icons/po/da.po | 136 +++ + extensions/desktop-icons/po/de.po | 136 +++ + extensions/desktop-icons/po/es.po | 186 +++++ + extensions/desktop-icons/po/fi.po | 136 +++ + extensions/desktop-icons/po/fr.po | 164 ++++ + extensions/desktop-icons/po/id.po | 135 +++ + extensions/desktop-icons/po/it.po | 152 ++++ + extensions/desktop-icons/po/meson.build | 1 + + extensions/desktop-icons/po/pl.po | 157 ++++ + extensions/desktop-icons/po/pt_BR.po | 163 ++++ + extensions/desktop-icons/po/ru.po | 153 ++++ + extensions/desktop-icons/po/zh_TW.po | 135 +++ + extensions/desktop-icons/prefs.js | 159 ++++ + extensions/desktop-icons/schemas/meson.build | 6 + + ...shell.extensions.desktop-icons.gschema.xml | 25 + + extensions/desktop-icons/stylesheet.css | 33 + + meson.build | 1 + + po/cs.po | 161 ++++ + po/da.po | 161 ++++ + po/de.po | 161 ++++ + po/es.po | 222 ++++- + po/fi.po | 167 +++- + po/fr.po | 190 +++++ + po/id.po | 159 ++++ + po/it.po | 177 ++++ + po/pl.po | 183 +++++ + po/pt_BR.po | 197 ++++- + po/ru.po | 179 ++++ + po/zh_TW.po | 165 +++- + 41 files changed, 6527 insertions(+), 30 deletions(-) + create mode 100755 extensions/desktop-icons/createThumbnail.js + create mode 100644 extensions/desktop-icons/dbusUtils.js + create mode 100644 extensions/desktop-icons/desktopGrid.js + create mode 100644 extensions/desktop-icons/desktopIconsUtil.js + create mode 100644 extensions/desktop-icons/desktopManager.js + create mode 100644 extensions/desktop-icons/extension.js + create mode 100644 extensions/desktop-icons/fileItem.js + create mode 100644 extensions/desktop-icons/meson.build + create mode 100644 extensions/desktop-icons/metadata.json.in + create mode 100644 extensions/desktop-icons/po/LINGUAS + create mode 100644 extensions/desktop-icons/po/POTFILES.in + create mode 100644 extensions/desktop-icons/po/cs.po + create mode 100644 extensions/desktop-icons/po/da.po + create mode 100644 extensions/desktop-icons/po/de.po + create mode 100644 extensions/desktop-icons/po/es.po + create mode 100644 extensions/desktop-icons/po/fi.po + create mode 100644 extensions/desktop-icons/po/fr.po + create mode 100644 extensions/desktop-icons/po/id.po + create mode 100644 extensions/desktop-icons/po/it.po + create mode 100644 extensions/desktop-icons/po/meson.build + create mode 100644 extensions/desktop-icons/po/pl.po + create mode 100644 extensions/desktop-icons/po/pt_BR.po + create mode 100644 extensions/desktop-icons/po/ru.po + create mode 100644 extensions/desktop-icons/po/zh_TW.po + create mode 100644 extensions/desktop-icons/prefs.js + create mode 100644 extensions/desktop-icons/schemas/meson.build + create mode 100644 extensions/desktop-icons/schemas/org.gnome.shell.extensions.desktop-icons.gschema.xml + create mode 100644 extensions/desktop-icons/stylesheet.css + +diff --git a/extensions/desktop-icons/createThumbnail.js b/extensions/desktop-icons/createThumbnail.js +new file mode 100755 +index 0000000..212f6b7 +--- /dev/null ++++ b/extensions/desktop-icons/createThumbnail.js +@@ -0,0 +1,35 @@ ++#!/usr/bin/gjs ++ ++/* Desktop Icons GNOME Shell extension ++ * ++ * Copyright (C) 2018 Sergio Costas ++ * ++ * This program is free software: you can redistribute it and/or modify ++ * it under the terms of the GNU General Public License as published by ++ * the Free Software Foundation, either version 3 of the License, or ++ * (at your option) any later version. ++ * ++ * This program is distributed in the hope that it will be useful, ++ * but WITHOUT ANY WARRANTY; without even the implied warranty of ++ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ++ * GNU General Public License for more details. ++ * ++ * You should have received a copy of the GNU General Public License ++ * along with this program. If not, see . ++ */ ++ ++const GnomeDesktop = imports.gi.GnomeDesktop; ++const Gio = imports.gi.Gio; ++ ++let thumbnailFactory = GnomeDesktop.DesktopThumbnailFactory.new(GnomeDesktop.DesktopThumbnailSize.LARGE); ++ ++let file = Gio.File.new_for_path(ARGV[0]); ++let fileUri = file.get_uri(); ++ ++let fileInfo = file.query_info('standard::content-type,time::modified', Gio.FileQueryInfoFlags.NONE, null); ++let modifiedTime = fileInfo.get_attribute_uint64('time::modified'); ++let thumbnailPixbuf = thumbnailFactory.generate_thumbnail(fileUri, fileInfo.get_content_type()); ++if (thumbnailPixbuf == null) ++ thumbnailFactory.create_failed_thumbnail(fileUri, modifiedTime); ++else ++ thumbnailFactory.save_thumbnail(thumbnailPixbuf, fileUri, modifiedTime); +diff --git a/extensions/desktop-icons/dbusUtils.js b/extensions/desktop-icons/dbusUtils.js +new file mode 100644 +index 0000000..ba9e912 +--- /dev/null ++++ b/extensions/desktop-icons/dbusUtils.js +@@ -0,0 +1,74 @@ ++const Gio = imports.gi.Gio; ++var NautilusFileOperationsProxy; ++var FreeDesktopFileManagerProxy; ++ ++const NautilusFileOperationsInterface = ` ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++`; ++ ++const NautilusFileOperationsProxyInterface = Gio.DBusProxy.makeProxyWrapper(NautilusFileOperationsInterface); ++ ++const FreeDesktopFileManagerInterface = ` ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++`; ++ ++const FreeDesktopFileManagerProxyInterface = Gio.DBusProxy.makeProxyWrapper(FreeDesktopFileManagerInterface); ++ ++function init() { ++ NautilusFileOperationsProxy = new NautilusFileOperationsProxyInterface( ++ Gio.DBus.session, ++ 'org.gnome.Nautilus', ++ '/org/gnome/Nautilus', ++ (proxy, error) => { ++ if (error) { ++ log('Error connecting to Nautilus'); ++ } ++ } ++ ); ++ ++ FreeDesktopFileManagerProxy = new FreeDesktopFileManagerProxyInterface( ++ Gio.DBus.session, ++ 'org.freedesktop.FileManager1', ++ '/org/freedesktop/FileManager1', ++ (proxy, error) => { ++ if (error) { ++ log('Error connecting to Nautilus'); ++ } ++ } ++ ); ++} +diff --git a/extensions/desktop-icons/desktopGrid.js b/extensions/desktop-icons/desktopGrid.js +new file mode 100644 +index 0000000..83ca2cc +--- /dev/null ++++ b/extensions/desktop-icons/desktopGrid.js +@@ -0,0 +1,664 @@ ++/* Desktop Icons GNOME Shell extension ++ * ++ * Copyright (C) 2017 Carlos Soriano ++ * ++ * This program is free software: you can redistribute it and/or modify ++ * it under the terms of the GNU General Public License as published by ++ * the Free Software Foundation, either version 3 of the License, or ++ * (at your option) any later version. ++ * ++ * This program is distributed in the hope that it will be useful, ++ * but WITHOUT ANY WARRANTY; without even the implied warranty of ++ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ++ * GNU General Public License for more details. ++ * ++ * You should have received a copy of the GNU General Public License ++ * along with this program. If not, see . ++ */ ++ ++const Gtk = imports.gi.Gtk; ++const Clutter = imports.gi.Clutter; ++const Lang = imports.lang; ++const St = imports.gi.St; ++const Gio = imports.gi.Gio; ++const GLib = imports.gi.GLib; ++const Shell = imports.gi.Shell; ++ ++const Signals = imports.signals; ++ ++const Layout = imports.ui.layout; ++const Main = imports.ui.main; ++const BoxPointer = imports.ui.boxpointer; ++const PopupMenu = imports.ui.popupMenu; ++const GrabHelper = imports.ui.grabHelper; ++ ++const ExtensionUtils = imports.misc.extensionUtils; ++const Me = ExtensionUtils.getCurrentExtension(); ++const Extension = Me.imports.extension; ++const FileItem = Me.imports.fileItem; ++const Prefs = Me.imports.prefs; ++const DBusUtils = Me.imports.dbusUtils; ++const DesktopIconsUtil = Me.imports.desktopIconsUtil; ++const Util = imports.misc.util; ++ ++const Clipboard = St.Clipboard.get_default(); ++const CLIPBOARD_TYPE = St.ClipboardType.CLIPBOARD; ++const Gettext = imports.gettext.domain('desktop-icons'); ++ ++const _ = Gettext.gettext; ++ ++ ++/* From NautilusFileUndoManagerState */ ++var UndoStatus = { ++ NONE: 0, ++ UNDO: 1, ++ REDO: 2, ++}; ++ ++var StoredCoordinates = { ++ PRESERVE: 0, ++ OVERWRITE:1, ++}; ++ ++class Placeholder extends St.Bin { ++ constructor() { ++ super(); ++ } ++} ++ ++var DesktopGrid = class { ++ ++ constructor(bgManager) { ++ this._bgManager = bgManager; ++ ++ this._fileItemHandlers = new Map(); ++ this._fileItems = []; ++ ++ this.layout = new Clutter.GridLayout({ ++ orientation: Clutter.Orientation.VERTICAL, ++ column_homogeneous: true, ++ row_homogeneous: true ++ }); ++ ++ this.actor = new St.Widget(); ++ this.actor._delegate = this; ++ ++ this._grid = new St.Widget({ ++ name: 'DesktopGrid', ++ layout_manager: this.layout, ++ reactive: true, ++ x_expand: true, ++ y_expand: true, ++ can_focus: true, ++ opacity: 255 ++ }); ++ this.actor.add_child(this._grid); ++ ++ this._renamePopup = new RenamePopup(this); ++ this.actor.add_child(this._renamePopup.actor); ++ ++ this._bgManager._container.add_child(this.actor); ++ ++ this.actor.connect('destroy', () => this._onDestroy()); ++ ++ let monitorIndex = bgManager._monitorIndex; ++ this._monitorConstraint = new Layout.MonitorConstraint({ ++ index: monitorIndex, ++ work_area: true ++ }); ++ this._grid.add_constraint(this._monitorConstraint); ++ ++ this._addDesktopBackgroundMenu(); ++ ++ this._bgDestroyedId = bgManager.backgroundActor.connect('destroy', ++ () => this._backgroundDestroyed()); ++ ++ this._grid.connect('button-press-event', (actor, event) => this._onPressButton(actor, event)); ++ ++ this._grid.connect('key-press-event', this._onKeyPress.bind(this)); ++ ++ this._grid.connect('allocation-changed', () => Extension.desktopManager.scheduleReLayoutChildren()); ++ } ++ ++ _onKeyPress(actor, event) { ++ if (global.stage.get_key_focus() != actor) ++ return Clutter.EVENT_PROPAGATE; ++ ++ let symbol = event.get_key_symbol(); ++ let isCtrl = (event.get_state() & Clutter.ModifierType.CONTROL_MASK) != 0; ++ let isShift = (event.get_state() & Clutter.ModifierType.SHIFT_MASK) != 0; ++ if (isCtrl && isShift && [Clutter.Z, Clutter.z].indexOf(symbol) > -1) { ++ this._doRedo(); ++ return Clutter.EVENT_STOP; ++ } ++ else if (isCtrl && [Clutter.Z, Clutter.z].indexOf(symbol) > -1) { ++ this._doUndo(); ++ return Clutter.EVENT_STOP; ++ } ++ else if (isCtrl && [Clutter.C, Clutter.c].indexOf(symbol) > -1) { ++ Extension.desktopManager.doCopy(); ++ return Clutter.EVENT_STOP; ++ } ++ else if (isCtrl && [Clutter.X, Clutter.x].indexOf(symbol) > -1) { ++ Extension.desktopManager.doCut(); ++ return Clutter.EVENT_STOP; ++ } ++ else if (isCtrl && [Clutter.V, Clutter.v].indexOf(symbol) > -1) { ++ this._doPaste(); ++ return Clutter.EVENT_STOP; ++ } ++ else if (symbol == Clutter.Return) { ++ Extension.desktopManager.doOpen(); ++ return Clutter.EVENT_STOP; ++ } ++ else if (symbol == Clutter.Delete) { ++ Extension.desktopManager.doTrash(); ++ return Clutter.EVENT_STOP; ++ } else if (symbol == Clutter.F2) { ++ // Support renaming other grids file items. ++ Extension.desktopManager.doRename(); ++ return Clutter.EVENT_STOP; ++ } ++ ++ return Clutter.EVENT_PROPAGATE; ++ } ++ ++ _backgroundDestroyed() { ++ this._bgDestroyedId = 0; ++ if (this._bgManager == null) ++ return; ++ ++ if (this._bgManager._backgroundSource) { ++ this._bgDestroyedId = this._bgManager.backgroundActor.connect('destroy', ++ () => this._backgroundDestroyed()); ++ } else { ++ this.actor.destroy(); ++ } ++ } ++ ++ _onDestroy() { ++ if (this._bgDestroyedId && this._bgManager.backgroundActor != null) ++ this._bgManager.backgroundActor.disconnect(this._bgDestroyedId); ++ this._bgDestroyedId = 0; ++ this._bgManager = null; ++ } ++ ++ _omNewFolderClicked() { ++ let dir = DesktopIconsUtil.getDesktopDir().get_child(_('New Folder')); ++ DBusUtils.NautilusFileOperationsProxy.CreateFolderRemote(dir.get_uri(), ++ (result, error) => { ++ if (error) ++ throw new Error('Error creating new folder: ' + error.message); ++ } ++ ); ++ } ++ ++ _parseClipboardText(text) { ++ let lines = text.split('\n'); ++ let [mime, action, ...files] = lines; ++ ++ if (mime != 'x-special/nautilus-clipboard') ++ return [false, false, null]; ++ ++ if (!(['copy', 'cut'].includes(action))) ++ return [false, false, null]; ++ let isCut = action == 'cut'; ++ ++ /* Last line is empty due to the split */ ++ if (files.length <= 1) ++ return [false, false, null]; ++ /* Remove last line */ ++ files.pop(); ++ ++ return [true, isCut, files]; ++ } ++ ++ _doPaste() { ++ Clipboard.get_text(CLIPBOARD_TYPE, ++ (clipboard, text) => { ++ let [valid, is_cut, files] = this._parseClipboardText(text); ++ if (!valid) ++ return; ++ ++ let desktopDir = `${DesktopIconsUtil.getDesktopDir().get_uri()}`; ++ if (is_cut) { ++ DBusUtils.NautilusFileOperationsProxy.MoveURIsRemote(files, desktopDir, ++ (result, error) => { ++ if (error) ++ throw new Error('Error moving files: ' + error.message); ++ } ++ ); ++ } else { ++ DBusUtils.NautilusFileOperationsProxy.CopyURIsRemote(files, desktopDir, ++ (result, error) => { ++ if (error) ++ throw new Error('Error copying files: ' + error.message); ++ } ++ ); ++ } ++ } ++ ); ++ } ++ ++ _onPasteClicked() { ++ this._doPaste(); ++ } ++ ++ _doUndo() { ++ DBusUtils.NautilusFileOperationsProxy.UndoRemote( ++ (result, error) => { ++ if (error) ++ throw new Error('Error performing undo: ' + error.message); ++ } ++ ); ++ } ++ ++ _onUndoClicked() { ++ this._doUndo(); ++ } ++ ++ _doRedo() { ++ DBusUtils.NautilusFileOperationsProxy.RedoRemote( ++ (result, error) => { ++ if (error) ++ throw new Error('Error performing redo: ' + error.message); ++ } ++ ); ++ } ++ ++ _onRedoClicked() { ++ this._doRedo(); ++ } ++ ++ _onOpenDesktopInFilesClicked() { ++ Gio.AppInfo.launch_default_for_uri_async(DesktopIconsUtil.getDesktopDir().get_uri(), ++ null, null, ++ (source, res) => { ++ try { ++ Gio.AppInfo.launch_default_for_uri_finish(res); ++ } catch (e) { ++ log('Error opening Desktop in Files: ' + e.message); ++ } ++ } ++ ); ++ } ++ ++ _onOpenTerminalClicked() { ++ let desktopPath = DesktopIconsUtil.getDesktopDir().get_path(); ++ let command = DesktopIconsUtil.getTerminalCommand(desktopPath); ++ ++ Util.spawnCommandLine(command); ++ } ++ ++ _syncUndoRedo() { ++ this._undoMenuItem.actor.visible = DBusUtils.NautilusFileOperationsProxy.UndoStatus == UndoStatus.UNDO; ++ this._redoMenuItem.actor.visible = DBusUtils.NautilusFileOperationsProxy.UndoStatus == UndoStatus.REDO; ++ } ++ ++ _undoStatusChanged(proxy, properties, test) { ++ if ('UndoStatus' in properties.deep_unpack()) ++ this._syncUndoRedo(); ++ } ++ ++ _createDesktopBackgroundMenu() { ++ let menu = new PopupMenu.PopupMenu(Main.layoutManager.dummyCursor, ++ 0, St.Side.TOP); ++ menu.addAction(_("New Folder"), () => this._omNewFolderClicked()); ++ menu.addMenuItem(new PopupMenu.PopupSeparatorMenuItem()); ++ this._pasteMenuItem = menu.addAction(_("Paste"), () => this._onPasteClicked()); ++ this._undoMenuItem = menu.addAction(_("Undo"), () => this._onUndoClicked()); ++ this._redoMenuItem = menu.addAction(_("Redo"), () => this._onRedoClicked()); ++ menu.addMenuItem(new PopupMenu.PopupSeparatorMenuItem()); ++ menu.addAction(_("Show Desktop in Files"), () => this._onOpenDesktopInFilesClicked()); ++ menu.addAction(_("Open in Terminal"), () => this._onOpenTerminalClicked()); ++ menu.addMenuItem(new PopupMenu.PopupSeparatorMenuItem()); ++ menu.addSettingsAction(_("Change Background…"), 'gnome-background-panel.desktop'); ++ menu.addMenuItem(new PopupMenu.PopupSeparatorMenuItem()); ++ menu.addSettingsAction(_("Display Settings"), 'gnome-display-panel.desktop'); ++ menu.addSettingsAction(_("Settings"), 'gnome-control-center.desktop'); ++ ++ menu.actor.add_style_class_name('background-menu'); ++ ++ Main.layoutManager.uiGroup.add_child(menu.actor); ++ menu.actor.hide(); ++ ++ menu._propertiesChangedId = DBusUtils.NautilusFileOperationsProxy.connect('g-properties-changed', ++ this._undoStatusChanged.bind(this)); ++ this._syncUndoRedo(); ++ ++ menu.connect('destroy', ++ () => DBusUtils.NautilusFileOperationsProxy.disconnect(menu._propertiesChangedId)); ++ menu.connect('open-state-changed', ++ (popupm, isOpen) => { ++ if (isOpen) { ++ Clipboard.get_text(CLIPBOARD_TYPE, ++ (clipBoard, text) => { ++ let [valid, is_cut, files] = this._parseClipboardText(text); ++ this._pasteMenuItem.actor.visible = valid; ++ } ++ ); ++ } ++ } ++ ); ++ ++ return menu; ++ } ++ ++ _openMenu(x, y) { ++ Main.layoutManager.setDummyCursorGeometry(x, y, 0, 0); ++ this.actor._desktopBackgroundMenu.open(BoxPointer.PopupAnimation.NONE); ++ /* Since the handler is in the press event it needs to ignore the release event ++ * to not immediately close the menu on release ++ */ ++ this.actor._desktopBackgroundManager.ignoreRelease(); ++ } ++ ++ _addFileItemTo(fileItem, column, row, overwriteCoordinates) { ++ let placeholder = this.layout.get_child_at(column, row); ++ placeholder.child = fileItem.actor; ++ this._fileItems.push(fileItem); ++ let selectedId = fileItem.connect('selected', this._onFileItemSelected.bind(this)); ++ let renameId = fileItem.connect('rename-clicked', this.doRename.bind(this)); ++ this._fileItemHandlers.set(fileItem, [selectedId, renameId]); ++ ++ /* If this file is new in the Desktop and hasn't yet ++ * fixed coordinates, store the new possition to ensure ++ * that the next time it will be shown in the same possition. ++ * Also store the new possition if it has been moved by the user, ++ * and not triggered by a screen change. ++ */ ++ if ((fileItem.savedCoordinates == null) || (overwriteCoordinates == StoredCoordinates.OVERWRITE)) { ++ let [fileX, fileY] = placeholder.get_transformed_position(); ++ fileItem.savedCoordinates = [Math.round(fileX), Math.round(fileY)]; ++ } ++ } ++ ++ addFileItemCloseTo(fileItem, x, y, overwriteCoordinates) { ++ let [column, row] = this._getEmptyPlaceClosestTo(x, y); ++ this._addFileItemTo(fileItem, column, row, overwriteCoordinates); ++ } ++ ++ _getEmptyPlaceClosestTo(x, y) { ++ let maxColumns = this._getMaxColumns(); ++ let maxRows = this._getMaxRows(); ++ ++ let [actorX, actorY] = this._grid.get_transformed_position(); ++ let actorWidth = this._grid.allocation.x2 - this._grid.allocation.x1; ++ let actorHeight = this._grid.allocation.y2 - this._grid.allocation.y1; ++ let placeX = Math.round((x - actorX) * maxColumns / actorWidth); ++ let placeY = Math.round((y - actorY) * maxRows / actorHeight); ++ ++ placeX = DesktopIconsUtil.clamp(placeX, 0, maxColumns - 1); ++ placeY = DesktopIconsUtil.clamp(placeY, 0, maxRows - 1); ++ if (this.layout.get_child_at(placeX, placeY).child == null) ++ return [placeX, placeY]; ++ let found = false; ++ let resColumn = null; ++ let resRow = null; ++ let minDistance = Infinity; ++ for (let column = 0; column < maxColumns; column++) { ++ for (let row = 0; row < maxRows; row++) { ++ let placeholder = this.layout.get_child_at(column, row); ++ if (placeholder.child != null) ++ continue; ++ ++ let [proposedX, proposedY] = placeholder.get_transformed_position(); ++ let distance = DesktopIconsUtil.distanceBetweenPoints(proposedX, proposedY, x, y); ++ if (distance < minDistance) { ++ found = true; ++ minDistance = distance; ++ resColumn = column; ++ resRow = row; ++ } ++ } ++ } ++ ++ if (!found) ++ throw new Error(`Not enough place at monitor ${this._bgManager._monitorIndex}`); ++ ++ return [resColumn, resRow]; ++ } ++ ++ removeFileItem(fileItem) { ++ let index = this._fileItems.indexOf(fileItem); ++ if (index > -1) ++ this._fileItems.splice(index, 1); ++ else ++ throw new Error('Error removing children from container'); ++ ++ let [column, row] = this._getPosOfFileItem(fileItem); ++ let placeholder = this.layout.get_child_at(column, row); ++ placeholder.child = null; ++ let [selectedId, renameId] = this._fileItemHandlers.get(fileItem); ++ fileItem.disconnect(selectedId); ++ fileItem.disconnect(renameId); ++ this._fileItemHandlers.delete(fileItem); ++ } ++ ++ _fillPlaceholders() { ++ for (let column = 0; column < this._getMaxColumns(); column++) { ++ for (let row = 0; row < this._getMaxRows(); row++) { ++ this.layout.attach(new Placeholder(), column, row, 1, 1); ++ } ++ } ++ } ++ ++ reset() { ++ let tmpFileItemsCopy = this._fileItems.slice(); ++ for (let fileItem of tmpFileItemsCopy) ++ this.removeFileItem(fileItem); ++ this._grid.remove_all_children(); ++ ++ this._fillPlaceholders(); ++ } ++ ++ _onStageMotion(actor, event) { ++ if (this._drawingRubberBand) { ++ let [x, y] = event.get_coords(); ++ this._updateRubberBand(x, y); ++ this._selectFromRubberband(x, y); ++ } ++ return Clutter.EVENT_PROPAGATE; ++ } ++ ++ _onPressButton(actor, event) { ++ let button = event.get_button(); ++ let [x, y] = event.get_coords(); ++ ++ this._grid.grab_key_focus(); ++ ++ if (button == 1) { ++ let shiftPressed = !!(event.get_state() & Clutter.ModifierType.SHIFT_MASK); ++ let controlPressed = !!(event.get_state() & Clutter.ModifierType.CONTROL_MASK); ++ if (!shiftPressed && !controlPressed) ++ Extension.desktopManager.clearSelection(); ++ let [gridX, gridY] = this._grid.get_transformed_position(); ++ Extension.desktopManager.startRubberBand(x, y, gridX, gridY); ++ return Clutter.EVENT_STOP; ++ } ++ ++ if (button == 3) { ++ this._openMenu(x, y); ++ ++ return Clutter.EVENT_STOP; ++ } ++ ++ return Clutter.EVENT_PROPAGATE; ++ } ++ ++ _addDesktopBackgroundMenu() { ++ this.actor._desktopBackgroundMenu = this._createDesktopBackgroundMenu(); ++ this.actor._desktopBackgroundManager = new PopupMenu.PopupMenuManager({ actor: this.actor }); ++ this.actor._desktopBackgroundManager.addMenu(this.actor._desktopBackgroundMenu); ++ ++ this.actor.connect('destroy', () => { ++ this.actor._desktopBackgroundMenu.destroy(); ++ this.actor._desktopBackgroundMenu = null; ++ this.actor._desktopBackgroundManager = null; ++ }); ++ } ++ ++ _getMaxColumns() { ++ let gridWidth = this._grid.allocation.x2 - this._grid.allocation.x1; ++ return Math.floor(gridWidth / Prefs.get_desired_width(St.ThemeContext.get_for_stage(global.stage).scale_factor)); ++ } ++ ++ _getMaxRows() { ++ let gridHeight = this._grid.allocation.y2 - this._grid.allocation.y1; ++ return Math.floor(gridHeight / Prefs.get_desired_height(St.ThemeContext.get_for_stage(global.stage).scale_factor)); ++ } ++ ++ acceptDrop(source, actor, x, y, time) { ++ /* Coordinates are relative to the grid, we want to transform them to ++ * absolute coordinates to work across monitors */ ++ let [gridX, gridY] = this._grid.get_transformed_position(); ++ let [absoluteX, absoluteY] = [x + gridX, y + gridY]; ++ return Extension.desktopManager.acceptDrop(absoluteX, absoluteY); ++ } ++ ++ _getPosOfFileItem(itemToFind) { ++ if (itemToFind == null) ++ throw new Error('Error at _getPosOfFileItem: child cannot be null'); ++ ++ let found = false; ++ let maxColumns = this._getMaxColumns(); ++ let maxRows = this._getMaxRows(); ++ let column = 0; ++ let row = 0; ++ for (column = 0; column < maxColumns; column++) { ++ for (row = 0; row < maxRows; row++) { ++ let item = this.layout.get_child_at(column, row); ++ if (item.child && item.child._delegate.file.equal(itemToFind.file)) { ++ found = true; ++ break; ++ } ++ } ++ ++ if (found) ++ break; ++ } ++ ++ if (!found) ++ throw new Error('Position of file item was not found'); ++ ++ return [column, row]; ++ } ++ ++ _onFileItemSelected(fileItem, keepCurrentSelection, addToSelection) { ++ this._grid.grab_key_focus(); ++ } ++ ++ doRename(fileItem) { ++ this._renamePopup.onFileItemRenameClicked(fileItem); ++ } ++}; ++ ++var RenamePopup = class { ++ ++ constructor(grid) { ++ this._source = null; ++ this._isOpen = false; ++ ++ this._renameEntry = new St.Entry({ hint_text: _("Enter file name…"), ++ can_focus: true, ++ x_expand: true }); ++ this._renameEntry.clutter_text.connect('activate', this._onRenameAccepted.bind(this)); ++ this._renameOkButton= new St.Button({ label: _("OK"), ++ style_class: 'app-view-control button', ++ button_mask: St.ButtonMask.ONE | St.ButtonMask.THREE, ++ reactive: true, ++ can_focus: true, ++ x_expand: true }); ++ this._renameCancelButton = new St.Button({ label: _("Cancel"), ++ style_class: 'app-view-control button', ++ button_mask: St.ButtonMask.ONE | St.ButtonMask.THREE, ++ reactive: true, ++ can_focus: true, ++ x_expand: true }); ++ this._renameCancelButton.connect('clicked', () => { this._onRenameCanceled(); }); ++ this._renameOkButton.connect('clicked', () => { this._onRenameAccepted(); }); ++ let renameButtonsBoxLayout = new Clutter.BoxLayout({ homogeneous: true }); ++ let renameButtonsBox = new St.Widget({ layout_manager: renameButtonsBoxLayout, ++ x_expand: true }); ++ renameButtonsBox.add_child(this._renameCancelButton); ++ renameButtonsBox.add_child(this._renameOkButton); ++ ++ let renameContentLayout = new Clutter.BoxLayout({ spacing: 6, ++ orientation: Clutter.Orientation.VERTICAL }); ++ let renameContent = new St.Widget({ style_class: 'rename-popup', ++ layout_manager: renameContentLayout, ++ x_expand: true }); ++ renameContent.add_child(this._renameEntry); ++ renameContent.add_child(renameButtonsBox); ++ ++ this._boxPointer = new BoxPointer.BoxPointer(St.Side.TOP, { can_focus: false, x_expand: true }); ++ this.actor = this._boxPointer.actor; ++ this.actor.style_class = 'popup-menu-boxpointer'; ++ this.actor.add_style_class_name('popup-menu'); ++ this.actor.visible = false; ++ this._boxPointer.bin.set_child(renameContent); ++ ++ this._grabHelper = new GrabHelper.GrabHelper(grid.actor, { actionMode: Shell.ActionMode.POPUP }); ++ this._grabHelper.addActor(this.actor); ++ } ++ ++ _popup() { ++ if (this._isOpen) ++ return; ++ ++ this._isOpen = this._grabHelper.grab({ actor: this.actor, ++ onUngrab: this._popdown.bind(this) }); ++ ++ if (!this._isOpen) { ++ this._grabHelper.ungrab({ actor: this.actor }); ++ return; ++ } ++ ++ this._boxPointer.setPosition(this._source.actor, 0.5); ++ this._boxPointer.show(BoxPointer.PopupAnimation.FADE | ++ BoxPointer.PopupAnimation.SLIDE, ++ null); ++ ++ this.emit('open-state-changed', true); ++ } ++ ++ _popdown() { ++ if (!this._isOpen) ++ return; ++ ++ this._grabHelper.ungrab({ actor: this.actor }); ++ ++ this._boxPointer.hide(BoxPointer.PopupAnimation.FADE | ++ BoxPointer.PopupAnimation.SLIDE); ++ this._isOpen = false; ++ this.emit('open-state-changed', false); ++ } ++ ++ onFileItemRenameClicked(fileItem) { ++ this._source = fileItem; ++ ++ this._renameEntry.text = fileItem.displayName; ++ ++ this._popup(); ++ this._renameEntry.grab_key_focus(); ++ this._renameEntry.navigate_focus(null, Gtk.DirectionType.TAB_FORWARD, false) ++ let allChars = fileItem.displayName.length; ++ this._renameEntry.clutter_text.set_selection(0, allChars); ++ } ++ ++ _onRenameAccepted() { ++ this._popdown(); ++ DBusUtils.NautilusFileOperationsProxy.RenameFileRemote(this._source.file.get_uri(), ++ this._renameEntry.get_text(), ++ (result, error) => { ++ if (error) ++ throw new Error('Error renaming file: ' + error.message); ++ } ++ ); ++ } ++ ++ _onRenameCanceled() { ++ this._popdown(); ++ } ++}; ++Signals.addSignalMethods(RenamePopup.prototype); +diff --git a/extensions/desktop-icons/desktopIconsUtil.js b/extensions/desktop-icons/desktopIconsUtil.js +new file mode 100644 +index 0000000..95f8e8b +--- /dev/null ++++ b/extensions/desktop-icons/desktopIconsUtil.js +@@ -0,0 +1,60 @@ ++/* Desktop Icons GNOME Shell extension ++ * ++ * Copyright (C) 2017 Carlos Soriano ++ * ++ * This program is free software: you can redistribute it and/or modify ++ * it under the terms of the GNU General Public License as published by ++ * the Free Software Foundation, either version 3 of the License, or ++ * (at your option) any later version. ++ * ++ * This program is distributed in the hope that it will be useful, ++ * but WITHOUT ANY WARRANTY; without even the implied warranty of ++ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ++ * GNU General Public License for more details. ++ * ++ * You should have received a copy of the GNU General Public License ++ * along with this program. If not, see . ++ */ ++ ++const Gio = imports.gi.Gio; ++const GLib = imports.gi.GLib; ++const ExtensionUtils = imports.misc.extensionUtils; ++const Me = ExtensionUtils.getCurrentExtension(); ++const Prefs = Me.imports.prefs; ++ ++const TERMINAL_SCHEMA = 'org.gnome.desktop.default-applications.terminal'; ++const EXEC_KEY = 'exec'; ++ ++var DEFAULT_ATTRIBUTES = 'metadata::*,standard::*,access::*,time::modified,unix::mode'; ++ ++function getDesktopDir() { ++ let desktopPath = GLib.get_user_special_dir(GLib.UserDirectory.DIRECTORY_DESKTOP); ++ return Gio.File.new_for_commandline_arg(desktopPath); ++} ++ ++function clamp(value, min, max) { ++ return Math.max(Math.min(value, max), min); ++}; ++ ++function getTerminalCommand(workdir) { ++ let terminalSettings = new Gio.Settings({ schema_id: TERMINAL_SCHEMA }); ++ let exec = terminalSettings.get_string(EXEC_KEY); ++ let command = `${exec} --working-directory=${workdir}`; ++ ++ return command; ++} ++ ++function distanceBetweenPoints(x, y, x2, y2) { ++ return (Math.pow(x - x2, 2) + Math.pow(y - y2, 2)); ++} ++ ++function getExtraFolders() { ++ let extraFolders = new Array(); ++ if (Prefs.settings.get_boolean('show-home')) { ++ extraFolders.push([Gio.File.new_for_commandline_arg(GLib.get_home_dir()), Prefs.FileType.USER_DIRECTORY_HOME]); ++ } ++ if (Prefs.settings.get_boolean('show-trash')) { ++ extraFolders.push([Gio.File.new_for_uri('trash:///'), Prefs.FileType.USER_DIRECTORY_TRASH]); ++ } ++ return extraFolders; ++} +diff --git a/extensions/desktop-icons/desktopManager.js b/extensions/desktop-icons/desktopManager.js +new file mode 100644 +index 0000000..37e0014 +--- /dev/null ++++ b/extensions/desktop-icons/desktopManager.js +@@ -0,0 +1,701 @@ ++/* Desktop Icons GNOME Shell extension ++ * ++ * Copyright (C) 2017 Carlos Soriano ++ * ++ * This program is free software: you can redistribute it and/or modify ++ * it under the terms of the GNU General Public License as published by ++ * the Free Software Foundation, either version 3 of the License, or ++ * (at your option) any later version. ++ * ++ * This program is distributed in the hope that it will be useful, ++ * but WITHOUT ANY WARRANTY; without even the implied warranty of ++ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ++ * GNU General Public License for more details. ++ * ++ * You should have received a copy of the GNU General Public License ++ * along with this program. If not, see . ++ */ ++ ++const Clutter = imports.gi.Clutter; ++const GObject = imports.gi.GObject; ++const Gio = imports.gi.Gio; ++const GLib = imports.gi.GLib; ++const Lang = imports.lang; ++const St = imports.gi.St; ++const Mainloop = imports.mainloop; ++const Meta = imports.gi.Meta; ++ ++const Animation = imports.ui.animation; ++const Background = imports.ui.background; ++const DND = imports.ui.dnd; ++const Main = imports.ui.main; ++const GrabHelper = imports.ui.grabHelper; ++ ++const ExtensionUtils = imports.misc.extensionUtils; ++const Me = ExtensionUtils.getCurrentExtension(); ++const Extension = Me.imports.extension; ++const DesktopGrid = Me.imports.desktopGrid; ++const FileItem = Me.imports.fileItem; ++const Prefs = Me.imports.prefs; ++const DBusUtils = Me.imports.dbusUtils; ++const DesktopIconsUtil = Me.imports.desktopIconsUtil; ++ ++const Clipboard = St.Clipboard.get_default(); ++const CLIPBOARD_TYPE = St.ClipboardType.CLIPBOARD; ++ ++var S_IWOTH = 0x00002; ++ ++function getDpy() { ++ return global.screen || global.display; ++} ++ ++function findMonitorIndexForPos(x, y) { ++ return getDpy().get_monitor_index_for_rect(new Meta.Rectangle({x, y})); ++} ++ ++ ++var DesktopManager = GObject.registerClass({ ++ Properties: { ++ 'writable-by-others': GObject.ParamSpec.boolean( ++ 'writable-by-others', ++ 'WritableByOthers', ++ 'Whether the desktop\'s directory can be written by others (o+w unix permission)', ++ GObject.ParamFlags.READABLE, ++ false ++ ) ++ } ++}, class DesktopManager extends GObject.Object { ++ _init(params) { ++ super._init(params); ++ ++ this._layoutChildrenId = 0; ++ this._deleteChildrenId = 0; ++ this._monitorDesktopDir = null; ++ this._desktopMonitorCancellable = null; ++ this._desktopGrids = {}; ++ this._fileItemHandlers = new Map(); ++ this._fileItems = new Map(); ++ this._dragCancelled = false; ++ this._queryFileInfoCancellable = null; ++ this._unixMode = null; ++ this._writableByOthers = null; ++ ++ this._monitorsChangedId = Main.layoutManager.connect('monitors-changed', () => this._recreateDesktopIcons()); ++ this._rubberBand = new St.Widget({ style_class: 'rubber-band' }); ++ this._rubberBand.hide(); ++ Main.layoutManager.uiGroup.add_child(this._rubberBand); ++ this._grabHelper = new GrabHelper.GrabHelper(global.stage); ++ ++ this._addDesktopIcons(); ++ this._monitorDesktopFolder(); ++ ++ this.settingsId = Prefs.settings.connect('changed', () => this._recreateDesktopIcons()); ++ this.gtkSettingsId = Prefs.gtkSettings.connect('changed', (obj, key) => { ++ if (key == 'show-hidden') ++ this._recreateDesktopIcons(); ++ }); ++ ++ this._selection = new Set(); ++ this._inDrag = false; ++ this._dragXStart = Number.POSITIVE_INFINITY; ++ this._dragYStart = Number.POSITIVE_INFINITY; ++ } ++ ++ startRubberBand(x, y) { ++ this._rubberBandInitialX = x; ++ this._rubberBandInitialY = y; ++ this._updateRubberBand(x, y); ++ this._rubberBand.show(); ++ this._grabHelper.grab({ actor: global.stage }); ++ Extension.lockActivitiesButton = true; ++ this._stageReleaseEventId = global.stage.connect('button-release-event', (actor, event) => { ++ this.endRubberBand(); ++ }); ++ this._rubberBandId = global.stage.connect('motion-event', (actor, event) => { ++ [x, y] = event.get_coords(); ++ this._updateRubberBand(x, y); ++ let x0, y0, x1, y1; ++ if (x >= this._rubberBandInitialX) { ++ x0 = this._rubberBandInitialX; ++ x1 = x; ++ } else { ++ x1 = this._rubberBandInitialX; ++ x0 = x; ++ } ++ if (y >= this._rubberBandInitialY) { ++ y0 = this._rubberBandInitialY; ++ y1 = y; ++ } else { ++ y1 = this._rubberBandInitialY; ++ y0 = y; ++ } ++ for(let [fileUri, fileItem] of this._fileItems) { ++ fileItem.emit('selected', true, ++ fileItem.intersectsWith(x0, y0, x1 - x0, y1 - y0)); ++ } ++ }); ++ } ++ ++ endRubberBand() { ++ this._rubberBand.hide(); ++ Extension.lockActivitiesButton = false; ++ this._grabHelper.ungrab(); ++ global.stage.disconnect(this._rubberBandId); ++ global.stage.disconnect(this._stageReleaseEventId); ++ this._rubberBandId = 0; ++ this._stageReleaseEventId = 0; ++ } ++ ++ _updateRubberBand(currentX, currentY) { ++ let x = this._rubberBandInitialX < currentX ? this._rubberBandInitialX ++ : currentX; ++ let y = this._rubberBandInitialY < currentY ? this._rubberBandInitialY ++ : currentY; ++ let width = Math.abs(this._rubberBandInitialX - currentX); ++ let height = Math.abs(this._rubberBandInitialY - currentY); ++ /* TODO: Convert to gobject.set for 3.30 */ ++ this._rubberBand.set_position(x, y); ++ this._rubberBand.set_size(width, height); ++ } ++ ++ _recreateDesktopIcons() { ++ this._destroyDesktopIcons(); ++ this._addDesktopIcons(); ++ } ++ ++ _addDesktopIcons() { ++ forEachBackgroundManager(bgManager => { ++ let newGrid = new DesktopGrid.DesktopGrid(bgManager); ++ newGrid.actor.connect('destroy', (actor) => { ++ // if a grid loses its actor, remove it from the grid list ++ for(let grid in this._desktopGrids) ++ if (this._desktopGrids[grid].actor == actor) { ++ delete this._desktopGrids[grid]; ++ break; ++ } ++ }); ++ this._desktopGrids[bgManager._monitorIndex] = newGrid; ++ }); ++ ++ this._scanFiles(); ++ } ++ ++ _destroyDesktopIcons() { ++ Object.values(this._desktopGrids).forEach(grid => grid.actor.destroy()); ++ this._desktopGrids = {}; ++ } ++ ++ async _scanFiles() { ++ for (let [fileItem, id] of this._fileItemHandlers) ++ fileItem.disconnect(id); ++ this._fileItemHandlers = new Map(); ++ this._fileItems = new Map(); ++ ++ if (!this._unixMode) { ++ let desktopDir = DesktopIconsUtil.getDesktopDir(); ++ let fileInfo = desktopDir.query_info(Gio.FILE_ATTRIBUTE_UNIX_MODE, ++ Gio.FileQueryInfoFlags.NONE, ++ null); ++ this._unixMode = fileInfo.get_attribute_uint32(Gio.FILE_ATTRIBUTE_UNIX_MODE); ++ this._setWritableByOthers((this._unixMode & S_IWOTH) != 0); ++ } ++ ++ try { ++ for (let [file, info, extra] of await this._enumerateDesktop()) { ++ let fileItem = new FileItem.FileItem(file, info, extra); ++ this._fileItems.set(fileItem.file.get_uri(), fileItem); ++ let id = fileItem.connect('selected', ++ this._onFileItemSelected.bind(this)); ++ ++ this._fileItemHandlers.set(fileItem, id); ++ } ++ } catch (e) { ++ if (!e.matches(Gio.IOErrorEnum, Gio.IOErrorEnum.CANCELLED)) ++ log(`Error loading desktop files ${e.message}`); ++ return; ++ } ++ ++ this.scheduleReLayoutChildren(); ++ } ++ ++ _enumerateDesktop() { ++ return new Promise((resolve, reject) => { ++ if (this._desktopEnumerateCancellable) ++ this._desktopEnumerateCancellable.cancel(); ++ ++ this._desktopEnumerateCancellable = new Gio.Cancellable(); ++ ++ let desktopDir = DesktopIconsUtil.getDesktopDir(); ++ desktopDir.enumerate_children_async(DesktopIconsUtil.DEFAULT_ATTRIBUTES, ++ Gio.FileQueryInfoFlags.NONE, ++ GLib.PRIORITY_DEFAULT, ++ this._desktopEnumerateCancellable, ++ (o, res) => { ++ try { ++ let fileEnum = desktopDir.enumerate_children_finish(res); ++ let resultGenerator = function *() { ++ let info; ++ while ((info = fileEnum.next_file(null))) ++ yield [fileEnum.get_child(info), info, Prefs.FileType.NONE]; ++ for (let [newFolder, extras] of DesktopIconsUtil.getExtraFolders()) { ++ yield [newFolder, newFolder.query_info(DesktopIconsUtil.DEFAULT_ATTRIBUTES, Gio.FileQueryInfoFlags.NONE, this._desktopEnumerateCancellable), extras]; ++ } ++ }.bind(this); ++ resolve(resultGenerator()); ++ } catch (e) { ++ reject(e); ++ } ++ }); ++ }); ++ } ++ ++ _monitorDesktopFolder() { ++ if (this._monitorDesktopDir) { ++ this._monitorDesktopDir.cancel(); ++ this._monitorDesktopDir = null; ++ } ++ ++ let desktopDir = DesktopIconsUtil.getDesktopDir(); ++ this._monitorDesktopDir = desktopDir.monitor_directory(Gio.FileMonitorFlags.WATCH_MOVES, null); ++ this._monitorDesktopDir.set_rate_limit(1000); ++ this._monitorDesktopDir.connect('changed', (obj, file, otherFile, eventType) => this._updateDesktopIfChanged(file, otherFile, eventType)); ++ } ++ ++ checkIfSpecialFilesAreSelected() { ++ for(let fileItem of this._selection) { ++ if (fileItem.isSpecial) ++ return true; ++ } ++ return false; ++ } ++ ++ get writableByOthers() { ++ return this._writableByOthers; ++ } ++ ++ _setWritableByOthers(value) { ++ if (value == this._writableByOthers) ++ return; ++ ++ this._writableByOthers = value ++ this.notify('writable-by-others'); ++ } ++ ++ _updateDesktopIfChanged (file, otherFile, eventType) { ++ let { ++ DELETED, MOVED_IN, MOVED_OUT, CREATED, RENAMED, CHANGES_DONE_HINT, ATTRIBUTE_CHANGED ++ } = Gio.FileMonitorEvent; ++ ++ let fileUri = file.get_uri(); ++ let fileItem = null; ++ if (this._fileItems.has(fileUri)) ++ fileItem = this._fileItems.get(fileUri); ++ switch(eventType) { ++ case RENAMED: ++ this._fileItems.delete(fileUri); ++ this._fileItems.set(otherFile.get_uri(), fileItem); ++ fileItem.onFileRenamed(otherFile); ++ return; ++ case CHANGES_DONE_HINT: ++ case ATTRIBUTE_CHANGED: ++ /* a file changed, rather than the desktop itself */ ++ let desktopDir = DesktopIconsUtil.getDesktopDir(); ++ if (file.get_uri() != desktopDir.get_uri()) ++ return; ++ ++ if (this._queryFileInfoCancellable) ++ this._queryFileInfoCancellable.cancel(); ++ ++ file.query_info_async(Gio.FILE_ATTRIBUTE_UNIX_MODE, ++ Gio.FileQueryInfoFlags.NONE, ++ GLib.PRIORITY_DEFAULT, ++ this._queryFileInfoCancellable, ++ (source, res) => { ++ try { ++ let info = source.query_info_finish(res); ++ this._queryFileInfoCancellable = null; ++ ++ this._unixMode = info.get_attribute_uint32(Gio.FILE_ATTRIBUTE_UNIX_MODE); ++ this._setWritableByOthers((this._unixMode & S_IWOTH) != 0); ++ ++ if (this._writableByOthers) ++ log(`desktop-icons: Desktop is writable by others - will not allow launching any desktop files`); ++ } catch(error) { ++ if (!error.matches(Gio.IOErrorEnum, Gio.IOErrorEnum.CANCELLED)) ++ global.log('Error getting desktop unix mode: ' + error); ++ } ++ }); ++ ++ return; ++ } ++ ++ // Only get a subset of events we are interested in. ++ // Note that CREATED will emit a CHANGES_DONE_HINT ++ if (![DELETED, MOVED_IN, MOVED_OUT, CREATED].includes(eventType)) ++ return; ++ ++ this._recreateDesktopIcons(); ++ } ++ ++ _setupDnD() { ++ this._draggableContainer = new St.Widget({ ++ visible: true, ++ width: 1, ++ height: 1, ++ x: 0, ++ y: 0, ++ style_class: 'draggable' ++ }); ++ this._draggableContainer._delegate = this; ++ this._draggable = DND.makeDraggable(this._draggableContainer, ++ { ++ manualMode: true, ++ dragActorOpacity: 100 ++ }); ++ ++ this._draggable.connect('drag-cancelled', () => this._onDragCancelled()); ++ this._draggable.connect('drag-end', () => this._onDragEnd()); ++ ++ this._draggable._dragActorDropped = event => this._dragActorDropped(event); ++ } ++ ++ dragStart() { ++ if (this._inDrag) { ++ return; ++ } ++ ++ this._setupDnD(); ++ let event = Clutter.get_current_event(); ++ let [x, y] = event.get_coords(); ++ [this._dragXStart, this._dragYStart] = event.get_coords(); ++ this._inDrag = true; ++ ++ for (let fileItem of this._selection) { ++ let clone = new Clutter.Clone({ ++ source: fileItem.actor, ++ reactive: false ++ }); ++ clone.x = fileItem.actor.get_transformed_position()[0]; ++ clone.y = fileItem.actor.get_transformed_position()[1]; ++ this._draggableContainer.add_child(clone); ++ } ++ ++ Main.layoutManager.uiGroup.add_child(this._draggableContainer); ++ this._draggable.startDrag(x, y, global.get_current_time(), event.get_event_sequence()); ++ } ++ ++ _onDragCancelled() { ++ let event = Clutter.get_current_event(); ++ let [x, y] = event.get_coords(); ++ this._dragCancelled = true; ++ } ++ ++ _onDragEnd() { ++ this._inDrag = false; ++ Main.layoutManager.uiGroup.remove_child(this._draggableContainer); ++ } ++ ++ _dragActorDropped(event) { ++ let [dropX, dropY] = event.get_coords(); ++ let target = this._draggable._dragActor.get_stage().get_actor_at_pos(Clutter.PickMode.ALL, ++ dropX, dropY); ++ ++ // We call observers only once per motion with the innermost ++ // target actor. If necessary, the observer can walk the ++ // parent itself. ++ let dropEvent = { ++ dropActor: this._draggable._dragActor, ++ targetActor: target, ++ clutterEvent: event ++ }; ++ for (let dragMonitor of DND.dragMonitors) { ++ let dropFunc = dragMonitor.dragDrop; ++ if (dropFunc) ++ switch (dropFunc(dropEvent)) { ++ case DragDropResult.FAILURE: ++ case DragDropResult.SUCCESS: ++ return true; ++ case DragDropResult.CONTINUE: ++ continue; ++ } ++ } ++ ++ // At this point it is too late to cancel a drag by destroying ++ // the actor, the fate of which is decided by acceptDrop and its ++ // side-effects ++ this._draggable._dragCancellable = false; ++ ++ let destroyActor = false; ++ while (target) { ++ if (target._delegate && target._delegate.acceptDrop) { ++ let [r, targX, targY] = target.transform_stage_point(dropX, dropY); ++ if (target._delegate.acceptDrop(this._draggable.actor._delegate, ++ this._draggable._dragActor, ++ targX, ++ targY, ++ event.get_time())) { ++ // If it accepted the drop without taking the actor, ++ // handle it ourselves. ++ if (this._draggable._dragActor.get_parent() == Main.uiGroup) { ++ if (this._draggable._restoreOnSuccess) { ++ this._draggable._restoreDragActor(event.get_time()); ++ return true; ++ } ++ else { ++ // We need this in order to make sure drag-end is fired ++ destroyActor = true; ++ } ++ } ++ ++ this._draggable._dragInProgress = false; ++ getDpy().set_cursor(Meta.Cursor.DEFAULT); ++ this._draggable.emit('drag-end', event.get_time(), true); ++ if (destroyActor) { ++ this._draggable._dragActor.destroy(); ++ } ++ this._draggable._dragComplete(); ++ ++ return true; ++ } ++ } ++ target = target.get_parent(); ++ } ++ ++ this._draggable._cancelDrag(event.get_time()); ++ ++ return true; ++ } ++ ++ acceptDrop(xEnd, yEnd) { ++ let savedCoordinates = new Map(); ++ let [xDiff, yDiff] = [xEnd - this._dragXStart, yEnd - this._dragYStart]; ++ /* Remove all items before dropping new ones, so we can freely reposition ++ * them. ++ */ ++ for (let item of this._selection) { ++ let [itemX, itemY] = item.actor.get_transformed_position(); ++ let monitorIndex = findMonitorIndexForPos(itemX, itemY); ++ savedCoordinates.set(item, [itemX, itemY]); ++ this._desktopGrids[monitorIndex].removeFileItem(item); ++ } ++ ++ for (let item of this._selection) { ++ let [itemX, itemY] = savedCoordinates.get(item); ++ /* Set the new ideal position where the item drop should happen */ ++ let newFileX = Math.round(xDiff + itemX); ++ let newFileY = Math.round(yDiff + itemY); ++ let monitorIndex = findMonitorIndexForPos(newFileX, newFileY); ++ this._desktopGrids[monitorIndex].addFileItemCloseTo(item, newFileX, newFileY, DesktopGrid.StoredCoordinates.OVERWRITE); ++ } ++ ++ return true; ++ } ++ ++ selectionDropOnFileItem (fileItemDestination) { ++ if (!fileItemDestination.isDirectory) ++ return false; ++ ++ let droppedUris = []; ++ for (let fileItem of this._selection) { ++ if (fileItem.isSpecial) ++ return false; ++ if (fileItemDestination.file.get_uri() == fileItem.file.get_uri()) ++ return false; ++ droppedUris.push(fileItem.file.get_uri()); ++ } ++ ++ if (droppedUris.length == 0) ++ return true; ++ ++ DBusUtils.NautilusFileOperationsProxy.MoveURIsRemote(droppedUris, ++ fileItemDestination.file.get_uri(), ++ (result, error) => { ++ if (error) ++ throw new Error('Error moving files: ' + error.message); ++ } ++ ); ++ for (let fileItem of this._selection) { ++ fileItem.state = FileItem.State.GONE; ++ } ++ ++ this._recreateDesktopIcons(); ++ ++ return true; ++ } ++ ++ _resetGridsAndScheduleLayout() { ++ this._deleteChildrenId = 0; ++ ++ Object.values(this._desktopGrids).forEach((grid) => grid.reset()); ++ ++ this._layoutChildrenId = GLib.idle_add(GLib.PRIORITY_LOW, () => this._layoutChildren()); ++ ++ return GLib.SOURCE_REMOVE; ++ } ++ ++ scheduleReLayoutChildren() { ++ if (this._deleteChildrenId != 0) ++ return; ++ ++ if (this._layoutChildrenId != 0) { ++ GLib.source_remove(this._layoutChildrenId); ++ this._layoutChildrenId = 0; ++ } ++ ++ ++ this._deleteChildrenId = GLib.idle_add(GLib.PRIORITY_LOW, () => this._resetGridsAndScheduleLayout()); ++ } ++ ++ _addFileItemCloseTo(item) { ++ let [x, y] = (item.savedCoordinates == null) ? [0, 0] : item.savedCoordinates; ++ let monitorIndex = findMonitorIndexForPos(x, y); ++ let desktopGrid = this._desktopGrids[monitorIndex]; ++ try { ++ desktopGrid.addFileItemCloseTo(item, x, y, DesktopGrid.StoredCoordinates.PRESERVE); ++ } catch (e) { ++ log(`Error adding children to desktop: ${e.message}`); ++ } ++ } ++ ++ _layoutChildren() { ++ let showHidden = Prefs.gtkSettings.get_boolean('show-hidden'); ++ /* ++ * Paint the icons in two passes: ++ * * first pass paints those that have their coordinates defined in the metadata ++ * * second pass paints those new files that still don't have their definitive coordinates ++ */ ++ for (let [fileUri, fileItem] of this._fileItems) { ++ if (fileItem.savedCoordinates == null) ++ continue; ++ if (fileItem.state != FileItem.State.NORMAL) ++ continue; ++ if (!showHidden && fileItem.isHidden) ++ continue; ++ this._addFileItemCloseTo(fileItem); ++ } ++ ++ for (let [fileUri, fileItem] of this._fileItems) { ++ if (fileItem.savedCoordinates !== null) ++ continue; ++ if (fileItem.state != FileItem.State.NORMAL) ++ continue; ++ if (!showHidden && fileItem.isHidden) ++ continue; ++ this._addFileItemCloseTo(fileItem); ++ } ++ ++ this._layoutChildrenId = 0; ++ return GLib.SOURCE_REMOVE; ++ } ++ ++ doRename() { ++ if (this._selection.size != 1) ++ return; ++ ++ let item = [...this._selection][0]; ++ if (item.canRename()) ++ item.doRename(); ++ } ++ ++ doOpen() { ++ for (let fileItem of this._selection) ++ fileItem.doOpen(); ++ } ++ ++ doTrash() { ++ DBusUtils.NautilusFileOperationsProxy.TrashFilesRemote([...this._selection].map((x) => { return x.file.get_uri(); }), ++ (source, error) => { ++ if (error) ++ throw new Error('Error trashing files on the desktop: ' + error.message); ++ } ++ ); ++ } ++ ++ doEmptyTrash() { ++ DBusUtils.NautilusFileOperationsProxy.EmptyTrashRemote( (source, error) => { ++ if (error) ++ throw new Error('Error trashing files on the desktop: ' + error.message); ++ }); ++ } ++ ++ _onFileItemSelected(fileItem, keepCurrentSelection, addToSelection) { ++ ++ if (!keepCurrentSelection && !this._inDrag) ++ this.clearSelection(); ++ ++ if (addToSelection) ++ this._selection.add(fileItem); ++ else ++ this._selection.delete(fileItem); ++ ++ for(let [fileUri, fileItem] of this._fileItems) ++ fileItem.isSelected = this._selection.has(fileItem); ++ } ++ ++ clearSelection() { ++ for (let [fileUri, fileItem] of this._fileItems) ++ fileItem.isSelected = false; ++ this._selection = new Set(); ++ } ++ ++ _getClipboardText(isCopy) { ++ let action = isCopy ? 'copy' : 'cut'; ++ let text = `x-special/nautilus-clipboard\n${action}\n${ ++ [...this._selection].map(s => s.file.get_uri()).join('\n') ++ }\n`; ++ ++ return text; ++ } ++ ++ doCopy() { ++ Clipboard.set_text(CLIPBOARD_TYPE, this._getClipboardText(true)); ++ } ++ ++ doCut() { ++ Clipboard.set_text(CLIPBOARD_TYPE, this._getClipboardText(false)); ++ } ++ ++ destroy() { ++ if (this._monitorDesktopDir) ++ this._monitorDesktopDir.cancel(); ++ this._monitorDesktopDir = null; ++ ++ if (this.settingsId) ++ Prefs.settings.disconnect(this.settingsId); ++ this.settingsId = 0; ++ if (this.gtkSettingsId) ++ Prefs.gtkSettings.disconnect(this.gtkSettingsId); ++ this.gtkSettingsId = 0; ++ ++ if (this._layoutChildrenId) ++ GLib.source_remove(this._layoutChildrenId); ++ this._layoutChildrenId = 0; ++ ++ if (this._deleteChildrenId) ++ GLib.source_remove(this._deleteChildrenId); ++ this._deleteChildrenId = 0; ++ ++ if (this._monitorsChangedId) ++ Main.layoutManager.disconnect(this._monitorsChangedId); ++ this._monitorsChangedId = 0; ++ if (this._stageReleaseEventId) ++ global.stage.disconnect(this._stageReleaseEventId); ++ this._stageReleaseEventId = 0; ++ ++ if (this._rubberBandId) ++ global.stage.disconnect(this._rubberBandId); ++ this._rubberBandId = 0; ++ ++ this._rubberBand.destroy(); ++ ++ if (this._queryFileInfoCancellable) ++ this._queryFileInfoCancellable.cancel(); ++ ++ Object.values(this._desktopGrids).forEach(grid => grid.actor.destroy()); ++ this._desktopGrids = {} ++ } ++}); ++ ++function forEachBackgroundManager(func) { ++ Main.layoutManager._bgManagers.forEach(func); ++} +diff --git a/extensions/desktop-icons/extension.js b/extensions/desktop-icons/extension.js +new file mode 100644 +index 0000000..e5ca440 +--- /dev/null ++++ b/extensions/desktop-icons/extension.js +@@ -0,0 +1,71 @@ ++/* Desktop Icons GNOME Shell extension ++ * ++ * Copyright (C) 2017 Carlos Soriano ++ * ++ * This program is free software: you can redistribute it and/or modify ++ * it under the terms of the GNU General Public License as published by ++ * the Free Software Foundation, either version 3 of the License, or ++ * (at your option) any later version. ++ * ++ * This program is distributed in the hope that it will be useful, ++ * but WITHOUT ANY WARRANTY; without even the implied warranty of ++ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ++ * GNU General Public License for more details. ++ * ++ * You should have received a copy of the GNU General Public License ++ * along with this program. If not, see . ++ */ ++ ++const Main = imports.ui.main; ++ ++const ExtensionUtils = imports.misc.extensionUtils; ++const Me = ExtensionUtils.getCurrentExtension(); ++const Prefs = Me.imports.prefs; ++const { DesktopManager } = Me.imports.desktopManager; ++const DBusUtils = Me.imports.dbusUtils; ++ ++var desktopManager = null; ++var addBackgroundMenuOrig = null; ++var _startupPreparedId; ++var lockActivitiesButton = false; ++ ++var oldShouldToggleByCornerOrButtonFunction = null; ++ ++function init() { ++ addBackgroundMenuOrig = Main.layoutManager._addBackgroundMenu; ++ ++ Prefs.initTranslations(); ++} ++ ++function newShouldToggleByCornerOrButton() { ++ if (lockActivitiesButton) ++ return false; ++ else ++ return oldShouldToggleByCornerOrButtonFunction.bind(Main.overview); ++} ++ ++function enable() { ++ // register a new function to allow to lock the Activities button when doing a rubberband selection ++ oldShouldToggleByCornerOrButtonFunction = Main.overview.shouldToggleByCornerOrButton; ++ Main.overview.shouldToggleByCornerOrButton = newShouldToggleByCornerOrButton; ++ // wait until the startup process has ended ++ if (Main.layoutManager._startingUp) ++ _startupPreparedId = Main.layoutManager.connect('startup-complete', () => innerEnable(true)); ++ else ++ innerEnable(false); ++} ++ ++function innerEnable(disconnectSignal) { ++ if (disconnectSignal) ++ Main.layoutManager.disconnect(_startupPreparedId); ++ DBusUtils.init(); ++ Prefs.init(); ++ Main.layoutManager._addBackgroundMenu = function() {}; ++ desktopManager = new DesktopManager(); ++} ++ ++function disable() { ++ desktopManager.destroy(); ++ Main.layoutManager._addBackgroundMenu = addBackgroundMenuOrig; ++ Main.overview.shouldToggleByCornerOrButton = oldShouldToggleByCornerOrButtonFunction; ++} +\ No newline at end of file +diff --git a/extensions/desktop-icons/fileItem.js b/extensions/desktop-icons/fileItem.js +new file mode 100644 +index 0000000..75c1d23 +--- /dev/null ++++ b/extensions/desktop-icons/fileItem.js +@@ -0,0 +1,771 @@ ++/* Desktop Icons GNOME Shell extension ++ * ++ * Copyright (C) 2017 Carlos Soriano ++ * ++ * This program is free software: you can redistribute it and/or modify ++ * it under the terms of the GNU General Public License as published by ++ * the Free Software Foundation, either version 3 of the License, or ++ * (at your option) any later version. ++ * ++ * This program is distributed in the hope that it will be useful, ++ * but WITHOUT ANY WARRANTY; without even the implied warranty of ++ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ++ * GNU General Public License for more details. ++ * ++ * You should have received a copy of the GNU General Public License ++ * along with this program. If not, see . ++ */ ++ ++const Clutter = imports.gi.Clutter; ++const Gio = imports.gi.Gio; ++const GLib = imports.gi.GLib; ++const Lang = imports.lang; ++const St = imports.gi.St; ++const Pango = imports.gi.Pango; ++const Meta = imports.gi.Meta; ++const GdkPixbuf = imports.gi.GdkPixbuf; ++const Cogl = imports.gi.Cogl; ++const GnomeDesktop = imports.gi.GnomeDesktop; ++ ++const Mainloop = imports.mainloop; ++const Signals = imports.signals; ++ ++const Background = imports.ui.background; ++const Main = imports.ui.main; ++const PopupMenu = imports.ui.popupMenu; ++const Util = imports.misc.util; ++ ++const ExtensionUtils = imports.misc.extensionUtils; ++const Me = ExtensionUtils.getCurrentExtension(); ++const Extension = Me.imports.extension; ++const Prefs = Me.imports.prefs; ++const DBusUtils = Me.imports.dbusUtils; ++const DesktopIconsUtil = Me.imports.desktopIconsUtil; ++ ++const Gettext = imports.gettext.domain('desktop-icons'); ++ ++const _ = Gettext.gettext; ++ ++const DRAG_TRESHOLD = 8; ++ ++var S_IXUSR = 0o00100; ++var S_IWOTH = 0o00002; ++ ++var State = { ++ NORMAL: 0, ++ GONE: 1, ++}; ++ ++var FileItem = class { ++ ++ constructor(file, fileInfo, fileExtra) { ++ this._fileExtra = fileExtra; ++ this._loadThumbnailDataCancellable = null; ++ this._thumbnailScriptWatch = 0; ++ this._setMetadataCancellable = null; ++ this._queryFileInfoCancellable = null; ++ this._isSpecial = this._fileExtra != Prefs.FileType.NONE; ++ ++ this._file = file; ++ ++ this._savedCoordinates = null; ++ let savedCoordinates = fileInfo.get_attribute_as_string('metadata::nautilus-icon-position'); ++ if (savedCoordinates != null) ++ this._savedCoordinates = savedCoordinates.split(',').map(x => Number(x)); ++ ++ this._state = State.NORMAL; ++ ++ this.actor = new St.Bin({ visible: true }); ++ this.actor.set_fill(true, true); ++ let scaleFactor = St.ThemeContext.get_for_stage(global.stage).scale_factor; ++ this.actor.set_height(Prefs.get_desired_height(scaleFactor)); ++ this.actor.set_width(Prefs.get_desired_width(scaleFactor)); ++ this.actor._delegate = this; ++ this.actor.connect('destroy', () => this._onDestroy()); ++ ++ this._container = new St.BoxLayout({ reactive: true, ++ track_hover: true, ++ can_focus: true, ++ style_class: 'file-item', ++ x_expand: true, ++ y_expand: true, ++ x_align: Clutter.ActorAlign.FILL, ++ vertical: true }); ++ this.actor.set_child(this._container); ++ this._icon = new St.Bin(); ++ this._icon.set_height(Prefs.get_icon_size() * scaleFactor); ++ ++ this._iconContainer = new St.Bin({ visible: true }); ++ this._iconContainer.child = this._icon; ++ this._container.add_child(this._iconContainer); ++ ++ this._label = new St.Label({ ++ style_class: 'name-label' ++ }); ++ ++ this._container.add_child(this._label); ++ let clutterText = this._label.get_clutter_text(); ++ /* TODO: Convert to gobject.set for 3.30 */ ++ clutterText.set_line_wrap(true); ++ clutterText.set_line_wrap_mode(Pango.WrapMode.WORD_CHAR); ++ clutterText.set_ellipsize(Pango.EllipsizeMode.END); ++ ++ this._container.connect('button-press-event', (actor, event) => this._onPressButton(actor, event)); ++ this._container.connect('motion-event', (actor, event) => this._onMotion(actor, event)); ++ this._container.connect('leave-event', (actor, event) => this._onLeave(actor, event)); ++ this._container.connect('button-release-event', (actor, event) => this._onReleaseButton(actor, event)); ++ ++ /* Set the metadata and update relevant UI */ ++ this._updateMetadataFromFileInfo(fileInfo); ++ ++ if (this._isDesktopFile) { ++ /* watch for the executable bit being removed or added */ ++ this._monitorDesktopFile = this._file.monitor_file(Gio.FileMonitorFlags.NONE, null); ++ this._monitorDesktopFileId = this._monitorDesktopFile.connect('changed', ++ (obj, file, otherFile, eventType) => { ++ switch(eventType) { ++ case Gio.FileMonitorEvent.ATTRIBUTE_CHANGED: ++ this._refreshMetadataAsync(true); ++ break; ++ } ++ }); ++ } ++ ++ this._createMenu(); ++ this._updateIcon(); ++ ++ this._isSelected = false; ++ this._primaryButtonPressed = false; ++ if (this._attributeCanExecute && !this._isDesktopFile) ++ this._execLine = this.file.get_path(); ++ if (fileExtra == Prefs.FileType.USER_DIRECTORY_TRASH) { ++ // if this icon is the trash, monitor the state of the directory to update the icon ++ this._trashChanged = false; ++ this._trashInitializeCancellable = null; ++ this._scheduleTrashRefreshId = 0; ++ this._monitorTrashDir = this._file.monitor_directory(Gio.FileMonitorFlags.WATCH_MOVES, null); ++ this._monitorTrashId = this._monitorTrashDir.connect('changed', (obj, file, otherFile, eventType) => { ++ switch(eventType) { ++ case Gio.FileMonitorEvent.DELETED: ++ case Gio.FileMonitorEvent.MOVED_OUT: ++ case Gio.FileMonitorEvent.CREATED: ++ case Gio.FileMonitorEvent.MOVED_IN: ++ if (this._queryTrashInfoCancellable || this._scheduleTrashRefreshId) { ++ if (this._scheduleTrashRefreshId) ++ GLib.source_remove(this._scheduleTrashRefreshId); ++ this._scheduleTrashRefreshId = Mainloop.timeout_add(200, () => this._refreshTrashIcon()); ++ } else { ++ this._refreshTrashIcon(); ++ } ++ break; ++ } ++ }); ++ } ++ ++ Extension.desktopManager.connect('notify::writable-by-others', () => { ++ if (!this._isDesktopFile) ++ return; ++ this._refreshMetadataAsync(true); ++ }); ++ } ++ ++ _onDestroy() { ++ /* Regular file data */ ++ if (this._setMetadataCancellable) ++ this._setMetadataCancellable.cancel(); ++ if (this._queryFileInfoCancellable) ++ this._queryFileInfoCancellable.cancel(); ++ ++ /* Thumbnailing */ ++ if (this._thumbnailScriptWatch) ++ GLib.source_remove(this._thumbnailScriptWatch); ++ if (this._loadThumbnailDataCancellable) ++ this._loadThumbnailDataCancellable.cancel(); ++ ++ /* Desktop file */ ++ if (this._monitorDesktopFileId) ++ this._monitorDesktopFile.disconnect(this._monitorDesktopFileId); ++ ++ /* Trash */ ++ if (this._monitorTrashDir) ++ this._monitorTrashDir.disconnect(this._monitorTrashId); ++ if (this._queryTrashInfoCancellable) ++ this._queryTrashInfoCancellable.cancel(); ++ if (this._scheduleTrashRefreshId) ++ GLib.source_remove(this._scheduleTrashRefreshId); ++ } ++ ++ _refreshMetadataAsync(rebuild) { ++ if (this._queryFileInfoCancellable) ++ this._queryFileInfoCancellable.cancel(); ++ this._queryFileInfoCancellable = new Gio.Cancellable(); ++ this._file.query_info_async(DesktopIconsUtil.DEFAULT_ATTRIBUTES, ++ Gio.FileQueryInfoFlags.NONE, ++ GLib.PRIORITY_DEFAULT, ++ this._queryFileInfoCancellable, ++ (source, res) => { ++ try { ++ let newFileInfo = source.query_info_finish(res); ++ this._queryFileInfoCancellable = null; ++ this._updateMetadataFromFileInfo(newFileInfo); ++ if (rebuild) { ++ this._createMenu(); ++ this._updateIcon(); ++ } ++ } catch(error) { ++ if (!error.matches(Gio.IOErrorEnum, Gio.IOErrorEnum.CANCELLED)) ++ global.log("Error getting the file info: " + error); ++ } ++ }); ++ } ++ ++ _updateMetadataFromFileInfo(fileInfo) { ++ this._fileInfo = fileInfo; ++ ++ let oldLabelText = this._label.text; ++ ++ this._displayName = fileInfo.get_attribute_as_string('standard::display-name'); ++ this._attributeCanExecute = fileInfo.get_attribute_boolean('access::can-execute'); ++ this._unixmode = fileInfo.get_attribute_uint32('unix::mode') ++ this._writableByOthers = (this._unixmode & S_IWOTH) != 0; ++ this._trusted = fileInfo.get_attribute_as_string('metadata::trusted') == 'true'; ++ this._attributeContentType = fileInfo.get_content_type(); ++ this._isDesktopFile = this._attributeContentType == 'application/x-desktop'; ++ ++ if (this._isDesktopFile && this._writableByOthers) ++ log(`desktop-icons: File ${this._displayName} is writable by others - will not allow launching`); ++ ++ if (this._isDesktopFile) { ++ this._desktopFile = Gio.DesktopAppInfo.new_from_filename(this._file.get_path()); ++ if (!this._desktopFile) { ++ log(`Couldn’t parse ${this._displayName} as a desktop file, will treat it as a regular file.`); ++ this._isDesktopFile = false; ++ } ++ } ++ ++ if (this.displayName != oldLabelText) { ++ this._label.text = this.displayName; ++ } ++ ++ this._fileType = fileInfo.get_file_type(); ++ this._isDirectory = this._fileType == Gio.FileType.DIRECTORY; ++ this._isSpecial = this._fileExtra != Prefs.FileType.NONE; ++ this._attributeHidden = fileInfo.get_is_hidden(); ++ this._isSymlink = fileInfo.get_is_symlink(); ++ this._modifiedTime = this._fileInfo.get_attribute_uint64("time::modified"); ++ /* ++ * This is a glib trick to detect broken symlinks. If a file is a symlink, the filetype ++ * points to the final file, unless it is broken; thus if the file type is SYMBOLIC_LINK, ++ * it must be a broken link. ++ * https://developer.gnome.org/gio/stable/GFile.html#g-file-query-info ++ */ ++ this._isBrokenSymlink = this._isSymlink && this._fileType == Gio.FileType.SYMBOLIC_LINK ++ } ++ ++ onFileRenamed(file) { ++ this._file = file; ++ this._refreshMetadataAsync(false); ++ } ++ ++ _updateIcon() { ++ if (this._fileExtra == Prefs.FileType.USER_DIRECTORY_TRASH) { ++ this._icon.child = this._createEmblemedStIcon(this._fileInfo.get_icon(), null); ++ return; ++ } ++ ++ let thumbnailFactory = GnomeDesktop.DesktopThumbnailFactory.new(GnomeDesktop.DesktopThumbnailSize.LARGE); ++ if (thumbnailFactory.can_thumbnail(this._file.get_uri(), ++ this._attributeContentType, ++ this._modifiedTime)) { ++ let thumbnail = thumbnailFactory.lookup(this._file.get_uri(), this._modifiedTime); ++ if (thumbnail == null) { ++ if (!thumbnailFactory.has_valid_failed_thumbnail(this._file.get_uri(), ++ this._modifiedTime)) { ++ let argv = []; ++ argv.push(GLib.build_filenamev([ExtensionUtils.getCurrentExtension().path, ++ 'createThumbnail.js'])); ++ argv.push(this._file.get_path()); ++ let [success, pid] = GLib.spawn_async(null, argv, null, ++ GLib.SpawnFlags.SEARCH_PATH | GLib.SpawnFlags.DO_NOT_REAP_CHILD, null); ++ if (this._thumbnailScriptWatch) ++ GLib.source_remove(this._thumbnailScriptWatch); ++ this._thumbnailScriptWatch = GLib.child_watch_add(GLib.PRIORITY_DEFAULT, ++ pid, ++ (pid, exitCode) => { ++ if (exitCode == 0) ++ this._updateIcon(); ++ else ++ global.log('Failed to generate thumbnail for ' + this._filePath); ++ GLib.spawn_close_pid(pid); ++ return false; ++ } ++ ); ++ } ++ } else { ++ if (this._loadThumbnailDataCancellable) ++ this._loadThumbnailDataCancellable.cancel(); ++ this._loadThumbnailDataCancellable = new Gio.Cancellable(); ++ let thumbnailFile = Gio.File.new_for_path(thumbnail); ++ thumbnailFile.load_bytes_async(this._loadThumbnailDataCancellable, ++ (obj, res) => { ++ try { ++ let [thumbnailData, etag_out] = obj.load_bytes_finish(res); ++ let thumbnailStream = Gio.MemoryInputStream.new_from_bytes(thumbnailData); ++ let thumbnailPixbuf = GdkPixbuf.Pixbuf.new_from_stream(thumbnailStream, null); ++ ++ if (thumbnailPixbuf != null) { ++ let scaleFactor = St.ThemeContext.get_for_stage(global.stage).scale_factor; ++ let thumbnailImage = new Clutter.Image(); ++ thumbnailImage.set_data(thumbnailPixbuf.get_pixels(), ++ thumbnailPixbuf.has_alpha ? Cogl.PixelFormat.RGBA_8888 : Cogl.PixelFormat.RGB_888, ++ thumbnailPixbuf.width, ++ thumbnailPixbuf.height, ++ thumbnailPixbuf.rowstride ++ ); ++ let icon = new Clutter.Actor(); ++ icon.set_content(thumbnailImage); ++ let width = Prefs.get_desired_width(scaleFactor); ++ let height = Prefs.get_icon_size() * scaleFactor; ++ let aspectRatio = thumbnailPixbuf.width / thumbnailPixbuf.height; ++ if ((width / height) > aspectRatio) ++ icon.set_size(height * aspectRatio, height); ++ else ++ icon.set_size(width, width / aspectRatio); ++ this._icon.child = icon; ++ } ++ } catch (error) { ++ if (!error.matches(Gio.IOErrorEnum, Gio.IOErrorEnum.CANCELLED)) { ++ global.log('Error while loading thumbnail: ' + error); ++ this._icon.child = this._createEmblemedStIcon(this._fileInfo.get_icon(), null); ++ } ++ } ++ } ++ ); ++ } ++ } ++ ++ if (this._isBrokenSymlink) { ++ this._icon.child = this._createEmblemedStIcon(null, 'text-x-generic'); ++ } else { ++ if (this.trustedDesktopFile && this._desktopFile.has_key('Icon')) ++ this._icon.child = this._createEmblemedStIcon(null, this._desktopFile.get_string('Icon')); ++ else ++ this._icon.child = this._createEmblemedStIcon(this._fileInfo.get_icon(), null); ++ } ++ } ++ ++ _refreshTrashIcon() { ++ if (this._queryTrashInfoCancellable) ++ this._queryTrashInfoCancellable.cancel(); ++ this._queryTrashInfoCancellable = new Gio.Cancellable(); ++ ++ this._file.query_info_async(DesktopIconsUtil.DEFAULT_ATTRIBUTES, ++ Gio.FileQueryInfoFlags.NONE, ++ GLib.PRIORITY_DEFAULT, ++ this._queryTrashInfoCancellable, ++ (source, res) => { ++ try { ++ this._fileInfo = source.query_info_finish(res); ++ this._queryTrashInfoCancellable = null; ++ this._updateIcon(); ++ } catch(error) { ++ if (!error.matches(Gio.IOErrorEnum, Gio.IOErrorEnum.CANCELLED)) ++ global.log('Error getting the number of files in the trash: ' + error); ++ } ++ }); ++ ++ this._scheduleTrashRefreshId = 0; ++ return false; ++ } ++ ++ get file() { ++ return this._file; ++ } ++ ++ get isHidden() { ++ return this._fileInfo.get_is_hidden(); ++ } ++ ++ _createEmblemedStIcon(icon, iconName) { ++ if (icon == null) { ++ if (GLib.path_is_absolute(iconName)) { ++ let iconFile = Gio.File.new_for_commandline_arg(iconName); ++ icon = new Gio.FileIcon({ file: iconFile }); ++ } else { ++ icon = Gio.ThemedIcon.new_with_default_fallbacks(iconName); ++ } ++ } ++ let itemIcon = Gio.EmblemedIcon.new(icon, null); ++ ++ if (this._isSymlink) { ++ if (this._isBrokenSymlink) ++ itemIcon.add_emblem(Gio.Emblem.new(Gio.ThemedIcon.new('emblem-unreadable'))); ++ else ++ itemIcon.add_emblem(Gio.Emblem.new(Gio.ThemedIcon.new('emblem-symbolic-link'))); ++ } else if (this.trustedDesktopFile) { ++ itemIcon.add_emblem(Gio.Emblem.new(Gio.ThemedIcon.new('emblem-symbolic-link'))); ++ } ++ ++ return new St.Icon({ gicon: itemIcon, ++ icon_size: Prefs.get_icon_size() ++ }); ++ } ++ ++ doRename() { ++ if (!this.canRename()) { ++ log (`Error: ${this.file.get_uri()} cannot be renamed`); ++ return; ++ } ++ ++ this.emit('rename-clicked'); ++ } ++ ++ doOpen() { ++ if (this._isBrokenSymlink) { ++ log(`Error: Can’t open ${this.file.get_uri()} because it is a broken symlink.`); ++ return; ++ } ++ ++ if (this.trustedDesktopFile) { ++ this._desktopFile.launch_uris_as_manager([], null, GLib.SpawnFlags.SEARCH_PATH, null, null); ++ return; ++ } ++ ++ if (this._attributeCanExecute && !this._isDirectory && !this._isDesktopFile) { ++ if (this._execLine) ++ Util.spawnCommandLine(this._execLine); ++ return; ++ } ++ ++ Gio.AppInfo.launch_default_for_uri_async(this.file.get_uri(), ++ null, null, ++ (source, res) => { ++ try { ++ Gio.AppInfo.launch_default_for_uri_finish(res); ++ } catch (e) { ++ log('Error opening file ' + this.file.get_uri() + ': ' + e.message); ++ } ++ } ++ ); ++ } ++ ++ _onCopyClicked() { ++ Extension.desktopManager.doCopy(); ++ } ++ ++ _onCutClicked() { ++ Extension.desktopManager.doCut(); ++ } ++ ++ _onShowInFilesClicked() { ++ ++ DBusUtils.FreeDesktopFileManagerProxy.ShowItemsRemote([this.file.get_uri()], '', ++ (result, error) => { ++ if (error) ++ log('Error showing file on desktop: ' + error.message); ++ } ++ ); ++ } ++ ++ _onPropertiesClicked() { ++ ++ DBusUtils.FreeDesktopFileManagerProxy.ShowItemPropertiesRemote([this.file.get_uri()], '', ++ (result, error) => { ++ if (error) ++ log('Error showing properties: ' + error.message); ++ } ++ ); ++ } ++ ++ _onMoveToTrashClicked() { ++ Extension.desktopManager.doTrash(); ++ } ++ ++ _onEmptyTrashClicked() { ++ Extension.desktopManager.doEmptyTrash(); ++ } ++ ++ get _allowLaunchingText() { ++ if (this.trustedDesktopFile) ++ return _("Don’t Allow Launching"); ++ ++ return _("Allow Launching"); ++ } ++ ++ get metadataTrusted() { ++ return this._trusted; ++ } ++ ++ set metadataTrusted(value) { ++ this._trusted = value; ++ ++ let info = new Gio.FileInfo(); ++ info.set_attribute_string('metadata::trusted', ++ value ? 'true' : 'false'); ++ this._file.set_attributes_async(info, ++ Gio.FileQueryInfoFlags.NONE, ++ GLib.PRIORITY_LOW, ++ null, ++ (source, res) => { ++ try { ++ source.set_attributes_finish(res); ++ this._refreshMetadataAsync(true); ++ } catch(e) { ++ log(`Failed to set metadata::trusted: ${e.message}`); ++ } ++ }); ++ } ++ ++ _onAllowDisallowLaunchingClicked() { ++ this.metadataTrusted = !this.trustedDesktopFile; ++ ++ /* ++ * we're marking as trusted, make the file executable too. note that we ++ * do not ever remove the executable bit, since we don't know who set ++ * it. ++ */ ++ if (this.metadataTrusted && !this._attributeCanExecute) { ++ let info = new Gio.FileInfo(); ++ let newUnixMode = this._unixmode | S_IXUSR; ++ info.set_attribute_uint32(Gio.FILE_ATTRIBUTE_UNIX_MODE, newUnixMode); ++ this._file.set_attributes_async(info, ++ Gio.FileQueryInfoFlags.NONE, ++ GLib.PRIORITY_LOW, ++ null, ++ (source, res) => { ++ try { ++ source.set_attributes_finish (res); ++ } catch(e) { ++ log(`Failed to set unix mode: ${e.message}`); ++ } ++ }); ++ this._file.set_attributes_from_info(info, ++ Gio.FileQueryInfoFlags.NONE, ++ GLib.PRIORITY_LOW, ++ null); ++ } ++ } ++ ++ canRename() { ++ return !this.trustedDesktopFile && this._fileExtra == Prefs.FileType.NONE; ++ } ++ ++ _createMenu() { ++ this._menuManager = new PopupMenu.PopupMenuManager({ actor: this.actor }); ++ let side = St.Side.LEFT; ++ if (Clutter.get_default_text_direction() == Clutter.TextDirection.RTL) ++ side = St.Side.RIGHT; ++ this._menu = new PopupMenu.PopupMenu(this.actor, 0.5, side); ++ this._menu.addAction(_('Open'), () => this.doOpen()); ++ switch (this._fileExtra) { ++ case Prefs.FileType.NONE: ++ this._actionCut = this._menu.addAction(_('Cut'), () => this._onCutClicked()); ++ this._actionCopy = this._menu.addAction(_('Copy'), () => this._onCopyClicked()); ++ if (this.canRename()) ++ this._menu.addAction(_('Rename…'), () => this.doRename()); ++ this._actionTrash = this._menu.addAction(_('Move to Trash'), () => this._onMoveToTrashClicked()); ++ if (this._isDesktopFile && !Extension.desktopManager.writableByOthers && !this._writableByOthers) { ++ this._menu.addMenuItem(new PopupMenu.PopupSeparatorMenuItem()); ++ this._allowLaunchingMenuItem = this._menu.addAction(this._allowLaunchingText, ++ () => this._onAllowDisallowLaunchingClicked()); ++ ++ } ++ break; ++ case Prefs.FileType.USER_DIRECTORY_TRASH: ++ this._menu.addMenuItem(new PopupMenu.PopupSeparatorMenuItem()); ++ this._menu.addAction(_('Empty Trash'), () => this._onEmptyTrashClicked()); ++ break; ++ default: ++ break; ++ } ++ this._menu.addMenuItem(new PopupMenu.PopupSeparatorMenuItem()); ++ this._menu.addAction(_('Properties'), () => this._onPropertiesClicked()); ++ this._menu.addMenuItem(new PopupMenu.PopupSeparatorMenuItem()); ++ this._menu.addAction(_('Show in Files'), () => this._onShowInFilesClicked()); ++ if (this._isDirectory && this.file.get_path() != null) ++ this._actionOpenInTerminal = this._menu.addAction(_('Open in Terminal'), () => this._onOpenTerminalClicked()); ++ ++ this._menuManager.addMenu(this._menu); ++ ++ Main.layoutManager.uiGroup.add_child(this._menu.actor); ++ this._menu.actor.hide(); ++ } ++ ++ _onOpenTerminalClicked () { ++ let command = DesktopIconsUtil.getTerminalCommand(this.file.get_path()); ++ Util.spawnCommandLine(command); ++ } ++ ++ _onPressButton(actor, event) { ++ let button = event.get_button(); ++ if (button == 3) { ++ if (!this.isSelected) ++ this.emit('selected', false, true); ++ this._menu.toggle(); ++ let specialFilesSelected = Extension.desktopManager.checkIfSpecialFilesAreSelected(); ++ if (this._actionCut) ++ this._actionCut.setSensitive(!specialFilesSelected); ++ if (this._actionCopy) ++ this._actionCopy.setSensitive(!specialFilesSelected); ++ if (this._actionTrash) ++ this._actionTrash.setSensitive(!specialFilesSelected); ++ return Clutter.EVENT_STOP; ++ } else if (button == 1) { ++ if (event.get_click_count() == 1) { ++ let [x, y] = event.get_coords(); ++ this._primaryButtonPressed = true; ++ this._buttonPressInitialX = x; ++ this._buttonPressInitialY = y; ++ let shiftPressed = !!(event.get_state() & Clutter.ModifierType.SHIFT_MASK); ++ let controlPressed = !!(event.get_state() & Clutter.ModifierType.CONTROL_MASK); ++ if (!this.isSelected) { ++ this.emit('selected', shiftPressed || controlPressed, true); ++ } ++ } ++ return Clutter.EVENT_STOP; ++ } ++ ++ return Clutter.EVENT_PROPAGATE; ++ } ++ ++ _onLeave(actor, event) { ++ this._primaryButtonPressed = false; ++ } ++ ++ _onMotion(actor, event) { ++ let [x, y] = event.get_coords(); ++ if (this._primaryButtonPressed) { ++ let xDiff = x - this._buttonPressInitialX; ++ let yDiff = y - this._buttonPressInitialY; ++ let distance = Math.sqrt(Math.pow(xDiff, 2) + Math.pow(yDiff, 2)); ++ if (distance > DRAG_TRESHOLD) { ++ // Don't need to track anymore this if we start drag, and also ++ // avoids reentrance here ++ this._primaryButtonPressed = false; ++ let event = Clutter.get_current_event(); ++ let [x, y] = event.get_coords(); ++ Extension.desktopManager.dragStart(); ++ } ++ } ++ ++ return Clutter.EVENT_PROPAGATE; ++ } ++ ++ _onReleaseButton(actor, event) { ++ let button = event.get_button(); ++ if (button == 1) { ++ // primaryButtonPressed is TRUE only if the user has pressed the button ++ // over an icon, and if (s)he has not started a drag&drop operation ++ if (this._primaryButtonPressed) { ++ this._primaryButtonPressed = false; ++ let shiftPressed = !!(event.get_state() & Clutter.ModifierType.SHIFT_MASK); ++ let controlPressed = !!(event.get_state() & Clutter.ModifierType.CONTROL_MASK); ++ if ((event.get_click_count() == 1) && Prefs.CLICK_POLICY_SINGLE && !shiftPressed && !controlPressed) ++ this.doOpen(); ++ this.emit('selected', shiftPressed || controlPressed, true); ++ return Clutter.EVENT_STOP; ++ } ++ if ((event.get_click_count() == 2) && (!Prefs.CLICK_POLICY_SINGLE)) ++ this.doOpen(); ++ } ++ return Clutter.EVENT_PROPAGATE; ++ } ++ ++ get savedCoordinates() { ++ return this._savedCoordinates; ++ } ++ ++ _onSetMetadataFileFinished(source, result) { ++ try { ++ let [success, info] = source.set_attributes_finish(result); ++ } catch (error) { ++ if (!error.matches(Gio.IOErrorEnum, Gio.IOErrorEnum.CANCELLED)) ++ log('Error setting metadata to desktop files ', error); ++ } ++ } ++ ++ set savedCoordinates(pos) { ++ if (this._setMetadataCancellable) ++ this._setMetadataCancellable.cancel(); ++ ++ this._setMetadataCancellable = new Gio.Cancellable(); ++ this._savedCoordinates = [pos[0], pos[1]]; ++ let info = new Gio.FileInfo(); ++ info.set_attribute_string('metadata::nautilus-icon-position', ++ `${pos[0]},${pos[1]}`); ++ this.file.set_attributes_async(info, ++ Gio.FileQueryInfoFlags.NONE, ++ GLib.PRIORITY_DEFAULT, ++ this._setMetadataCancellable, ++ (source, result) => this._onSetMetadataFileFinished(source, result) ++ ); ++ } ++ ++ intersectsWith(argX, argY, argWidth, argHeight) { ++ let rect = new Meta.Rectangle({ x: argX, y: argY, width: argWidth, height: argHeight }); ++ let [containerX, containerY] = this._container.get_transformed_position(); ++ let boundingBox = new Meta.Rectangle({ x: containerX, ++ y: containerY, ++ width: this._container.allocation.x2 - this._container.allocation.x1, ++ height: this._container.allocation.y2 - this._container.allocation.y1 }); ++ let [intersects, _] = rect.intersect(boundingBox); ++ ++ return intersects; ++ } ++ ++ set isSelected(isSelected) { ++ isSelected = !!isSelected; ++ if (isSelected == this._isSelected) ++ return; ++ ++ if (isSelected) ++ this._container.add_style_pseudo_class('selected'); ++ else ++ this._container.remove_style_pseudo_class('selected'); ++ ++ this._isSelected = isSelected; ++ } ++ ++ get isSelected() { ++ return this._isSelected; ++ } ++ ++ get isSpecial() { ++ return this._isSpecial; ++ } ++ ++ get state() { ++ return this._state; ++ } ++ ++ set state(state) { ++ if (state == this._state) ++ return; ++ ++ this._state = state; ++ } ++ ++ get isDirectory() { ++ return this._isDirectory; ++ } ++ ++ get trustedDesktopFile() { ++ return this._isDesktopFile && ++ this._attributeCanExecute && ++ this.metadataTrusted && ++ !Extension.desktopManager.writableByOthers && ++ !this._writableByOthers; ++ } ++ ++ get displayName() { ++ if (this.trustedDesktopFile) ++ return this._desktopFile.get_name(); ++ ++ return this._displayName || null; ++ } ++ ++ acceptDrop() { ++ return Extension.desktopManager.selectionDropOnFileItem(this); ++ } ++}; ++Signals.addSignalMethods(FileItem.prototype); +diff --git a/extensions/desktop-icons/meson.build b/extensions/desktop-icons/meson.build +new file mode 100644 +index 0000000..4e03db1 +--- /dev/null ++++ b/extensions/desktop-icons/meson.build +@@ -0,0 +1,18 @@ ++extension_data += configure_file( ++ input: metadata_name + '.in', ++ output: metadata_name, ++ configuration: metadata_conf ++) ++ ++extension_schemas += files(join_paths('schemas', metadata_conf.get('gschemaname') + '.gschema.xml')) ++ ++extension_sources += files( ++ 'createThumbnail.js', ++ 'dbusUtils.js', ++ 'desktopGrid.js', ++ 'desktopIconsUtil.js', ++ 'desktopManager.js', ++ 'extension.js', ++ 'fileItem.js', ++ 'prefs.js' ++) +diff --git a/extensions/desktop-icons/metadata.json.in b/extensions/desktop-icons/metadata.json.in +new file mode 100644 +index 0000000..78cabf0 +--- /dev/null ++++ b/extensions/desktop-icons/metadata.json.in +@@ -0,0 +1,11 @@ ++{ ++"extension-id": "@extension_id@", ++"uuid": "@uuid@", ++"settings-schema": "@gschemaname@", ++"gettext-domain": "@gettext_domain@", ++"name": "Desktop Icons", ++"description": "Provide desktop icons support for classic mode", ++"original-authors": [ "csoriano@redhat.com" ], ++"shell-version": [ "@shell_current@" ], ++"url": "@url@" ++} +diff --git a/extensions/desktop-icons/po/LINGUAS b/extensions/desktop-icons/po/LINGUAS +new file mode 100644 +index 0000000..bc8bc5d +--- /dev/null ++++ b/extensions/desktop-icons/po/LINGUAS +@@ -0,0 +1,12 @@ ++cs ++da ++de ++es ++fi ++fr ++id ++it ++pl ++pt_BR ++ru ++zh_TW +diff --git a/extensions/desktop-icons/po/POTFILES.in b/extensions/desktop-icons/po/POTFILES.in +new file mode 100644 +index 0000000..7c2ebd3 +--- /dev/null ++++ b/extensions/desktop-icons/po/POTFILES.in +@@ -0,0 +1,4 @@ ++prefs.js ++desktopGrid.js ++fileItem.js ++schemas/org.gnome.shell.extensions.desktop-icons.gschema.xml +\ No newline at end of file +diff --git a/extensions/desktop-icons/po/cs.po b/extensions/desktop-icons/po/cs.po +new file mode 100644 +index 0000000..1da4122 +--- /dev/null ++++ b/extensions/desktop-icons/po/cs.po +@@ -0,0 +1,136 @@ ++# Czech translation for desktop-icons. ++# Copyright (C) 2018 desktop-icons's COPYRIGHT HOLDER ++# This file is distributed under the same license as the desktop-icons package. ++# Marek Černocký , 2018. ++# ++msgid "" ++msgstr "" ++"Project-Id-Version: desktop-icons master\n" ++"Report-Msgid-Bugs-To: https://gitlab.gnome.org/World/ShellExtensions/desktop-" ++"icons/issues\n" ++"POT-Creation-Date: 2018-10-01 20:15+0000\n" ++"PO-Revision-Date: 2018-10-02 11:10+0200\n" ++"Last-Translator: Marek Černocký \n" ++"Language-Team: čeština \n" ++"Language: cs\n" ++"MIME-Version: 1.0\n" ++"Content-Type: text/plain; charset=UTF-8\n" ++"Content-Transfer-Encoding: 8bit\n" ++"Plural-Forms: nplurals=3; plural=(n==1) ? 0 : (n>=2 && n<=4) ? 1 : 2;\n" ++"X-Generator: Gtranslator 2.91.7\n" ++ ++#: prefs.js:89 ++msgid "Size for the desktop icons" ++msgstr "Velikost ikon na pracovní ploše" ++ ++#: prefs.js:89 ++msgid "Small" ++msgstr "malé" ++ ++#: prefs.js:89 ++msgid "Standard" ++msgstr "standardní" ++ ++#: prefs.js:89 ++msgid "Large" ++msgstr "velké" ++ ++#: prefs.js:89 ++msgid "Huge" ++msgstr "obrovské" ++ ++#: prefs.js:90 ++msgid "Show the personal folder in the desktop" ++msgstr "Zobrazovat osobní složku na pracovní ploše" ++ ++#: prefs.js:91 ++msgid "Show the trash icon in the desktop" ++msgstr "Zobrazovat ikonu koše na pracovní ploše" ++ ++#: desktopGrid.js:178 desktopGrid.js:297 ++msgid "New Folder" ++msgstr "Nová složka" ++ ++#: desktopGrid.js:299 ++msgid "Paste" ++msgstr "Vložit" ++ ++#: desktopGrid.js:300 ++msgid "Undo" ++msgstr "Zpět" ++ ++#: desktopGrid.js:301 ++msgid "Redo" ++msgstr "Znovu" ++ ++#: desktopGrid.js:303 ++msgid "Open Desktop in Files" ++msgstr "Otevřít plochu v Souborech" ++ ++#: desktopGrid.js:304 ++msgid "Open Terminal" ++msgstr "Otevřít terminál" ++ ++#: desktopGrid.js:306 ++msgid "Change Background…" ++msgstr "Změnit pozadí…" ++ ++#: desktopGrid.js:307 ++msgid "Display Settings" ++msgstr "Zobrazit nastavení" ++ ++#: desktopGrid.js:308 ++msgid "Settings" ++msgstr "Nastavení" ++ ++#: fileItem.js:226 ++msgid "Open" ++msgstr "Otevřít" ++ ++#: fileItem.js:229 ++msgid "Cut" ++msgstr "Vyjmout" ++ ++#: fileItem.js:230 ++msgid "Copy" ++msgstr "Kopírovat" ++ ++#: fileItem.js:231 ++msgid "Move to Trash" ++msgstr "Přesunout do koše" ++ ++#: fileItem.js:235 ++msgid "Empty trash" ++msgstr "Vyprázdnit koš" ++ ++#: fileItem.js:241 ++msgid "Properties" ++msgstr "Vlastnosti" ++ ++#: fileItem.js:243 ++msgid "Show in Files" ++msgstr "Zobrazit v Souborech" ++ ++#: schemas/org.gnome.shell.extensions.desktop-icons.gschema.xml:12 ++msgid "Icon size" ++msgstr "Velikost ikon" ++ ++#: schemas/org.gnome.shell.extensions.desktop-icons.gschema.xml:13 ++msgid "Set the size for the desktop icons." ++msgstr "Nastavit velikost pro ikony na pracovní ploše." ++ ++#: schemas/org.gnome.shell.extensions.desktop-icons.gschema.xml:17 ++msgid "Show personal folder" ++msgstr "Zobrazovat osobní složku" ++ ++#: schemas/org.gnome.shell.extensions.desktop-icons.gschema.xml:18 ++msgid "Show the personal folder in the desktop." ++msgstr "Zobrazovat osobní složku na pracovní ploše." ++ ++#: schemas/org.gnome.shell.extensions.desktop-icons.gschema.xml:22 ++msgid "Show trash icon" ++msgstr "Zobrazovat koš" ++ ++#: schemas/org.gnome.shell.extensions.desktop-icons.gschema.xml:23 ++msgid "Show the trash icon in the desktop." ++msgstr "Zobrazovat ikonu koše na pracovní ploše." +diff --git a/extensions/desktop-icons/po/da.po b/extensions/desktop-icons/po/da.po +new file mode 100644 +index 0000000..98f2617 +--- /dev/null ++++ b/extensions/desktop-icons/po/da.po +@@ -0,0 +1,136 @@ ++# Danish translation for desktop-icons. ++# Copyright (C) 2018 desktop-icons's COPYRIGHT HOLDER ++# This file is distributed under the same license as the desktop-icons package. ++# Alan Mortensen , 2018. ++# ++msgid "" ++msgstr "" ++"Project-Id-Version: desktop-icons master\n" ++"Report-Msgid-Bugs-To: https://gitlab.gnome.org/World/ShellExtensions/desktop-" ++"icons/issues\n" ++"POT-Creation-Date: 2018-10-26 12:04+0000\n" ++"PO-Revision-Date: 2018-10-27 16:42+0200\n" ++"Language-Team: Danish \n" ++"Language: da\n" ++"MIME-Version: 1.0\n" ++"Content-Type: text/plain; charset=UTF-8\n" ++"Content-Transfer-Encoding: 8bit\n" ++"Plural-Forms: nplurals=2; plural=(n != 1);\n" ++"Last-Translator: Alan Mortensen \n" ++"X-Generator: Poedit 2.0.6\n" ++ ++#: prefs.js:89 ++msgid "Size for the desktop icons" ++msgstr "Størrelsen på skrivebordsikoner" ++ ++#: prefs.js:89 ++msgid "Small" ++msgstr "Små" ++ ++#: prefs.js:89 ++msgid "Standard" ++msgstr "Standard" ++ ++#: prefs.js:89 ++msgid "Large" ++msgstr "Store" ++ ++#: prefs.js:89 ++msgid "Huge" ++msgstr "Enorme" ++ ++#: prefs.js:90 ++msgid "Show the personal folder in the desktop" ++msgstr "Vis den personlige mappe på skrivebordet" ++ ++#: prefs.js:91 ++msgid "Show the trash icon in the desktop" ++msgstr "Vis papirkurvsikonet på skrivebordet" ++ ++#: desktopGrid.js:178 desktopGrid.js:297 ++msgid "New Folder" ++msgstr "Ny mappe" ++ ++#: desktopGrid.js:299 ++msgid "Paste" ++msgstr "Indsæt" ++ ++#: desktopGrid.js:300 ++msgid "Undo" ++msgstr "Fortryd" ++ ++#: desktopGrid.js:301 ++msgid "Redo" ++msgstr "Omgør" ++ ++#: desktopGrid.js:303 ++msgid "Open Desktop in Files" ++msgstr "Åbn skrivebordet i Filer" ++ ++#: desktopGrid.js:304 ++msgid "Open Terminal" ++msgstr "Åbn Terminal" ++ ++#: desktopGrid.js:306 ++msgid "Change Background…" ++msgstr "Skift baggrund …" ++ ++#: desktopGrid.js:307 ++msgid "Display Settings" ++msgstr "Skærmindstillinger" ++ ++#: desktopGrid.js:308 ++msgid "Settings" ++msgstr "Indstillinger" ++ ++#: fileItem.js:385 ++msgid "Open" ++msgstr "Åbn" ++ ++#: fileItem.js:388 ++msgid "Cut" ++msgstr "Klip" ++ ++#: fileItem.js:389 ++msgid "Copy" ++msgstr "Kopiér" ++ ++#: fileItem.js:390 ++msgid "Move to Trash" ++msgstr "Flyt til papirkurven" ++ ++#: fileItem.js:394 ++msgid "Empty trash" ++msgstr "Tøm papirkurven" ++ ++#: fileItem.js:400 ++msgid "Properties" ++msgstr "Egenskaber" ++ ++#: fileItem.js:402 ++msgid "Show in Files" ++msgstr "Vis i Filer" ++ ++#: schemas/org.gnome.shell.extensions.desktop-icons.gschema.xml:12 ++msgid "Icon size" ++msgstr "Ikonstørrelse" ++ ++#: schemas/org.gnome.shell.extensions.desktop-icons.gschema.xml:13 ++msgid "Set the size for the desktop icons." ++msgstr "Angiv størrelsen på skrivebordsikoner." ++ ++#: schemas/org.gnome.shell.extensions.desktop-icons.gschema.xml:17 ++msgid "Show personal folder" ++msgstr "Vis personlig mappe" ++ ++#: schemas/org.gnome.shell.extensions.desktop-icons.gschema.xml:18 ++msgid "Show the personal folder in the desktop." ++msgstr "Vis den personlige mappe på skrivebordet." ++ ++#: schemas/org.gnome.shell.extensions.desktop-icons.gschema.xml:22 ++msgid "Show trash icon" ++msgstr "Vis papirkurvsikon" ++ ++#: schemas/org.gnome.shell.extensions.desktop-icons.gschema.xml:23 ++msgid "Show the trash icon in the desktop." ++msgstr "Vis papirkurvsikonet på skrivebordet." +diff --git a/extensions/desktop-icons/po/de.po b/extensions/desktop-icons/po/de.po +new file mode 100644 +index 0000000..e2b0fb3 +--- /dev/null ++++ b/extensions/desktop-icons/po/de.po +@@ -0,0 +1,136 @@ ++# German translation for desktop-icons. ++# Copyright (C) 2018 desktop-icons's COPYRIGHT HOLDER ++# This file is distributed under the same license as the desktop-icons package. ++# Mario Blättermann , 2018. ++# ++msgid "" ++msgstr "" ++"Project-Id-Version: desktop-icons master\n" ++"Report-Msgid-Bugs-To: https://gitlab.gnome.org/World/ShellExtensions/desktop-" ++"icons/issues\n" ++"POT-Creation-Date: 2018-10-02 21:39+0000\n" ++"PO-Revision-Date: 2018-10-03 21:28+0200\n" ++"Last-Translator: Mario Blättermann \n" ++"Language-Team: German \n" ++"Language: de\n" ++"MIME-Version: 1.0\n" ++"Content-Type: text/plain; charset=UTF-8\n" ++"Content-Transfer-Encoding: 8bit\n" ++"Plural-Forms: nplurals=2; plural=(n != 1);\n" ++"X-Generator: Poedit 2.1.1\n" ++ ++#: prefs.js:89 ++msgid "Size for the desktop icons" ++msgstr "Größe der Arbeitsflächensymbole" ++ ++#: prefs.js:89 ++msgid "Small" ++msgstr "Klein" ++ ++#: prefs.js:89 ++msgid "Standard" ++msgstr "Standard" ++ ++#: prefs.js:89 ++msgid "Large" ++msgstr "Groß" ++ ++#: prefs.js:89 ++msgid "Huge" ++msgstr "Riesig" ++ ++#: prefs.js:90 ++msgid "Show the personal folder in the desktop" ++msgstr "Den persönlichen Ordner auf der Arbeitsfläche anzeigen" ++ ++#: prefs.js:91 ++msgid "Show the trash icon in the desktop" ++msgstr "Papierkorb-Symbol auf der Arbeitsfläche anzeigen" ++ ++#: desktopGrid.js:178 desktopGrid.js:297 ++msgid "New Folder" ++msgstr "Neuer Ordner" ++ ++#: desktopGrid.js:299 ++msgid "Paste" ++msgstr "Einfügen" ++ ++#: desktopGrid.js:300 ++msgid "Undo" ++msgstr "Rückgängig" ++ ++#: desktopGrid.js:301 ++msgid "Redo" ++msgstr "Wiederholen" ++ ++#: desktopGrid.js:303 ++msgid "Open Desktop in Files" ++msgstr "Arbeitsfläche in Dateiverwaltung öffnen" ++ ++#: desktopGrid.js:304 ++msgid "Open Terminal" ++msgstr "Terminal öffnen" ++ ++#: desktopGrid.js:306 ++msgid "Change Background…" ++msgstr "Hintergrund ändern …" ++ ++#: desktopGrid.js:307 ++msgid "Display Settings" ++msgstr "Anzeigeeinstellungen" ++ ++#: desktopGrid.js:308 ++msgid "Settings" ++msgstr "Einstellungen" ++ ++#: fileItem.js:226 ++msgid "Open" ++msgstr "Öffnen" ++ ++#: fileItem.js:229 ++msgid "Cut" ++msgstr "Ausschneiden" ++ ++#: fileItem.js:230 ++msgid "Copy" ++msgstr "Kopieren" ++ ++#: fileItem.js:231 ++msgid "Move to Trash" ++msgstr "In den Papierkorb verschieben" ++ ++#: fileItem.js:235 ++msgid "Empty trash" ++msgstr "Papierkorb leeren" ++ ++#: fileItem.js:241 ++msgid "Properties" ++msgstr "Eigenschaften" ++ ++#: fileItem.js:243 ++msgid "Show in Files" ++msgstr "In Dateiverwaltung anzeigen" ++ ++#: schemas/org.gnome.shell.extensions.desktop-icons.gschema.xml:12 ++msgid "Icon size" ++msgstr "Symbolgröße" ++ ++#: schemas/org.gnome.shell.extensions.desktop-icons.gschema.xml:13 ++msgid "Set the size for the desktop icons." ++msgstr "Die Größe der Arbeitsflächensymbole festlegen." ++ ++#: schemas/org.gnome.shell.extensions.desktop-icons.gschema.xml:17 ++msgid "Show personal folder" ++msgstr "Persönlichen Ordner anzeigen" ++ ++#: schemas/org.gnome.shell.extensions.desktop-icons.gschema.xml:18 ++msgid "Show the personal folder in the desktop." ++msgstr "Den persönlichen Ordner auf der Arbeitsfläche anzeigen." ++ ++#: schemas/org.gnome.shell.extensions.desktop-icons.gschema.xml:22 ++msgid "Show trash icon" ++msgstr "Papierkorb-Symbol anzeigen" ++ ++#: schemas/org.gnome.shell.extensions.desktop-icons.gschema.xml:23 ++msgid "Show the trash icon in the desktop." ++msgstr "Das Papierkorb-Symbol auf der Arbeitsfläche anzeigen." +diff --git a/extensions/desktop-icons/po/es.po b/extensions/desktop-icons/po/es.po +new file mode 100644 +index 0000000..05b47c2 +--- /dev/null ++++ b/extensions/desktop-icons/po/es.po +@@ -0,0 +1,186 @@ ++# SOME DESCRIPTIVE TITLE. ++# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER ++# This file is distributed under the same license as the PACKAGE package. ++# FIRST AUTHOR , YEAR. ++# Sergio Costas , 2018. ++# Daniel Mustieles , 2018, 2019. ++# ++msgid "" ++msgstr "" ++"Project-Id-Version: PACKAGE VERSION\n" ++"Report-Msgid-Bugs-To: https://gitlab.gnome.org/World/ShellExtensions/desktop-" ++"icons/issues\n" ++"POT-Creation-Date: 2018-12-14 09:12+0000\n" ++"PO-Revision-Date: 2019-01-08 16:12+0100\n" ++"Last-Translator: Daniel Mustieles \n" ++"Language-Team: es \n" ++"Language: es\n" ++"MIME-Version: 1.0\n" ++"Content-Type: text/plain; charset=UTF-8\n" ++"Content-Transfer-Encoding: 8bit\n" ++"Plural-Forms: nplurals=2; plural=(n != 1);\n" ++"X-Generator: Gtranslator 2.91.7\n" ++ ++#: prefs.js:102 ++msgid "Size for the desktop icons" ++msgstr "Tamaño de los iconos del escritorio" ++ ++#: prefs.js:102 ++msgid "Small" ++msgstr "Pequeño" ++ ++#: prefs.js:102 ++msgid "Standard" ++msgstr "Estándar" ++ ++#: prefs.js:102 ++msgid "Large" ++msgstr "Grande" ++ ++#: prefs.js:102 ++msgid "Huge" ++msgstr "Inmenso" ++ ++#: prefs.js:103 ++msgid "Show the personal folder in the desktop" ++msgstr "Mostrar la carpeta personal en el escritorio" ++ ++#: prefs.js:104 ++msgid "Show the trash icon in the desktop" ++msgstr "Mostrar la papelera en el escritorio" ++ ++#: desktopGrid.js:182 desktopGrid.js:301 ++msgid "New Folder" ++msgstr "Nueva carpeta" ++ ++#: desktopGrid.js:303 ++msgid "Paste" ++msgstr "Pegar" ++ ++#: desktopGrid.js:304 ++msgid "Undo" ++msgstr "Deshacer" ++ ++#: desktopGrid.js:305 ++msgid "Redo" ++msgstr "Rehacer" ++ ++#: desktopGrid.js:307 ++msgid "Open Desktop in Files" ++msgstr "Abrir el escritorio en Files" ++ ++#: desktopGrid.js:308 ++msgid "Open Terminal" ++msgstr "Abrir un terminal" ++ ++#: desktopGrid.js:310 ++msgid "Change Background…" ++msgstr "Cambiar el fondo..." ++ ++#: desktopGrid.js:311 ++msgid "Display Settings" ++msgstr "Configuración de pantalla" ++ ++#: desktopGrid.js:312 ++msgid "Settings" ++msgstr "Configuración" ++ ++#: desktopGrid.js:568 ++msgid "Enter file name…" ++msgstr "Introduzca el nombre del archivo…" ++ ++#: desktopGrid.js:572 ++msgid "OK" ++msgstr "Aceptar" ++ ++#: desktopGrid.js:578 ++msgid "Cancel" ++msgstr "Cancelar" ++ ++#: fileItem.js:485 ++msgid "Don’t Allow Launching" ++msgstr "No permitir lanzar" ++ ++#: fileItem.js:487 ++msgid "Allow Launching" ++msgstr "Permitir lanzar" ++ ++#: fileItem.js:550 ++msgid "Open" ++msgstr "Abrir" ++ ++#: fileItem.js:553 ++msgid "Cut" ++msgstr "Cortar" ++ ++#: fileItem.js:554 ++msgid "Copy" ++msgstr "Copiar" ++ ++#: fileItem.js:556 ++msgid "Rename" ++msgstr "Renombrar" ++ ++#: fileItem.js:557 ++msgid "Move to Trash" ++msgstr "Mover a la papelera" ++ ++#: fileItem.js:567 ++msgid "Empty Trash" ++msgstr "Vaciar la papelera" ++ ++#: fileItem.js:573 ++msgid "Properties" ++msgstr "Propiedades" ++ ++#: fileItem.js:575 ++msgid "Show in Files" ++msgstr "Mostrar en Files" ++ ++#: schemas/org.gnome.shell.extensions.desktop-icons.gschema.xml:12 ++msgid "Icon size" ++msgstr "Tamaño de los iconos" ++ ++#: schemas/org.gnome.shell.extensions.desktop-icons.gschema.xml:13 ++msgid "Set the size for the desktop icons." ++msgstr "Establece el tamaño de los iconos del escritorio." ++ ++#: schemas/org.gnome.shell.extensions.desktop-icons.gschema.xml:17 ++msgid "Show personal folder" ++msgstr "Mostrar la carpeta personal" ++ ++#: schemas/org.gnome.shell.extensions.desktop-icons.gschema.xml:18 ++msgid "Show the personal folder in the desktop." ++msgstr "Mostrar la carpeta personal en el escritorio." ++ ++#: schemas/org.gnome.shell.extensions.desktop-icons.gschema.xml:22 ++msgid "Show trash icon" ++msgstr "Mostrar la papelera" ++ ++#: schemas/org.gnome.shell.extensions.desktop-icons.gschema.xml:23 ++msgid "Show the trash icon in the desktop." ++msgstr "Mostrar la papelera en el escritorio." ++ ++#~ msgid "Ok" ++#~ msgstr "Aceptar" ++ ++#~ msgid "huge" ++#~ msgstr "inmenso" ++ ++#~ msgid "Maximum width for the icons and filename." ++#~ msgstr "Ancho máximo de los iconos y el nombre de fichero." ++ ++#~ msgid "Shows the Documents folder in the desktop." ++#~ msgstr "Muestra la carpeta Documentos en el escritorio." ++ ++#~ msgid "Shows the Downloads folder in the desktop." ++#~ msgstr "Muestra la carpeta Descargas en el escritorio." ++ ++#~ msgid "Shows the Music folder in the desktop." ++#~ msgstr "Muestra la carpeta Música en el escritorio." ++ ++#~ msgid "Shows the Pictures folder in the desktop." ++#~ msgstr "Muestra la carpeta Imágenes en el escritorio." ++ ++#~ msgid "Shows the Videos folder in the desktop." ++#~ msgstr "Muestra la carpeta Vídeos en el escritorio." +diff --git a/extensions/desktop-icons/po/fi.po b/extensions/desktop-icons/po/fi.po +new file mode 100644 +index 0000000..216e5c1 +--- /dev/null ++++ b/extensions/desktop-icons/po/fi.po +@@ -0,0 +1,136 @@ ++# Finnish translation for desktop-icons. ++# Copyright (C) 2018 desktop-icons's COPYRIGHT HOLDER ++# This file is distributed under the same license as the desktop-icons package. ++# Jiri Grönroos , 2018. ++# ++msgid "" ++msgstr "" ++"Project-Id-Version: desktop-icons master\n" ++"Report-Msgid-Bugs-To: https://gitlab.gnome.org/World/ShellExtensions/desktop-" ++"icons/issues\n" ++"POT-Creation-Date: 2018-10-14 08:09+0000\n" ++"PO-Revision-Date: 2018-10-14 12:44+0300\n" ++"Language-Team: Finnish \n" ++"Language: fi\n" ++"MIME-Version: 1.0\n" ++"Content-Type: text/plain; charset=UTF-8\n" ++"Content-Transfer-Encoding: 8bit\n" ++"Plural-Forms: nplurals=2; plural=(n != 1);\n" ++"Last-Translator: Jiri Grönroos \n" ++"X-Generator: Poedit 2.0.6\n" ++ ++#: prefs.js:89 ++msgid "Size for the desktop icons" ++msgstr "Työpöytäkuvakkeiden koko" ++ ++#: prefs.js:89 ++msgid "Small" ++msgstr "Pieni" ++ ++#: prefs.js:89 ++msgid "Standard" ++msgstr "Normaali" ++ ++#: prefs.js:89 ++msgid "Large" ++msgstr "Suuri" ++ ++#: prefs.js:89 ++msgid "Huge" ++msgstr "Valtava" ++ ++#: prefs.js:90 ++msgid "Show the personal folder in the desktop" ++msgstr "Näytä kotikansio työpöydällä" ++ ++#: prefs.js:91 ++msgid "Show the trash icon in the desktop" ++msgstr "Näytä roskakorin kuvake työpöydällä" ++ ++#: desktopGrid.js:178 desktopGrid.js:297 ++msgid "New Folder" ++msgstr "Uusi kansio" ++ ++#: desktopGrid.js:299 ++msgid "Paste" ++msgstr "Liitä" ++ ++#: desktopGrid.js:300 ++msgid "Undo" ++msgstr "Kumoa" ++ ++#: desktopGrid.js:301 ++msgid "Redo" ++msgstr "Tee uudeleen" ++ ++#: desktopGrid.js:303 ++msgid "Open Desktop in Files" ++msgstr "Avaa työpöytä tiedostonhallinnassa" ++ ++#: desktopGrid.js:304 ++msgid "Open Terminal" ++msgstr "Avaa pääte" ++ ++#: desktopGrid.js:306 ++msgid "Change Background…" ++msgstr "Vaihda taustakuvaa…" ++ ++#: desktopGrid.js:307 ++msgid "Display Settings" ++msgstr "Näytön asetukset" ++ ++#: desktopGrid.js:308 ++msgid "Settings" ++msgstr "Asetukset" ++ ++#: fileItem.js:211 ++msgid "Open" ++msgstr "Avaa" ++ ++#: fileItem.js:214 ++msgid "Cut" ++msgstr "Leikkaa" ++ ++#: fileItem.js:215 ++msgid "Copy" ++msgstr "Kopioi" ++ ++#: fileItem.js:216 ++msgid "Move to Trash" ++msgstr "Siirrä roskakoriin" ++ ++#: fileItem.js:220 ++msgid "Empty trash" ++msgstr "Tyhjennä roskakori" ++ ++#: fileItem.js:226 ++msgid "Properties" ++msgstr "Ominaisuudet" ++ ++#: fileItem.js:228 ++msgid "Show in Files" ++msgstr "Näytä tiedostonhallinnassa" ++ ++#: schemas/org.gnome.shell.extensions.desktop-icons.gschema.xml:12 ++msgid "Icon size" ++msgstr "Kuvakekoko" ++ ++#: schemas/org.gnome.shell.extensions.desktop-icons.gschema.xml:13 ++msgid "Set the size for the desktop icons." ++msgstr "Aseta työpöytäkuvakkeiden koko." ++ ++#: schemas/org.gnome.shell.extensions.desktop-icons.gschema.xml:17 ++msgid "Show personal folder" ++msgstr "Näytä kotikansio" ++ ++#: schemas/org.gnome.shell.extensions.desktop-icons.gschema.xml:18 ++msgid "Show the personal folder in the desktop." ++msgstr "Näytä kotikansio työpöydällä." ++ ++#: schemas/org.gnome.shell.extensions.desktop-icons.gschema.xml:22 ++msgid "Show trash icon" ++msgstr "Näytä roskakorin kuvake" ++ ++#: schemas/org.gnome.shell.extensions.desktop-icons.gschema.xml:23 ++msgid "Show the trash icon in the desktop." ++msgstr "Näytä roskakorin kuvake työpöydällä." +diff --git a/extensions/desktop-icons/po/fr.po b/extensions/desktop-icons/po/fr.po +new file mode 100644 +index 0000000..13e8f3a +--- /dev/null ++++ b/extensions/desktop-icons/po/fr.po +@@ -0,0 +1,164 @@ ++# French translation for desktop-icons. ++# Copyright (C) 2018 desktop-icons's COPYRIGHT HOLDER ++# This file is distributed under the same license as the desktop-icons package. ++# ghentdebian , 2018. ++# Charles Monzat , 2018. ++# ++msgid "" ++msgstr "" ++"Project-Id-Version: desktop-icons master\n" ++"Report-Msgid-Bugs-To: https://gitlab.gnome.org/World/ShellExtensions/desktop-" ++"icons/issues\n" ++"POT-Creation-Date: 2018-12-14 09:12+0000\n" ++"PO-Revision-Date: 2018-12-16 17:47+0100\n" ++"Last-Translator: Charles Monzat \n" ++"Language-Team: GNOME French Team \n" ++"Language: fr\n" ++"MIME-Version: 1.0\n" ++"Content-Type: text/plain; charset=UTF-8\n" ++"Content-Transfer-Encoding: 8bit\n" ++"Plural-Forms: nplurals=2; plural=(n > 1)\n" ++"X-Generator: Gtranslator 3.30.0\n" ++ ++#: prefs.js:102 ++msgid "Size for the desktop icons" ++msgstr "Taille des icônes du bureau" ++ ++#: prefs.js:102 ++msgid "Small" ++msgstr "Petite" ++ ++#: prefs.js:102 ++msgid "Standard" ++msgstr "Normale" ++ ++#: prefs.js:102 ++msgid "Large" ++msgstr "Grande" ++ ++#: prefs.js:102 ++msgid "Huge" ++msgstr "Immense" ++ ++#: prefs.js:103 ++msgid "Show the personal folder in the desktop" ++msgstr "Montrer le dossier personnel sur le bureau" ++ ++#: prefs.js:104 ++msgid "Show the trash icon in the desktop" ++msgstr "Montrer la corbeille sur le bureau" ++ ++#: desktopGrid.js:182 desktopGrid.js:301 ++msgid "New Folder" ++msgstr "Nouveau dossier" ++ ++#: desktopGrid.js:303 ++msgid "Paste" ++msgstr "Coller" ++ ++#: desktopGrid.js:304 ++msgid "Undo" ++msgstr "Annuler" ++ ++#: desktopGrid.js:305 ++msgid "Redo" ++msgstr "Refaire" ++ ++#: desktopGrid.js:307 ++msgid "Open Desktop in Files" ++msgstr "Ouvrir le bureau dans Fichiers" ++ ++#: desktopGrid.js:308 ++msgid "Open Terminal" ++msgstr "Ouvrir un terminal" ++ ++#: desktopGrid.js:310 ++msgid "Change Background…" ++msgstr "Changer l’arrière-plan…" ++ ++#: desktopGrid.js:311 ++msgid "Display Settings" ++msgstr "Configuration d’affichage" ++ ++#: desktopGrid.js:312 ++msgid "Settings" ++msgstr "Paramètres" ++ ++#: desktopGrid.js:568 ++msgid "Enter file name…" ++msgstr "Saisir un nom de fichier…" ++ ++#: desktopGrid.js:572 ++msgid "OK" ++msgstr "Valider" ++ ++#: desktopGrid.js:578 ++msgid "Cancel" ++msgstr "Annuler" ++ ++#: fileItem.js:485 ++msgid "Don’t Allow Launching" ++msgstr "Ne pas autoriser le lancement" ++ ++#: fileItem.js:487 ++msgid "Allow Launching" ++msgstr "Autoriser le lancement" ++ ++#: fileItem.js:550 ++msgid "Open" ++msgstr "Ouvrir" ++ ++#: fileItem.js:553 ++msgid "Cut" ++msgstr "Couper" ++ ++#: fileItem.js:554 ++msgid "Copy" ++msgstr "Copier" ++ ++#: fileItem.js:556 ++msgid "Rename" ++msgstr "Renommer" ++ ++#: fileItem.js:557 ++msgid "Move to Trash" ++msgstr "Mettre à la corbeille" ++ ++#: fileItem.js:567 ++msgid "Empty Trash" ++msgstr "Vider la corbeille" ++ ++#: fileItem.js:573 ++msgid "Properties" ++msgstr "Propriétés" ++ ++#: fileItem.js:575 ++msgid "Show in Files" ++msgstr "Montrer dans Fichiers" ++ ++#: schemas/org.gnome.shell.extensions.desktop-icons.gschema.xml:12 ++msgid "Icon size" ++msgstr "Taille d’icônes" ++ ++#: schemas/org.gnome.shell.extensions.desktop-icons.gschema.xml:13 ++msgid "Set the size for the desktop icons." ++msgstr "Définir la taille des icônes du bureau." ++ ++#: schemas/org.gnome.shell.extensions.desktop-icons.gschema.xml:17 ++msgid "Show personal folder" ++msgstr "Montrer le dossier personnel" ++ ++#: schemas/org.gnome.shell.extensions.desktop-icons.gschema.xml:18 ++msgid "Show the personal folder in the desktop." ++msgstr "Montrer le dossier personnel sur le bureau." ++ ++#: schemas/org.gnome.shell.extensions.desktop-icons.gschema.xml:22 ++msgid "Show trash icon" ++msgstr "Montrer l’icône de la corbeille" ++ ++#: schemas/org.gnome.shell.extensions.desktop-icons.gschema.xml:23 ++msgid "Show the trash icon in the desktop." ++msgstr "Montrer la corbeille sur le bureau." ++ ++#~ msgid "Ok" ++#~ msgstr "Valider" +diff --git a/extensions/desktop-icons/po/id.po b/extensions/desktop-icons/po/id.po +new file mode 100644 +index 0000000..c145d1a +--- /dev/null ++++ b/extensions/desktop-icons/po/id.po +@@ -0,0 +1,135 @@ ++# Indonesian translation for desktop-icons. ++# Copyright (C) 2018 desktop-icons's COPYRIGHT HOLDER ++# This file is distributed under the same license as the desktop-icons package. ++# FIRST AUTHOR , YEAR. ++# ++msgid "" ++msgstr "" ++"Project-Id-Version: desktop-icons master\n" ++"Report-Msgid-Bugs-To: https://gitlab.gnome.org/World/ShellExtensions/desktop-" ++"icons/issues\n" ++"POT-Creation-Date: 2018-10-02 09:18+0000\n" ++"PO-Revision-Date: 2018-10-02 20:30+0700\n" ++"Language-Team: Indonesian \n" ++"Language: id\n" ++"MIME-Version: 1.0\n" ++"Content-Type: text/plain; charset=UTF-8\n" ++"Content-Transfer-Encoding: 8bit\n" ++"Last-Translator: Kukuh Syafaat \n" ++"X-Generator: Poedit 2.0.6\n" ++ ++#: prefs.js:89 ++msgid "Size for the desktop icons" ++msgstr "Ukuran untuk ikon destop" ++ ++#: prefs.js:89 ++msgid "Small" ++msgstr "Kecil" ++ ++#: prefs.js:89 ++msgid "Standard" ++msgstr "Standar" ++ ++#: prefs.js:89 ++msgid "Large" ++msgstr "Besar" ++ ++#: prefs.js:89 ++msgid "Huge" ++msgstr "Sangat besar" ++ ++#: prefs.js:90 ++msgid "Show the personal folder in the desktop" ++msgstr "Tampilkan folder pribadi di destop" ++ ++#: prefs.js:91 ++msgid "Show the trash icon in the desktop" ++msgstr "Tampilkan ikon tong sampah di destop" ++ ++#: desktopGrid.js:178 desktopGrid.js:297 ++msgid "New Folder" ++msgstr "Folder Baru" ++ ++#: desktopGrid.js:299 ++msgid "Paste" ++msgstr "Tempel" ++ ++#: desktopGrid.js:300 ++msgid "Undo" ++msgstr "Tak Jadi" ++ ++#: desktopGrid.js:301 ++msgid "Redo" ++msgstr "Jadi Lagi" ++ ++#: desktopGrid.js:303 ++msgid "Open Desktop in Files" ++msgstr "Buka Destop pada Berkas" ++ ++#: desktopGrid.js:304 ++msgid "Open Terminal" ++msgstr "Buka Terminal" ++ ++#: desktopGrid.js:306 ++msgid "Change Background…" ++msgstr "Ubah Latar Belakang…" ++ ++#: desktopGrid.js:307 ++msgid "Display Settings" ++msgstr "Pengaturan Tampilan" ++ ++#: desktopGrid.js:308 ++msgid "Settings" ++msgstr "Pengaturan" ++ ++#: fileItem.js:226 ++msgid "Open" ++msgstr "Buka" ++ ++#: fileItem.js:229 ++msgid "Cut" ++msgstr "Potong" ++ ++#: fileItem.js:230 ++msgid "Copy" ++msgstr "Salin" ++ ++#: fileItem.js:231 ++msgid "Move to Trash" ++msgstr "Pindahkan ke Tong Sampah" ++ ++#: fileItem.js:235 ++msgid "Empty trash" ++msgstr "Kosongkan Tong Sampah" ++ ++#: fileItem.js:241 ++msgid "Properties" ++msgstr "Properti" ++ ++#: fileItem.js:243 ++msgid "Show in Files" ++msgstr "Tampilkan pada Berkas" ++ ++#: schemas/org.gnome.shell.extensions.desktop-icons.gschema.xml:12 ++msgid "Icon size" ++msgstr "Ukuran ikon" ++ ++#: schemas/org.gnome.shell.extensions.desktop-icons.gschema.xml:13 ++msgid "Set the size for the desktop icons." ++msgstr "Set ukuran untuk ikon destop." ++ ++#: schemas/org.gnome.shell.extensions.desktop-icons.gschema.xml:17 ++msgid "Show personal folder" ++msgstr "Tampilkan folder pribadi" ++ ++#: schemas/org.gnome.shell.extensions.desktop-icons.gschema.xml:18 ++msgid "Show the personal folder in the desktop." ++msgstr "Tampilkan folder pribadi di destop." ++ ++#: schemas/org.gnome.shell.extensions.desktop-icons.gschema.xml:22 ++msgid "Show trash icon" ++msgstr "Tampilkan ikon tong sampah" ++ ++#: schemas/org.gnome.shell.extensions.desktop-icons.gschema.xml:23 ++msgid "Show the trash icon in the desktop." ++msgstr "Tampilkan ikon tong sampah di destop." +diff --git a/extensions/desktop-icons/po/it.po b/extensions/desktop-icons/po/it.po +new file mode 100644 +index 0000000..38c0572 +--- /dev/null ++++ b/extensions/desktop-icons/po/it.po +@@ -0,0 +1,152 @@ ++# SOME DESCRIPTIVE TITLE. ++# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER ++# This file is distributed under the same license as the PACKAGE package. ++# Massimo Branchini , 2018. ++# ++msgid "" ++msgstr "" ++"Project-Id-Version: desktop-icons master\n" ++"Report-Msgid-Bugs-To: https://gitlab.gnome.org/World/ShellExtensions/desktop-" ++"icons/issues\n" ++"POT-Creation-Date: 2018-11-29 09:07+0000\n" ++"PO-Revision-Date: 2018-12-05 11:14+0100\n" ++"Last-Translator: Massimo Branchini \n" ++"Language-Team: Italian \n" ++"Language: it\n" ++"MIME-Version: 1.0\n" ++"Content-Type: text/plain; charset=UTF-8\n" ++"Content-Transfer-Encoding: 8bit\n" ++"Plural-Forms: nplurals=2; plural=(n != 1);\n" ++"X-Generator: Poedit 2.2\n" ++ ++#: prefs.js:93 ++msgid "Size for the desktop icons" ++msgstr "Dimensione delle icone della scrivania" ++ ++#: prefs.js:93 ++msgid "Small" ++msgstr "Piccola" ++ ++#: prefs.js:93 ++msgid "Standard" ++msgstr "Normale" ++ ++#: prefs.js:93 ++msgid "Large" ++msgstr "Grande" ++ ++#: prefs.js:93 ++msgid "Huge" ++msgstr "Enorme" ++ ++#: prefs.js:94 ++msgid "Show the personal folder in the desktop" ++msgstr "Mostra la cartella personale sulla scrivania" ++ ++#: prefs.js:95 ++msgid "Show the trash icon in the desktop" ++msgstr "Mostra il cestino sulla scrivania" ++ ++#: desktopGrid.js:185 desktopGrid.js:304 ++msgid "New Folder" ++msgstr "Nuova cartella" ++ ++#: desktopGrid.js:306 ++msgid "Paste" ++msgstr "Incolla" ++ ++#: desktopGrid.js:307 ++msgid "Undo" ++msgstr "Annulla" ++ ++#: desktopGrid.js:308 ++msgid "Redo" ++msgstr "Ripeti" ++ ++#: desktopGrid.js:310 ++msgid "Open Desktop in Files" ++msgstr "Apri la scrivania in File" ++ ++#: desktopGrid.js:311 ++msgid "Open Terminal" ++msgstr "Apri un terminale" ++ ++#: desktopGrid.js:313 ++msgid "Change Background…" ++msgstr "Cambia lo sfondo…" ++ ++#: desktopGrid.js:314 ++msgid "Display Settings" ++msgstr "Impostazioni dello schermo" ++ ++#: desktopGrid.js:315 ++msgid "Settings" ++msgstr "Impostazioni" ++ ++#: desktopGrid.js:569 ++msgid "Enter file name…" ++msgstr "Indicare un nome per il file…" ++ ++#: desktopGrid.js:573 ++msgid "Ok" ++msgstr "Ok" ++ ++#: desktopGrid.js:579 ++msgid "Cancel" ++msgstr "Annulla" ++ ++#: fileItem.js:393 ++msgid "Open" ++msgstr "Apri" ++ ++#: fileItem.js:396 ++msgid "Cut" ++msgstr "Taglia" ++ ++#: fileItem.js:397 ++msgid "Copy" ++msgstr "Copia" ++ ++#: fileItem.js:398 ++msgid "Rename" ++msgstr "Rinomina" ++ ++#: fileItem.js:399 ++msgid "Move to Trash" ++msgstr "Sposta nel cestino" ++ ++#: fileItem.js:403 ++msgid "Empty Trash" ++msgstr "Svuota il cestino" ++ ++#: fileItem.js:409 ++msgid "Properties" ++msgstr "Proprietà" ++ ++#: fileItem.js:411 ++msgid "Show in Files" ++msgstr "Mostra in File" ++ ++#: schemas/org.gnome.shell.extensions.desktop-icons.gschema.xml:12 ++msgid "Icon size" ++msgstr "Dimensione dell'icona" ++ ++#: schemas/org.gnome.shell.extensions.desktop-icons.gschema.xml:13 ++msgid "Set the size for the desktop icons." ++msgstr "Imposta la grandezza delle icone della scrivania." ++ ++#: schemas/org.gnome.shell.extensions.desktop-icons.gschema.xml:17 ++msgid "Show personal folder" ++msgstr "Mostra la cartella personale" ++ ++#: schemas/org.gnome.shell.extensions.desktop-icons.gschema.xml:18 ++msgid "Show the personal folder in the desktop." ++msgstr "Mostra la cartella personale sulla scrivania." ++ ++#: schemas/org.gnome.shell.extensions.desktop-icons.gschema.xml:22 ++msgid "Show trash icon" ++msgstr "Mostra il cestino" ++ ++#: schemas/org.gnome.shell.extensions.desktop-icons.gschema.xml:23 ++msgid "Show the trash icon in the desktop." ++msgstr "Mostra il cestino sulla scrivania." +diff --git a/extensions/desktop-icons/po/meson.build b/extensions/desktop-icons/po/meson.build +new file mode 100644 +index 0000000..b2e9e42 +--- /dev/null ++++ b/extensions/desktop-icons/po/meson.build +@@ -0,0 +1 @@ ++i18n.gettext (meson.project_name (), preset: 'glib') +diff --git a/extensions/desktop-icons/po/pl.po b/extensions/desktop-icons/po/pl.po +new file mode 100644 +index 0000000..32ae8ef +--- /dev/null ++++ b/extensions/desktop-icons/po/pl.po +@@ -0,0 +1,157 @@ ++# Polish translation for desktop-icons. ++# Copyright © 2018-2019 the desktop-icons authors. ++# This file is distributed under the same license as the desktop-icons package. ++# Piotr Drąg , 2018-2019. ++# Aviary.pl , 2018-2019. ++# ++msgid "" ++msgstr "" ++"Project-Id-Version: desktop-icons\n" ++"Report-Msgid-Bugs-To: https://gitlab.gnome.org/World/ShellExtensions/desktop-" ++"icons/issues\n" ++"POT-Creation-Date: 2019-01-15 10:59+0000\n" ++"PO-Revision-Date: 2019-01-15 20:57+0100\n" ++"Last-Translator: Piotr Drąg \n" ++"Language-Team: Polish \n" ++"Language: pl\n" ++"MIME-Version: 1.0\n" ++"Content-Type: text/plain; charset=UTF-8\n" ++"Content-Transfer-Encoding: 8bit\n" ++"Plural-Forms: nplurals=3; plural=(n==1 ? 0 : n%10>=2 && n%10<=4 && (n%100<10 " ++"|| n%100>=20) ? 1 : 2);\n" ++ ++#: prefs.js:102 ++msgid "Size for the desktop icons" ++msgstr "Rozmiar ikon na pulpicie" ++ ++#: prefs.js:102 ++msgid "Small" ++msgstr "Mały" ++ ++#: prefs.js:102 ++msgid "Standard" ++msgstr "Standardowy" ++ ++#: prefs.js:102 ++msgid "Large" ++msgstr "Duży" ++ ++#: prefs.js:103 ++msgid "Show the personal folder in the desktop" ++msgstr "Katalog domowy na pulpicie" ++ ++#: prefs.js:104 ++msgid "Show the trash icon in the desktop" ++msgstr "Kosz na pulpicie" ++ ++#: desktopGrid.js:187 desktopGrid.js:306 ++msgid "New Folder" ++msgstr "Nowy katalog" ++ ++#: desktopGrid.js:308 ++msgid "Paste" ++msgstr "Wklej" ++ ++#: desktopGrid.js:309 ++msgid "Undo" ++msgstr "Cofnij" ++ ++#: desktopGrid.js:310 ++msgid "Redo" ++msgstr "Ponów" ++ ++#: desktopGrid.js:312 ++msgid "Show Desktop in Files" ++msgstr "Wyświetl pulpit w menedżerze plików" ++ ++#: desktopGrid.js:313 fileItem.js:586 ++msgid "Open in Terminal" ++msgstr "Otwórz w terminalu" ++ ++#: desktopGrid.js:315 ++msgid "Change Background…" ++msgstr "Zmień tło…" ++ ++#: desktopGrid.js:317 ++msgid "Display Settings" ++msgstr "Ustawienia ekranu" ++ ++#: desktopGrid.js:318 ++msgid "Settings" ++msgstr "Ustawienia" ++ ++#: desktopGrid.js:559 ++msgid "Enter file name…" ++msgstr "Nazwa pliku…" ++ ++#: desktopGrid.js:563 ++msgid "OK" ++msgstr "OK" ++ ++#: desktopGrid.js:569 ++msgid "Cancel" ++msgstr "Anuluj" ++ ++#: fileItem.js:490 ++msgid "Don’t Allow Launching" ++msgstr "Nie zezwalaj na uruchamianie" ++ ++#: fileItem.js:492 ++msgid "Allow Launching" ++msgstr "Zezwól na uruchamianie" ++ ++#: fileItem.js:559 ++msgid "Open" ++msgstr "Otwórz" ++ ++#: fileItem.js:562 ++msgid "Cut" ++msgstr "Wytnij" ++ ++#: fileItem.js:563 ++msgid "Copy" ++msgstr "Skopiuj" ++ ++#: fileItem.js:565 ++msgid "Rename…" ++msgstr "Zmień nazwę…" ++ ++#: fileItem.js:566 ++msgid "Move to Trash" ++msgstr "Przenieś do kosza" ++ ++#: fileItem.js:576 ++msgid "Empty Trash" ++msgstr "Opróżnij kosz" ++ ++#: fileItem.js:582 ++msgid "Properties" ++msgstr "Właściwości" ++ ++#: fileItem.js:584 ++msgid "Show in Files" ++msgstr "Wyświetl w menedżerze plików" ++ ++#: schemas/org.gnome.shell.extensions.desktop-icons.gschema.xml:11 ++msgid "Icon size" ++msgstr "Rozmiar ikon" ++ ++#: schemas/org.gnome.shell.extensions.desktop-icons.gschema.xml:12 ++msgid "Set the size for the desktop icons." ++msgstr "Ustawia rozmiar ikon na pulpicie." ++ ++#: schemas/org.gnome.shell.extensions.desktop-icons.gschema.xml:16 ++msgid "Show personal folder" ++msgstr "Katalog domowy" ++ ++#: schemas/org.gnome.shell.extensions.desktop-icons.gschema.xml:17 ++msgid "Show the personal folder in the desktop." ++msgstr "Wyświetla katalog domowy na pulpicie." ++ ++#: schemas/org.gnome.shell.extensions.desktop-icons.gschema.xml:21 ++msgid "Show trash icon" ++msgstr "Kosz" ++ ++#: schemas/org.gnome.shell.extensions.desktop-icons.gschema.xml:22 ++msgid "Show the trash icon in the desktop." ++msgstr "Wyświetla kosz na pulpicie." +diff --git a/extensions/desktop-icons/po/pt_BR.po b/extensions/desktop-icons/po/pt_BR.po +new file mode 100644 +index 0000000..42162f2 +--- /dev/null ++++ b/extensions/desktop-icons/po/pt_BR.po +@@ -0,0 +1,163 @@ ++# Brazilian Portuguese translation for desktop-icons. ++# Copyright (C) 2018 desktop-icons's COPYRIGHT HOLDER ++# This file is distributed under the same license as the desktop-icons package. ++# Enrico Nicoletto , 2018. ++# Rafael Fontenelle , 2018. ++msgid "" ++msgstr "" ++"Project-Id-Version: desktop-icons master\n" ++"Report-Msgid-Bugs-To: https://gitlab.gnome.org/World/ShellExtensions/" ++"desktop-icons/issues\n" ++"POT-Creation-Date: 2018-12-14 09:12+0000\n" ++"PO-Revision-Date: 2018-12-17 01:01-0200\n" ++"Last-Translator: Rafael Fontenelle \n" ++"Language-Team: Brazilian Portuguese \n" ++"Language: pt_BR\n" ++"MIME-Version: 1.0\n" ++"Content-Type: text/plain; charset=UTF-8\n" ++"Content-Transfer-Encoding: 8bit\n" ++"Plural-Forms: nplurals=2; plural=(n > 1);\n" ++"X-Generator: Virtaal 1.0.0-beta1\n" ++ ++#: prefs.js:102 ++msgid "Size for the desktop icons" ++msgstr "Tamanho para os ícones da área de trabalho" ++ ++#: prefs.js:102 ++msgid "Small" ++msgstr "Pequeno" ++ ++#: prefs.js:102 ++msgid "Standard" ++msgstr "Padrão" ++ ++#: prefs.js:102 ++msgid "Large" ++msgstr "Grande" ++ ++#: prefs.js:102 ++msgid "Huge" ++msgstr "Enorme" ++ ++#: prefs.js:103 ++msgid "Show the personal folder in the desktop" ++msgstr "Mostrar a pasta pessoal na área de trabalho" ++ ++#: prefs.js:104 ++msgid "Show the trash icon in the desktop" ++msgstr "Mostrar o ícone da lixeira na área de trabalho" ++ ++#: desktopGrid.js:182 desktopGrid.js:301 ++msgid "New Folder" ++msgstr "Nova pasta" ++ ++#: desktopGrid.js:303 ++msgid "Paste" ++msgstr "Colar" ++ ++#: desktopGrid.js:304 ++msgid "Undo" ++msgstr "Desfazer" ++ ++#: desktopGrid.js:305 ++msgid "Redo" ++msgstr "Refazer" ++ ++#: desktopGrid.js:307 ++msgid "Open Desktop in Files" ++msgstr "Abrir área de trabalho no Arquivos" ++ ++#: desktopGrid.js:308 ++msgid "Open Terminal" ++msgstr "Abrir terminal" ++ ++#: desktopGrid.js:310 ++msgid "Change Background…" ++msgstr "Alterar plano de fundo…" ++ ++#: desktopGrid.js:311 ++msgid "Display Settings" ++msgstr "Configurações de exibição" ++ ++#: desktopGrid.js:312 ++msgid "Settings" ++msgstr "Configurações" ++ ++#: desktopGrid.js:568 ++msgid "Enter file name…" ++msgstr "Insira um nome de arquivo…" ++ ++#: desktopGrid.js:572 ++msgid "OK" ++msgstr "OK" ++ ++#: desktopGrid.js:578 ++msgid "Cancel" ++msgstr "Cancelar" ++ ++#: fileItem.js:485 ++msgid "Don’t Allow Launching" ++msgstr "Não permitir iniciar" ++ ++#: fileItem.js:487 ++msgid "Allow Launching" ++msgstr "Permitir iniciar" ++ ++#: fileItem.js:550 ++msgid "Open" ++msgstr "Abrir" ++ ++#: fileItem.js:553 ++msgid "Cut" ++msgstr "Recortar" ++ ++#: fileItem.js:554 ++msgid "Copy" ++msgstr "Copiar" ++ ++#: fileItem.js:556 ++msgid "Rename" ++msgstr "Renomear" ++ ++#: fileItem.js:557 ++msgid "Move to Trash" ++msgstr "Mover para a lixeira" ++ ++#: fileItem.js:567 ++msgid "Empty Trash" ++msgstr "Esvaziar lixeira" ++ ++#: fileItem.js:573 ++msgid "Properties" ++msgstr "Propriedades" ++ ++#: fileItem.js:575 ++msgid "Show in Files" ++msgstr "Mostrar no Arquivos" ++ ++#: schemas/org.gnome.shell.extensions.desktop-icons.gschema.xml:12 ++msgid "Icon size" ++msgstr "Tamanho do ícone" ++ ++#: schemas/org.gnome.shell.extensions.desktop-icons.gschema.xml:13 ++msgid "Set the size for the desktop icons." ++msgstr "Define o tamanho para os ícones da área de trabalho." ++ ++#: schemas/org.gnome.shell.extensions.desktop-icons.gschema.xml:17 ++msgid "Show personal folder" ++msgstr "Mostrar pasta pessoal" ++ ++#: schemas/org.gnome.shell.extensions.desktop-icons.gschema.xml:18 ++msgid "Show the personal folder in the desktop." ++msgstr "Mostra a pasta pessoal na área de trabalho." ++ ++#: schemas/org.gnome.shell.extensions.desktop-icons.gschema.xml:22 ++msgid "Show trash icon" ++msgstr "Mostrar ícone da lixeira" ++ ++#: schemas/org.gnome.shell.extensions.desktop-icons.gschema.xml:23 ++msgid "Show the trash icon in the desktop." ++msgstr "Mostra o ícone da lixeira na área de trabalho." ++ ++#~ msgid "Ok" ++#~ msgstr "Ok" +diff --git a/extensions/desktop-icons/po/ru.po b/extensions/desktop-icons/po/ru.po +new file mode 100644 +index 0000000..4094f16 +--- /dev/null ++++ b/extensions/desktop-icons/po/ru.po +@@ -0,0 +1,153 @@ ++# SOME DESCRIPTIVE TITLE. ++# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER ++# This file is distributed under the same license as the PACKAGE package. ++# Eaglers , 2018. ++# ++msgid "" ++msgstr "" ++"Project-Id-Version: \n" ++"Report-Msgid-Bugs-To: https://gitlab.gnome.org/World/ShellExtensions/desktop-" ++"icons/issues\n" ++"POT-Creation-Date: 2018-11-22 08:42+0000\n" ++"PO-Revision-Date: 2018-11-22 22:02+0300\n" ++"Last-Translator: Stas Solovey \n" ++"Language-Team: Russian \n" ++"Language: ru\n" ++"MIME-Version: 1.0\n" ++"Content-Type: text/plain; charset=UTF-8\n" ++"Content-Transfer-Encoding: 8bit\n" ++"Plural-Forms: nplurals=3; plural=(n%10==1 && n%100!=11 ? 0 : n%10>=2 && n" ++"%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2);\n" ++"X-Generator: Poedit 2.2\n" ++ ++#: prefs.js:89 ++msgid "Size for the desktop icons" ++msgstr "Размер значков" ++ ++#: prefs.js:89 ++msgid "Small" ++msgstr "Маленький" ++ ++#: prefs.js:89 ++msgid "Standard" ++msgstr "Стандартный" ++ ++#: prefs.js:89 ++msgid "Large" ++msgstr "Большой" ++ ++#: prefs.js:89 ++msgid "Huge" ++msgstr "Огромный" ++ ++#: prefs.js:90 ++msgid "Show the personal folder in the desktop" ++msgstr "Показывать домашнюю папку на рабочем столе" ++ ++#: prefs.js:91 ++msgid "Show the trash icon in the desktop" ++msgstr "Показывать «Корзину» на рабочем столе" ++ ++#: desktopGrid.js:185 desktopGrid.js:304 ++msgid "New Folder" ++msgstr "Создать папку" ++ ++#: desktopGrid.js:306 ++msgid "Paste" ++msgstr "Вставить" ++ ++#: desktopGrid.js:307 ++msgid "Undo" ++msgstr "Отменить" ++ ++#: desktopGrid.js:308 ++msgid "Redo" ++msgstr "Повторить" ++ ++#: desktopGrid.js:310 ++msgid "Open Desktop in Files" ++msgstr "Открыть «Рабочий стол» в «Файлах»" ++ ++#: desktopGrid.js:311 ++msgid "Open Terminal" ++msgstr "Открыть терминал" ++ ++#: desktopGrid.js:313 ++msgid "Change Background…" ++msgstr "Изменить фон…" ++ ++#: desktopGrid.js:314 ++msgid "Display Settings" ++msgstr "Настройки дисплея" ++ ++#: desktopGrid.js:315 ++msgid "Settings" ++msgstr "Параметры" ++ ++#: desktopGrid.js:569 ++msgid "Enter file name…" ++msgstr "Ввести имя файла…" ++ ++#: desktopGrid.js:573 ++msgid "Ok" ++msgstr "ОК" ++ ++#: desktopGrid.js:579 ++msgid "Cancel" ++msgstr "Отмена" ++ ++#: fileItem.js:390 ++msgid "Open" ++msgstr "Открыть" ++ ++#: fileItem.js:393 ++msgid "Cut" ++msgstr "Вырезать" ++ ++#: fileItem.js:394 ++msgid "Copy" ++msgstr "Вставить" ++ ++#: fileItem.js:395 ++msgid "Rename" ++msgstr "Переименовать" ++ ++#: fileItem.js:396 ++msgid "Move to Trash" ++msgstr "Переместить в корзину" ++ ++#: fileItem.js:400 ++msgid "Empty trash" ++msgstr "Очистить корзину" ++ ++#: fileItem.js:406 ++msgid "Properties" ++msgstr "Свойства" ++ ++#: fileItem.js:408 ++msgid "Show in Files" ++msgstr "Показать в «Файлах»" ++ ++#: schemas/org.gnome.shell.extensions.desktop-icons.gschema.xml:12 ++msgid "Icon size" ++msgstr "Размер значков" ++ ++#: schemas/org.gnome.shell.extensions.desktop-icons.gschema.xml:13 ++msgid "Set the size for the desktop icons." ++msgstr "Установить размер значков на рабочем столе." ++ ++#: schemas/org.gnome.shell.extensions.desktop-icons.gschema.xml:17 ++msgid "Show personal folder" ++msgstr "Показывать домашнюю папку" ++ ++#: schemas/org.gnome.shell.extensions.desktop-icons.gschema.xml:18 ++msgid "Show the personal folder in the desktop." ++msgstr "Показывать значок домашней папки на рабочем столе." ++ ++#: schemas/org.gnome.shell.extensions.desktop-icons.gschema.xml:22 ++msgid "Show trash icon" ++msgstr "Показывать значок корзины" ++ ++#: schemas/org.gnome.shell.extensions.desktop-icons.gschema.xml:23 ++msgid "Show the trash icon in the desktop." ++msgstr "Показывать значок корзины на рабочем столе." +diff --git a/extensions/desktop-icons/po/zh_TW.po b/extensions/desktop-icons/po/zh_TW.po +new file mode 100644 +index 0000000..8ce4ab9 +--- /dev/null ++++ b/extensions/desktop-icons/po/zh_TW.po +@@ -0,0 +1,135 @@ ++# Chinese (Taiwan) translation for desktop-icons. ++# Copyright (C) 2018 desktop-icons's COPYRIGHT HOLDER ++# This file is distributed under the same license as the desktop-icons package. ++# Yi-Jyun Pan , 2018. ++# ++msgid "" ++msgstr "" ++"Project-Id-Version: desktop-icons master\n" ++"Report-Msgid-Bugs-To: https://gitlab.gnome.org/World/ShellExtensions/desktop-" ++"icons/issues\n" ++"POT-Creation-Date: 2018-10-22 14:12+0000\n" ++"PO-Revision-Date: 2018-10-24 21:31+0800\n" ++"Language-Team: Chinese (Taiwan) \n" ++"Language: zh_TW\n" ++"MIME-Version: 1.0\n" ++"Content-Type: text/plain; charset=UTF-8\n" ++"Content-Transfer-Encoding: 8bit\n" ++"Last-Translator: pan93412 \n" ++"X-Generator: Poedit 2.2\n" ++ ++#: prefs.js:89 ++msgid "Size for the desktop icons" ++msgstr "桌面圖示的大小" ++ ++#: prefs.js:89 ++msgid "Small" ++msgstr "小圖示" ++ ++#: prefs.js:89 ++msgid "Standard" ++msgstr "標準大小圖示" ++ ++#: prefs.js:89 ++msgid "Large" ++msgstr "大圖示" ++ ++#: prefs.js:89 ++msgid "Huge" ++msgstr "巨大圖示" ++ ++#: prefs.js:90 ++msgid "Show the personal folder in the desktop" ++msgstr "在桌面顯示個人資料夾" ++ ++#: prefs.js:91 ++msgid "Show the trash icon in the desktop" ++msgstr "在桌面顯示垃圾桶圖示" ++ ++#: desktopGrid.js:178 desktopGrid.js:297 ++msgid "New Folder" ++msgstr "新增資料夾" ++ ++#: desktopGrid.js:299 ++msgid "Paste" ++msgstr "貼上" ++ ++#: desktopGrid.js:300 ++msgid "Undo" ++msgstr "復原" ++ ++#: desktopGrid.js:301 ++msgid "Redo" ++msgstr "重做" ++ ++#: desktopGrid.js:303 ++msgid "Open Desktop in Files" ++msgstr "在《檔案》中開啟桌面" ++ ++#: desktopGrid.js:304 ++msgid "Open Terminal" ++msgstr "開啟終端器" ++ ++#: desktopGrid.js:306 ++msgid "Change Background…" ++msgstr "變更背景圖片…" ++ ++#: desktopGrid.js:307 ++msgid "Display Settings" ++msgstr "顯示設定" ++ ++#: desktopGrid.js:308 ++msgid "Settings" ++msgstr "設定" ++ ++#: fileItem.js:223 ++msgid "Open" ++msgstr "開啟" ++ ++#: fileItem.js:226 ++msgid "Cut" ++msgstr "剪下" ++ ++#: fileItem.js:227 ++msgid "Copy" ++msgstr "複製" ++ ++#: fileItem.js:228 ++msgid "Move to Trash" ++msgstr "移動到垃圾桶" ++ ++#: fileItem.js:232 ++msgid "Empty trash" ++msgstr "清空回收桶" ++ ++#: fileItem.js:238 ++msgid "Properties" ++msgstr "屬性" ++ ++#: fileItem.js:240 ++msgid "Show in Files" ++msgstr "在《檔案》中顯示" ++ ++#: schemas/org.gnome.shell.extensions.desktop-icons.gschema.xml:12 ++msgid "Icon size" ++msgstr "圖示大小" ++ ++#: schemas/org.gnome.shell.extensions.desktop-icons.gschema.xml:13 ++msgid "Set the size for the desktop icons." ++msgstr "設定桌面圖示的大小。" ++ ++#: schemas/org.gnome.shell.extensions.desktop-icons.gschema.xml:17 ++msgid "Show personal folder" ++msgstr "顯示個人資料夾" ++ ++#: schemas/org.gnome.shell.extensions.desktop-icons.gschema.xml:18 ++msgid "Show the personal folder in the desktop." ++msgstr "在桌面顯示個人資料夾。" ++ ++#: schemas/org.gnome.shell.extensions.desktop-icons.gschema.xml:22 ++msgid "Show trash icon" ++msgstr "顯示垃圾桶圖示" ++ ++#: schemas/org.gnome.shell.extensions.desktop-icons.gschema.xml:23 ++msgid "Show the trash icon in the desktop." ++msgstr "在桌面顯示垃圾桶圖示。" +diff --git a/extensions/desktop-icons/prefs.js b/extensions/desktop-icons/prefs.js +new file mode 100644 +index 0000000..0a5b1ce +--- /dev/null ++++ b/extensions/desktop-icons/prefs.js +@@ -0,0 +1,159 @@ ++ ++/* Desktop Icons GNOME Shell extension ++ * ++ * Copyright (C) 2017 Carlos Soriano ++ * ++ * This program is free software: you can redistribute it and/or modify ++ * it under the terms of the GNU General Public License as published by ++ * the Free Software Foundation, either version 3 of the License, or ++ * (at your option) any later version. ++ * ++ * This program is distributed in the hope that it will be useful, ++ * but WITHOUT ANY WARRANTY; without even the implied warranty of ++ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ++ * GNU General Public License for more details. ++ * ++ * You should have received a copy of the GNU General Public License ++ * along with this program. If not, see . ++ */ ++ ++const Gtk = imports.gi.Gtk; ++const GObject = imports.gi.GObject; ++const Gio = imports.gi.Gio; ++const GioSSS = Gio.SettingsSchemaSource; ++const ExtensionUtils = imports.misc.extensionUtils; ++const Gettext = imports.gettext; ++ ++const Config = imports.misc.config; ++ ++var _ = Gettext.domain('desktop-icons').gettext; ++ ++const SCHEMA_NAUTILUS = 'org.gnome.nautilus.preferences'; ++const SCHEMA_GTK = 'org.gtk.Settings.FileChooser'; ++const SCHEMA = 'org.gnome.shell.extensions.desktop-icons'; ++ ++const ICON_SIZE = { 'small': 48, 'standard': 64, 'large': 96 }; ++const ICON_WIDTH = { 'small': 120, 'standard': 128, 'large': 128 }; ++const ICON_HEIGHT = { 'small': 98, 'standard': 114, 'large': 146 }; ++ ++var FileType = { ++ NONE: null, ++ USER_DIRECTORY_HOME: 'show-home', ++ USER_DIRECTORY_TRASH: 'show-trash', ++} ++ ++var nautilusSettings; ++var gtkSettings; ++var settings; ++// This is already in Nautilus settings, so it should not be made tweakable here ++var CLICK_POLICY_SINGLE = false; ++ ++function initTranslations() { ++ let extension = ExtensionUtils.getCurrentExtension(); ++ ++ let localedir = extension.dir.get_child('locale'); ++ if (localedir.query_exists(null)) ++ Gettext.bindtextdomain('desktop-icons', localedir.get_path()); ++ else ++ Gettext.bindtextdomain('desktop-icons', Config.LOCALEDIR); ++} ++ ++function init() { ++ let schemaSource = GioSSS.get_default(); ++ let schemaGtk = schemaSource.lookup(SCHEMA_GTK, true); ++ gtkSettings = new Gio.Settings({ settings_schema: schemaGtk }); ++ let schemaObj = schemaSource.lookup(SCHEMA_NAUTILUS, true); ++ if (!schemaObj) { ++ nautilusSettings = null; ++ } else { ++ nautilusSettings = new Gio.Settings({ settings_schema: schemaObj });; ++ nautilusSettings.connect('changed', _onNautilusSettingsChanged); ++ _onNautilusSettingsChanged(); ++ } ++ settings = get_schema(SCHEMA); ++} ++ ++function get_schema(schema) { ++ let extension = ExtensionUtils.getCurrentExtension(); ++ ++ // check if this extension was built with "make zip-file", and thus ++ // has the schema files in a subfolder ++ // otherwise assume that extension has been installed in the ++ // same prefix as gnome-shell (and therefore schemas are available ++ // in the standard folders) ++ let schemaDir = extension.dir.get_child('schemas'); ++ let schemaSource; ++ if (schemaDir.query_exists(null)) ++ schemaSource = GioSSS.new_from_directory(schemaDir.get_path(), GioSSS.get_default(), false); ++ else ++ schemaSource = GioSSS.get_default(); ++ ++ let schemaObj = schemaSource.lookup(schema, true); ++ if (!schemaObj) ++ throw new Error('Schema ' + schema + ' could not be found for extension ' + extension.metadata.uuid + '. Please check your installation.'); ++ ++ return new Gio.Settings({ settings_schema: schemaObj }); ++} ++ ++function buildPrefsWidget() { ++ ++ let frame = new Gtk.Box({ orientation: Gtk.Orientation.VERTICAL, border_width: 10, spacing: 10 }); ++ ++ frame.add(buildSelector('icon-size', _("Size for the desktop icons"), { 'small': _("Small"), 'standard': _("Standard"), 'large': _("Large") })); ++ frame.add(buildSwitcher('show-home', _("Show the personal folder in the desktop"))); ++ frame.add(buildSwitcher('show-trash', _("Show the trash icon in the desktop"))); ++ frame.show_all(); ++ return frame; ++} ++ ++function buildSwitcher(key, labelText) { ++ let hbox = new Gtk.Box({ orientation: Gtk.Orientation.HORIZONTAL, spacing: 10 }); ++ let label = new Gtk.Label({ label: labelText, xalign: 0 }); ++ let switcher = new Gtk.Switch({ active: settings.get_boolean(key) }); ++ settings.bind(key, switcher, 'active', 3); ++ hbox.pack_start(label, true, true, 0); ++ hbox.add(switcher); ++ return hbox; ++} ++ ++function buildSelector(key, labelText, elements) { ++ let listStore = new Gtk.ListStore(); ++ listStore.set_column_types ([GObject.TYPE_STRING, GObject.TYPE_STRING]); ++ let schemaKey = settings.settings_schema.get_key(key); ++ let values = schemaKey.get_range().get_child_value(1).get_child_value(0).get_strv(); ++ for (let val of values) { ++ let iter = listStore.append(); ++ let visibleText = val; ++ if (visibleText in elements) ++ visibleText = elements[visibleText]; ++ listStore.set (iter, [0, 1], [visibleText, val]); ++ } ++ let hbox = new Gtk.Box({ orientation: Gtk.Orientation.HORIZONTAL, spacing: 10 }); ++ let label = new Gtk.Label({ label: labelText, xalign: 0 }); ++ let combo = new Gtk.ComboBox({model: listStore}); ++ let rendererText = new Gtk.CellRendererText(); ++ combo.pack_start (rendererText, false); ++ combo.add_attribute (rendererText, 'text', 0); ++ combo.set_id_column(1); ++ settings.bind(key, combo, 'active-id', 3); ++ hbox.pack_start(label, true, true, 0); ++ hbox.add(combo); ++ return hbox; ++} ++ ++function _onNautilusSettingsChanged() { ++ CLICK_POLICY_SINGLE = nautilusSettings.get_string('click-policy') == 'single'; ++} ++ ++function get_icon_size() { ++ // this one doesn't need scaling because Gnome Shell automagically scales the icons ++ return ICON_SIZE[settings.get_string('icon-size')]; ++} ++ ++function get_desired_width(scale_factor) { ++ return ICON_WIDTH[settings.get_string('icon-size')] * scale_factor; ++} ++ ++function get_desired_height(scale_factor) { ++ return ICON_HEIGHT[settings.get_string('icon-size')] * scale_factor; ++} +diff --git a/extensions/desktop-icons/schemas/meson.build b/extensions/desktop-icons/schemas/meson.build +new file mode 100644 +index 0000000..2b17916 +--- /dev/null ++++ b/extensions/desktop-icons/schemas/meson.build +@@ -0,0 +1,6 @@ ++gnome.compile_schemas() ++ ++install_data( ++ 'org.gnome.shell.extensions.desktop-icons.gschema.xml', ++ install_dir : schema_dir ++) +diff --git a/extensions/desktop-icons/schemas/org.gnome.shell.extensions.desktop-icons.gschema.xml b/extensions/desktop-icons/schemas/org.gnome.shell.extensions.desktop-icons.gschema.xml +new file mode 100644 +index 0000000..bb4e50f +--- /dev/null ++++ b/extensions/desktop-icons/schemas/org.gnome.shell.extensions.desktop-icons.gschema.xml +@@ -0,0 +1,25 @@ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ 'standard' ++ Icon size ++ Set the size for the desktop icons. ++ ++ ++ true ++ Show personal folder ++ Show the personal folder in the desktop. ++ ++ ++ true ++ Show trash icon ++ Show the trash icon in the desktop. ++ ++ ++ +diff --git a/extensions/desktop-icons/stylesheet.css b/extensions/desktop-icons/stylesheet.css +new file mode 100644 +index 0000000..82209d7 +--- /dev/null ++++ b/extensions/desktop-icons/stylesheet.css +@@ -0,0 +1,33 @@ ++.file-item { ++ padding: 4px; ++ border: 1px; ++ margin: 1px; ++} ++ ++.file-item:hover { ++ background-color: rgba(238, 238, 238, 0.2); ++} ++ ++.file-item:selected { ++ background-color: rgba(74, 144, 217, 0.6); ++ border-color: rgba(74, 144, 217, 0.8); ++} ++ ++.rubber-band { ++ background-color: rgba(74, 144, 238, 0.4); ++} ++ ++.name-label { ++ text-shadow: 1px 1px black; ++ color: white; ++ text-align: center; ++} ++ ++.draggable { ++ background-color: red; ++} ++ ++.rename-popup { ++ min-width: 300px; ++ margin: 6px; ++} +diff --git a/meson.build b/meson.build +index 201c484..99a4738 100644 +--- a/meson.build ++++ b/meson.build +@@ -36,6 +36,7 @@ uuid_suffix = '@gnome-shell-extensions.gcampax.github.com' + classic_extensions = [ + 'alternate-tab', + 'apps-menu', ++ 'desktop-icons', + 'places-menu', + 'launch-new-instance', + 'window-list' +diff --git a/po/cs.po b/po/cs.po +index 04bb195..e8f2fa3 100644 +--- a/po/cs.po ++++ b/po/cs.po +@@ -1,11 +1,28 @@ ++# #-#-#-#-# cs.po (gnome-shell-extensions) #-#-#-#-# ++# #-#-#-#-# cs.po (gnome-shell-extensions) #-#-#-#-# + # Czech translation for gnome-shell-extensions. + # Copyright (C) 2011 gnome-shell-extensions's COPYRIGHT HOLDER + # This file is distributed under the same license as the gnome-shell-extensions package. + # Petr Kovar , 2013. + # Marek Černocký , 2011, 2012, 2013, 2014, 2015, 2017. + # ++# #-#-#-#-# cs.po (desktop-icons master) #-#-#-#-# ++# Czech translation for desktop-icons. ++# Copyright (C) 2018 desktop-icons's COPYRIGHT HOLDER ++# This file is distributed under the same license as the desktop-icons package. ++# Marek Černocký , 2018. ++# ++# #-#-#-#-# cs.po (desktop-icons master) #-#-#-#-# ++# Czech translation for desktop-icons. ++# Copyright (C) 2018 desktop-icons's COPYRIGHT HOLDER ++# This file is distributed under the same license as the desktop-icons package. ++# Marek Černocký , 2018. ++# ++#, fuzzy + msgid "" + msgstr "" ++"#-#-#-#-# cs.po (gnome-shell-extensions) #-#-#-#-#\n" ++"#-#-#-#-# cs.po (gnome-shell-extensions) #-#-#-#-#\n" + "Project-Id-Version: gnome-shell-extensions\n" + "Report-Msgid-Bugs-To: https://bugzilla.gnome.org/enter_bug.cgi?product=gnome-" + "shell&keywords=I18N+L10N&component=extensions\n" +@@ -19,6 +36,34 @@ msgstr "" + "Content-Transfer-Encoding: 8bit\n" + "Plural-Forms: nplurals=3; plural=(n==1) ? 0 : (n>=2 && n<=4) ? 1 : 2;\n" + "X-Generator: Gtranslator 2.91.6\n" ++"#-#-#-#-# cs.po (desktop-icons master) #-#-#-#-#\n" ++"Project-Id-Version: desktop-icons master\n" ++"Report-Msgid-Bugs-To: https://gitlab.gnome.org/World/ShellExtensions/desktop-" ++"icons/issues\n" ++"POT-Creation-Date: 2018-10-01 20:15+0000\n" ++"PO-Revision-Date: 2018-10-02 11:10+0200\n" ++"Last-Translator: Marek Černocký \n" ++"Language-Team: čeština \n" ++"Language: cs\n" ++"MIME-Version: 1.0\n" ++"Content-Type: text/plain; charset=UTF-8\n" ++"Content-Transfer-Encoding: 8bit\n" ++"Plural-Forms: nplurals=3; plural=(n==1) ? 0 : (n>=2 && n<=4) ? 1 : 2;\n" ++"X-Generator: Gtranslator 2.91.7\n" ++"#-#-#-#-# cs.po (desktop-icons master) #-#-#-#-#\n" ++"Project-Id-Version: desktop-icons master\n" ++"Report-Msgid-Bugs-To: https://gitlab.gnome.org/World/ShellExtensions/desktop-" ++"icons/issues\n" ++"POT-Creation-Date: 2018-10-01 20:15+0000\n" ++"PO-Revision-Date: 2018-10-02 11:10+0200\n" ++"Last-Translator: Marek Černocký \n" ++"Language-Team: čeština \n" ++"Language: cs\n" ++"MIME-Version: 1.0\n" ++"Content-Type: text/plain; charset=UTF-8\n" ++"Content-Transfer-Encoding: 8bit\n" ++"Plural-Forms: nplurals=3; plural=(n==1) ? 0 : (n>=2 && n<=4) ? 1 : 2;\n" ++"X-Generator: Gtranslator 2.91.7\n" + + #: data/gnome-classic.desktop.in:3 data/gnome-classic.session.desktop.in:3 + msgid "GNOME Classic" +@@ -350,3 +395,119 @@ msgstr "Název" + #, javascript-format + msgid "Workspace %d" + msgstr "Pracovní plocha %d" ++ ++#: prefs.js:89 ++msgid "Size for the desktop icons" ++msgstr "Velikost ikon na pracovní ploše" ++ ++#: prefs.js:89 ++msgid "Small" ++msgstr "malé" ++ ++#: prefs.js:89 ++msgid "Standard" ++msgstr "standardní" ++ ++#: prefs.js:89 ++msgid "Large" ++msgstr "velké" ++ ++#: prefs.js:89 ++msgid "Huge" ++msgstr "obrovské" ++ ++#: prefs.js:90 ++msgid "Show the personal folder in the desktop" ++msgstr "Zobrazovat osobní složku na pracovní ploše" ++ ++#: prefs.js:91 ++msgid "Show the trash icon in the desktop" ++msgstr "Zobrazovat ikonu koše na pracovní ploše" ++ ++#: desktopGrid.js:178 desktopGrid.js:297 ++msgid "New Folder" ++msgstr "Nová složka" ++ ++#: desktopGrid.js:299 ++msgid "Paste" ++msgstr "Vložit" ++ ++#: desktopGrid.js:300 ++msgid "Undo" ++msgstr "Zpět" ++ ++#: desktopGrid.js:301 ++msgid "Redo" ++msgstr "Znovu" ++ ++#: desktopGrid.js:303 ++msgid "Open Desktop in Files" ++msgstr "Otevřít plochu v Souborech" ++ ++#: desktopGrid.js:304 ++msgid "Open Terminal" ++msgstr "Otevřít terminál" ++ ++#: desktopGrid.js:306 ++msgid "Change Background…" ++msgstr "Změnit pozadí…" ++ ++#: desktopGrid.js:307 ++msgid "Display Settings" ++msgstr "Zobrazit nastavení" ++ ++#: desktopGrid.js:308 ++msgid "Settings" ++msgstr "Nastavení" ++ ++#: fileItem.js:226 ++msgid "Open" ++msgstr "Otevřít" ++ ++#: fileItem.js:229 ++msgid "Cut" ++msgstr "Vyjmout" ++ ++#: fileItem.js:230 ++msgid "Copy" ++msgstr "Kopírovat" ++ ++#: fileItem.js:231 ++msgid "Move to Trash" ++msgstr "Přesunout do koše" ++ ++#: fileItem.js:235 ++msgid "Empty trash" ++msgstr "Vyprázdnit koš" ++ ++#: fileItem.js:241 ++msgid "Properties" ++msgstr "Vlastnosti" ++ ++#: fileItem.js:243 ++msgid "Show in Files" ++msgstr "Zobrazit v Souborech" ++ ++#: schemas/org.gnome.shell.extensions.desktop-icons.gschema.xml:12 ++msgid "Icon size" ++msgstr "Velikost ikon" ++ ++#: schemas/org.gnome.shell.extensions.desktop-icons.gschema.xml:13 ++msgid "Set the size for the desktop icons." ++msgstr "Nastavit velikost pro ikony na pracovní ploše." ++ ++#: schemas/org.gnome.shell.extensions.desktop-icons.gschema.xml:17 ++msgid "Show personal folder" ++msgstr "Zobrazovat osobní složku" ++ ++#: schemas/org.gnome.shell.extensions.desktop-icons.gschema.xml:18 ++msgid "Show the personal folder in the desktop." ++msgstr "Zobrazovat osobní složku na pracovní ploše." ++ ++#: schemas/org.gnome.shell.extensions.desktop-icons.gschema.xml:22 ++msgid "Show trash icon" ++msgstr "Zobrazovat koš" ++ ++#: schemas/org.gnome.shell.extensions.desktop-icons.gschema.xml:23 ++msgid "Show the trash icon in the desktop." ++msgstr "Zobrazovat ikonu koše na pracovní ploše." +diff --git a/po/da.po b/po/da.po +index 5a4c257..eeeef12 100644 +--- a/po/da.po ++++ b/po/da.po +@@ -1,3 +1,5 @@ ++# #-#-#-#-# da.po (gnome-shell-extensions master) #-#-#-#-# ++# #-#-#-#-# da.po (gnome-shell-extensions master) #-#-#-#-# + # Danish translation for gnome-shell-extensions. + # Copyright (C) 2011-2017 gnome-shell-extensions's COPYRIGHT HOLDER + # This file is distributed under the same license as the gnome-shell-extensions package. +@@ -6,8 +8,23 @@ + # Ask Hjorth Larsen , 2015, 2017. + # Joe Hansen , 2017. + # ++# #-#-#-#-# da.po (desktop-icons master) #-#-#-#-# ++# Danish translation for desktop-icons. ++# Copyright (C) 2018 desktop-icons's COPYRIGHT HOLDER ++# This file is distributed under the same license as the desktop-icons package. ++# Alan Mortensen , 2018. ++# ++# #-#-#-#-# da.po (desktop-icons master) #-#-#-#-# ++# Danish translation for desktop-icons. ++# Copyright (C) 2018 desktop-icons's COPYRIGHT HOLDER ++# This file is distributed under the same license as the desktop-icons package. ++# Alan Mortensen , 2018. ++# ++#, fuzzy + msgid "" + msgstr "" ++"#-#-#-#-# da.po (gnome-shell-extensions master) #-#-#-#-#\n" ++"#-#-#-#-# da.po (gnome-shell-extensions master) #-#-#-#-#\n" + "Project-Id-Version: gnome-shell-extensions master\n" + "Report-Msgid-Bugs-To: https://bugzilla.gnome.org/enter_bug.cgi?product=gnome-" + "shell&keywords=I18N+L10N&component=extensions\n" +@@ -20,6 +37,34 @@ msgstr "" + "Content-Type: text/plain; charset=UTF-8\n" + "Content-Transfer-Encoding: 8bit\n" + "Plural-Forms: nplurals=2; plural=(n != 1);\n" ++"#-#-#-#-# da.po (desktop-icons master) #-#-#-#-#\n" ++"Project-Id-Version: desktop-icons master\n" ++"Report-Msgid-Bugs-To: https://gitlab.gnome.org/World/ShellExtensions/desktop-" ++"icons/issues\n" ++"POT-Creation-Date: 2018-10-26 12:04+0000\n" ++"PO-Revision-Date: 2018-10-27 16:42+0200\n" ++"Language-Team: Danish \n" ++"Language: da\n" ++"MIME-Version: 1.0\n" ++"Content-Type: text/plain; charset=UTF-8\n" ++"Content-Transfer-Encoding: 8bit\n" ++"Plural-Forms: nplurals=2; plural=(n != 1);\n" ++"Last-Translator: Alan Mortensen \n" ++"X-Generator: Poedit 2.0.6\n" ++"#-#-#-#-# da.po (desktop-icons master) #-#-#-#-#\n" ++"Project-Id-Version: desktop-icons master\n" ++"Report-Msgid-Bugs-To: https://gitlab.gnome.org/World/ShellExtensions/desktop-" ++"icons/issues\n" ++"POT-Creation-Date: 2018-10-26 12:04+0000\n" ++"PO-Revision-Date: 2018-10-27 16:42+0200\n" ++"Language-Team: Danish \n" ++"Language: da\n" ++"MIME-Version: 1.0\n" ++"Content-Type: text/plain; charset=UTF-8\n" ++"Content-Transfer-Encoding: 8bit\n" ++"Plural-Forms: nplurals=2; plural=(n != 1);\n" ++"Last-Translator: Alan Mortensen \n" ++"X-Generator: Poedit 2.0.6\n" + + #: data/gnome-classic.desktop.in:3 data/gnome-classic.session.desktop.in:3 + msgid "GNOME Classic" +@@ -359,3 +404,119 @@ msgstr "Navn" + #, javascript-format + msgid "Workspace %d" + msgstr "Arbejdsområde %d" ++ ++#: prefs.js:89 ++msgid "Size for the desktop icons" ++msgstr "Størrelsen på skrivebordsikoner" ++ ++#: prefs.js:89 ++msgid "Small" ++msgstr "Små" ++ ++#: prefs.js:89 ++msgid "Standard" ++msgstr "Standard" ++ ++#: prefs.js:89 ++msgid "Large" ++msgstr "Store" ++ ++#: prefs.js:89 ++msgid "Huge" ++msgstr "Enorme" ++ ++#: prefs.js:90 ++msgid "Show the personal folder in the desktop" ++msgstr "Vis den personlige mappe på skrivebordet" ++ ++#: prefs.js:91 ++msgid "Show the trash icon in the desktop" ++msgstr "Vis papirkurvsikonet på skrivebordet" ++ ++#: desktopGrid.js:178 desktopGrid.js:297 ++msgid "New Folder" ++msgstr "Ny mappe" ++ ++#: desktopGrid.js:299 ++msgid "Paste" ++msgstr "Indsæt" ++ ++#: desktopGrid.js:300 ++msgid "Undo" ++msgstr "Fortryd" ++ ++#: desktopGrid.js:301 ++msgid "Redo" ++msgstr "Omgør" ++ ++#: desktopGrid.js:303 ++msgid "Open Desktop in Files" ++msgstr "Åbn skrivebordet i Filer" ++ ++#: desktopGrid.js:304 ++msgid "Open Terminal" ++msgstr "Åbn Terminal" ++ ++#: desktopGrid.js:306 ++msgid "Change Background…" ++msgstr "Skift baggrund …" ++ ++#: desktopGrid.js:307 ++msgid "Display Settings" ++msgstr "Skærmindstillinger" ++ ++#: desktopGrid.js:308 ++msgid "Settings" ++msgstr "Indstillinger" ++ ++#: fileItem.js:385 ++msgid "Open" ++msgstr "Åbn" ++ ++#: fileItem.js:388 ++msgid "Cut" ++msgstr "Klip" ++ ++#: fileItem.js:389 ++msgid "Copy" ++msgstr "Kopiér" ++ ++#: fileItem.js:390 ++msgid "Move to Trash" ++msgstr "Flyt til papirkurven" ++ ++#: fileItem.js:394 ++msgid "Empty trash" ++msgstr "Tøm papirkurven" ++ ++#: fileItem.js:400 ++msgid "Properties" ++msgstr "Egenskaber" ++ ++#: fileItem.js:402 ++msgid "Show in Files" ++msgstr "Vis i Filer" ++ ++#: schemas/org.gnome.shell.extensions.desktop-icons.gschema.xml:12 ++msgid "Icon size" ++msgstr "Ikonstørrelse" ++ ++#: schemas/org.gnome.shell.extensions.desktop-icons.gschema.xml:13 ++msgid "Set the size for the desktop icons." ++msgstr "Angiv størrelsen på skrivebordsikoner." ++ ++#: schemas/org.gnome.shell.extensions.desktop-icons.gschema.xml:17 ++msgid "Show personal folder" ++msgstr "Vis personlig mappe" ++ ++#: schemas/org.gnome.shell.extensions.desktop-icons.gschema.xml:18 ++msgid "Show the personal folder in the desktop." ++msgstr "Vis den personlige mappe på skrivebordet." ++ ++#: schemas/org.gnome.shell.extensions.desktop-icons.gschema.xml:22 ++msgid "Show trash icon" ++msgstr "Vis papirkurvsikon" ++ ++#: schemas/org.gnome.shell.extensions.desktop-icons.gschema.xml:23 ++msgid "Show the trash icon in the desktop." ++msgstr "Vis papirkurvsikonet på skrivebordet." +diff --git a/po/de.po b/po/de.po +index 01924b8..6ca034e 100644 +--- a/po/de.po ++++ b/po/de.po +@@ -1,3 +1,5 @@ ++# #-#-#-#-# de.po (gnome-shell-extensions master) #-#-#-#-# ++# #-#-#-#-# de.po (gnome-shell-extensions master) #-#-#-#-# + # German translation for gnome-shell-extensions. + # Copyright (C) 2011 gnome-shell-extensions's COPYRIGHT HOLDER + # This file is distributed under the same license as the gnome-shell-extensions package. +@@ -7,8 +9,23 @@ + # Wolfgang Stöggl , 2014. + # Paul Seyfert , 2017. + # ++# #-#-#-#-# de.po (desktop-icons master) #-#-#-#-# ++# German translation for desktop-icons. ++# Copyright (C) 2018 desktop-icons's COPYRIGHT HOLDER ++# This file is distributed under the same license as the desktop-icons package. ++# Mario Blättermann , 2018. ++# ++# #-#-#-#-# de.po (desktop-icons master) #-#-#-#-# ++# German translation for desktop-icons. ++# Copyright (C) 2018 desktop-icons's COPYRIGHT HOLDER ++# This file is distributed under the same license as the desktop-icons package. ++# Mario Blättermann , 2018. ++# ++#, fuzzy + msgid "" + msgstr "" ++"#-#-#-#-# de.po (gnome-shell-extensions master) #-#-#-#-#\n" ++"#-#-#-#-# de.po (gnome-shell-extensions master) #-#-#-#-#\n" + "Project-Id-Version: gnome-shell-extensions master\n" + "Report-Msgid-Bugs-To: https://bugzilla.gnome.org/enter_bug.cgi?product=gnome-" + "shell&keywords=I18N+L10N&component=extensions\n" +@@ -22,6 +39,34 @@ msgstr "" + "Content-Transfer-Encoding: 8bit\n" + "Plural-Forms: nplurals=2; plural=(n != 1);\n" + "X-Generator: Poedit 2.0.2\n" ++"#-#-#-#-# de.po (desktop-icons master) #-#-#-#-#\n" ++"Project-Id-Version: desktop-icons master\n" ++"Report-Msgid-Bugs-To: https://gitlab.gnome.org/World/ShellExtensions/desktop-" ++"icons/issues\n" ++"POT-Creation-Date: 2018-10-02 21:39+0000\n" ++"PO-Revision-Date: 2018-10-03 21:28+0200\n" ++"Last-Translator: Mario Blättermann \n" ++"Language-Team: German \n" ++"Language: de\n" ++"MIME-Version: 1.0\n" ++"Content-Type: text/plain; charset=UTF-8\n" ++"Content-Transfer-Encoding: 8bit\n" ++"Plural-Forms: nplurals=2; plural=(n != 1);\n" ++"X-Generator: Poedit 2.1.1\n" ++"#-#-#-#-# de.po (desktop-icons master) #-#-#-#-#\n" ++"Project-Id-Version: desktop-icons master\n" ++"Report-Msgid-Bugs-To: https://gitlab.gnome.org/World/ShellExtensions/desktop-" ++"icons/issues\n" ++"POT-Creation-Date: 2018-10-02 21:39+0000\n" ++"PO-Revision-Date: 2018-10-03 21:28+0200\n" ++"Last-Translator: Mario Blättermann \n" ++"Language-Team: German \n" ++"Language: de\n" ++"MIME-Version: 1.0\n" ++"Content-Type: text/plain; charset=UTF-8\n" ++"Content-Transfer-Encoding: 8bit\n" ++"Plural-Forms: nplurals=2; plural=(n != 1);\n" ++"X-Generator: Poedit 2.1.1\n" + + #: data/gnome-classic.desktop.in:3 data/gnome-classic.session.desktop.in:3 + msgid "GNOME Classic" +@@ -366,3 +411,119 @@ msgstr "Name" + #, javascript-format + msgid "Workspace %d" + msgstr "Arbeitsfläche %d" ++ ++#: prefs.js:89 ++msgid "Size for the desktop icons" ++msgstr "Größe der Arbeitsflächensymbole" ++ ++#: prefs.js:89 ++msgid "Small" ++msgstr "Klein" ++ ++#: prefs.js:89 ++msgid "Standard" ++msgstr "Standard" ++ ++#: prefs.js:89 ++msgid "Large" ++msgstr "Groß" ++ ++#: prefs.js:89 ++msgid "Huge" ++msgstr "Riesig" ++ ++#: prefs.js:90 ++msgid "Show the personal folder in the desktop" ++msgstr "Den persönlichen Ordner auf der Arbeitsfläche anzeigen" ++ ++#: prefs.js:91 ++msgid "Show the trash icon in the desktop" ++msgstr "Papierkorb-Symbol auf der Arbeitsfläche anzeigen" ++ ++#: desktopGrid.js:178 desktopGrid.js:297 ++msgid "New Folder" ++msgstr "Neuer Ordner" ++ ++#: desktopGrid.js:299 ++msgid "Paste" ++msgstr "Einfügen" ++ ++#: desktopGrid.js:300 ++msgid "Undo" ++msgstr "Rückgängig" ++ ++#: desktopGrid.js:301 ++msgid "Redo" ++msgstr "Wiederholen" ++ ++#: desktopGrid.js:303 ++msgid "Open Desktop in Files" ++msgstr "Arbeitsfläche in Dateiverwaltung öffnen" ++ ++#: desktopGrid.js:304 ++msgid "Open Terminal" ++msgstr "Terminal öffnen" ++ ++#: desktopGrid.js:306 ++msgid "Change Background…" ++msgstr "Hintergrund ändern …" ++ ++#: desktopGrid.js:307 ++msgid "Display Settings" ++msgstr "Anzeigeeinstellungen" ++ ++#: desktopGrid.js:308 ++msgid "Settings" ++msgstr "Einstellungen" ++ ++#: fileItem.js:226 ++msgid "Open" ++msgstr "Öffnen" ++ ++#: fileItem.js:229 ++msgid "Cut" ++msgstr "Ausschneiden" ++ ++#: fileItem.js:230 ++msgid "Copy" ++msgstr "Kopieren" ++ ++#: fileItem.js:231 ++msgid "Move to Trash" ++msgstr "In den Papierkorb verschieben" ++ ++#: fileItem.js:235 ++msgid "Empty trash" ++msgstr "Papierkorb leeren" ++ ++#: fileItem.js:241 ++msgid "Properties" ++msgstr "Eigenschaften" ++ ++#: fileItem.js:243 ++msgid "Show in Files" ++msgstr "In Dateiverwaltung anzeigen" ++ ++#: schemas/org.gnome.shell.extensions.desktop-icons.gschema.xml:12 ++msgid "Icon size" ++msgstr "Symbolgröße" ++ ++#: schemas/org.gnome.shell.extensions.desktop-icons.gschema.xml:13 ++msgid "Set the size for the desktop icons." ++msgstr "Die Größe der Arbeitsflächensymbole festlegen." ++ ++#: schemas/org.gnome.shell.extensions.desktop-icons.gschema.xml:17 ++msgid "Show personal folder" ++msgstr "Persönlichen Ordner anzeigen" ++ ++#: schemas/org.gnome.shell.extensions.desktop-icons.gschema.xml:18 ++msgid "Show the personal folder in the desktop." ++msgstr "Den persönlichen Ordner auf der Arbeitsfläche anzeigen." ++ ++#: schemas/org.gnome.shell.extensions.desktop-icons.gschema.xml:22 ++msgid "Show trash icon" ++msgstr "Papierkorb-Symbol anzeigen" ++ ++#: schemas/org.gnome.shell.extensions.desktop-icons.gschema.xml:23 ++msgid "Show the trash icon in the desktop." ++msgstr "Das Papierkorb-Symbol auf der Arbeitsfläche anzeigen." +diff --git a/po/es.po b/po/es.po +index a3c0703..18cf8d4 100644 +--- a/po/es.po ++++ b/po/es.po +@@ -1,3 +1,5 @@ ++# #-#-#-#-# es.po (gnome-shell-extensions master) #-#-#-#-# ++# #-#-#-#-# es.po (gnome-shell-extensions master) #-#-#-#-# + # Spanish translation for gnome-shell-extensions. + # Copyright (C) 2011 gnome-shell-extensions's COPYRIGHT HOLDER + # This file is distributed under the same license as the gnome-shell-extensions package. +@@ -6,8 +8,27 @@ + # + # Daniel Mustieles , 2011-2015, 2017. + # ++# #-#-#-#-# es.po (PACKAGE VERSION) #-#-#-#-# ++# SOME DESCRIPTIVE TITLE. ++# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER ++# This file is distributed under the same license as the PACKAGE package. ++# FIRST AUTHOR , YEAR. ++# Sergio Costas , 2018. ++# Daniel Mustieles , 2018, 2019. ++# ++# #-#-#-#-# es.po (PACKAGE VERSION) #-#-#-#-# ++# SOME DESCRIPTIVE TITLE. ++# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER ++# This file is distributed under the same license as the PACKAGE package. ++# FIRST AUTHOR , YEAR. ++# Sergio Costas , 2018. ++# Daniel Mustieles , 2018, 2019. ++# ++#, fuzzy + msgid "" + msgstr "" ++"#-#-#-#-# es.po (gnome-shell-extensions master) #-#-#-#-#\n" ++"#-#-#-#-# es.po (gnome-shell-extensions master) #-#-#-#-#\n" + "Project-Id-Version: gnome-shell-extensions master\n" + "Report-Msgid-Bugs-To: https://bugzilla.gnome.org/enter_bug.cgi?product=gnome-" + "shell&keywords=I18N+L10N&component=extensions\n" +@@ -21,6 +42,34 @@ msgstr "" + "Content-Transfer-Encoding: 8bit\n" + "Plural-Forms: nplurals=2; plural=(n != 1);\n" + "X-Generator: Gtranslator 2.91.6\n" ++"#-#-#-#-# es.po (PACKAGE VERSION) #-#-#-#-#\n" ++"Project-Id-Version: PACKAGE VERSION\n" ++"Report-Msgid-Bugs-To: https://gitlab.gnome.org/World/ShellExtensions/desktop-" ++"icons/issues\n" ++"POT-Creation-Date: 2018-12-14 09:12+0000\n" ++"PO-Revision-Date: 2019-01-08 16:12+0100\n" ++"Last-Translator: Daniel Mustieles \n" ++"Language-Team: es \n" ++"Language: es\n" ++"MIME-Version: 1.0\n" ++"Content-Type: text/plain; charset=UTF-8\n" ++"Content-Transfer-Encoding: 8bit\n" ++"Plural-Forms: nplurals=2; plural=(n != 1);\n" ++"X-Generator: Gtranslator 2.91.7\n" ++"#-#-#-#-# es.po (PACKAGE VERSION) #-#-#-#-#\n" ++"Project-Id-Version: PACKAGE VERSION\n" ++"Report-Msgid-Bugs-To: https://gitlab.gnome.org/World/ShellExtensions/desktop-" ++"icons/issues\n" ++"POT-Creation-Date: 2018-12-14 09:12+0000\n" ++"PO-Revision-Date: 2019-01-08 16:12+0100\n" ++"Last-Translator: Daniel Mustieles \n" ++"Language-Team: es \n" ++"Language: es\n" ++"MIME-Version: 1.0\n" ++"Content-Type: text/plain; charset=UTF-8\n" ++"Content-Transfer-Encoding: 8bit\n" ++"Plural-Forms: nplurals=2; plural=(n != 1);\n" ++"X-Generator: Gtranslator 2.91.7\n" + + #: data/gnome-classic.desktop.in:3 data/gnome-classic.session.desktop.in:3 + msgid "GNOME Classic" +@@ -361,6 +410,146 @@ msgstr "Nombre" + msgid "Workspace %d" + msgstr "Área de trabajo %d" + ++#: desktopGrid.js:311 ++msgid "Display Settings" ++msgstr "Configuración de pantalla" ++ ++#: schemas/org.gnome.shell.extensions.desktop-icons.gschema.xml:12 ++msgid "Icon size" ++msgstr "Tamaño de los iconos" ++ ++#: desktopGrid.js:578 ++msgid "Cancel" ++msgstr "Cancelar" ++ ++#: prefs.js:102 ++msgid "Size for the desktop icons" ++msgstr "Tamaño de los iconos del escritorio" ++ ++#: prefs.js:102 ++msgid "Small" ++msgstr "Pequeño" ++ ++#: prefs.js:102 ++msgid "Standard" ++msgstr "Estándar" ++ ++#: prefs.js:102 ++msgid "Large" ++msgstr "Grande" ++ ++#: prefs.js:102 ++msgid "Huge" ++msgstr "Inmenso" ++ ++#: prefs.js:103 ++msgid "Show the personal folder in the desktop" ++msgstr "Mostrar la carpeta personal en el escritorio" ++ ++#: prefs.js:104 ++msgid "Show the trash icon in the desktop" ++msgstr "Mostrar la papelera en el escritorio" ++ ++#: desktopGrid.js:182 desktopGrid.js:301 ++msgid "New Folder" ++msgstr "Nueva carpeta" ++ ++#: desktopGrid.js:303 ++msgid "Paste" ++msgstr "Pegar" ++ ++#: desktopGrid.js:304 ++msgid "Undo" ++msgstr "Deshacer" ++ ++#: desktopGrid.js:305 ++msgid "Redo" ++msgstr "Rehacer" ++ ++#: desktopGrid.js:307 ++msgid "Open Desktop in Files" ++msgstr "Abrir el escritorio en Files" ++ ++#: desktopGrid.js:308 ++msgid "Open Terminal" ++msgstr "Abrir un terminal" ++ ++#: desktopGrid.js:310 ++msgid "Change Background…" ++msgstr "Cambiar el fondo..." ++ ++#: desktopGrid.js:312 ++msgid "Settings" ++msgstr "Configuración" ++ ++#: desktopGrid.js:568 ++msgid "Enter file name…" ++msgstr "Introduzca el nombre del archivo…" ++ ++#: desktopGrid.js:572 ++msgid "OK" ++msgstr "Aceptar" ++ ++#: fileItem.js:485 ++msgid "Don’t Allow Launching" ++msgstr "No permitir lanzar" ++ ++#: fileItem.js:487 ++msgid "Allow Launching" ++msgstr "Permitir lanzar" ++ ++#: fileItem.js:550 ++msgid "Open" ++msgstr "Abrir" ++ ++#: fileItem.js:553 ++msgid "Cut" ++msgstr "Cortar" ++ ++#: fileItem.js:554 ++msgid "Copy" ++msgstr "Copiar" ++ ++#: fileItem.js:556 ++msgid "Rename" ++msgstr "Renombrar" ++ ++#: fileItem.js:557 ++msgid "Move to Trash" ++msgstr "Mover a la papelera" ++ ++#: fileItem.js:567 ++msgid "Empty Trash" ++msgstr "Vaciar la papelera" ++ ++#: fileItem.js:573 ++msgid "Properties" ++msgstr "Propiedades" ++ ++#: fileItem.js:575 ++msgid "Show in Files" ++msgstr "Mostrar en Files" ++ ++#: schemas/org.gnome.shell.extensions.desktop-icons.gschema.xml:13 ++msgid "Set the size for the desktop icons." ++msgstr "Establece el tamaño de los iconos del escritorio." ++ ++#: schemas/org.gnome.shell.extensions.desktop-icons.gschema.xml:17 ++msgid "Show personal folder" ++msgstr "Mostrar la carpeta personal" ++ ++#: schemas/org.gnome.shell.extensions.desktop-icons.gschema.xml:18 ++msgid "Show the personal folder in the desktop." ++msgstr "Mostrar la carpeta personal en el escritorio." ++ ++#: schemas/org.gnome.shell.extensions.desktop-icons.gschema.xml:22 ++msgid "Show trash icon" ++msgstr "Mostrar la papelera" ++ ++#: schemas/org.gnome.shell.extensions.desktop-icons.gschema.xml:23 ++msgid "Show the trash icon in the desktop." ++msgstr "Mostrar la papelera en el escritorio." ++ + #~ msgid "CPU" + #~ msgstr "CPU" + +@@ -409,9 +598,6 @@ msgstr "Área de trabajo %d" + #~ msgid "Display" + #~ msgstr "Pantalla" + +-#~ msgid "Display Settings" +-#~ msgstr "Configuración de pantalla" +- + #~ msgid "File System" + #~ msgstr "Sistema de archivos" + +@@ -459,9 +645,6 @@ msgstr "Área de trabajo %d" + #~ "Configura la posición del tablero en la pantalla. Los valores permitidos " + #~ "son «right» (derecha) o «left» (izquierda)" + +-#~ msgid "Icon size" +-#~ msgstr "Tamaño del icono" +- + #~ msgid "Sets icon size of the dock." + #~ msgstr "Configura el tamaño de los íconos del tablero." + +@@ -632,9 +815,6 @@ msgstr "Área de trabajo %d" + #~ msgid "Alt Tab Behaviour" + #~ msgstr "Comportamiento de Alt+Tab" + +-#~ msgid "Cancel" +-#~ msgstr "Cancelar" +- + #~ msgid "Notifications" + #~ msgstr "Notificaciones" + +@@ -668,3 +848,27 @@ msgstr "Área de trabajo %d" + + #~ msgid "Busy" + #~ msgstr "Ocupado" ++ ++#~ msgid "Ok" ++#~ msgstr "Aceptar" ++ ++#~ msgid "huge" ++#~ msgstr "inmenso" ++ ++#~ msgid "Maximum width for the icons and filename." ++#~ msgstr "Ancho máximo de los iconos y el nombre de fichero." ++ ++#~ msgid "Shows the Documents folder in the desktop." ++#~ msgstr "Muestra la carpeta Documentos en el escritorio." ++ ++#~ msgid "Shows the Downloads folder in the desktop." ++#~ msgstr "Muestra la carpeta Descargas en el escritorio." ++ ++#~ msgid "Shows the Music folder in the desktop." ++#~ msgstr "Muestra la carpeta Música en el escritorio." ++ ++#~ msgid "Shows the Pictures folder in the desktop." ++#~ msgstr "Muestra la carpeta Imágenes en el escritorio." ++ ++#~ msgid "Shows the Videos folder in the desktop." ++#~ msgstr "Muestra la carpeta Vídeos en el escritorio." +diff --git a/po/fi.po b/po/fi.po +index e036448..73b33cc 100644 +--- a/po/fi.po ++++ b/po/fi.po +@@ -1,3 +1,5 @@ ++# #-#-#-#-# fi.po (gnome-shell-extensions) #-#-#-#-# ++# #-#-#-#-# fi.po (gnome-shell-extensions) #-#-#-#-# + # Finnish translation of gnome-shell-extensions. + # Copyright (C) 2011 Ville-Pekka Vainio + # This file is distributed under the same license as the gnome-shell-extensions package. +@@ -7,8 +9,23 @@ + # Ville-Pekka Vainio , 2011. + # Jiri Grönroos , 2012, 2013, 2014, 2015, 2017. + # ++# #-#-#-#-# fi.po (desktop-icons master) #-#-#-#-# ++# Finnish translation for desktop-icons. ++# Copyright (C) 2018 desktop-icons's COPYRIGHT HOLDER ++# This file is distributed under the same license as the desktop-icons package. ++# Jiri Grönroos , 2018. ++# ++# #-#-#-#-# fi.po (desktop-icons master) #-#-#-#-# ++# Finnish translation for desktop-icons. ++# Copyright (C) 2018 desktop-icons's COPYRIGHT HOLDER ++# This file is distributed under the same license as the desktop-icons package. ++# Jiri Grönroos , 2018. ++# ++#, fuzzy + msgid "" + msgstr "" ++"#-#-#-#-# fi.po (gnome-shell-extensions) #-#-#-#-#\n" ++"#-#-#-#-# fi.po (gnome-shell-extensions) #-#-#-#-#\n" + "Project-Id-Version: gnome-shell-extensions\n" + "Report-Msgid-Bugs-To: https://bugzilla.gnome.org/enter_bug.cgi?product=gnome-" + "shell&keywords=I18N+L10N&component=extensions\n" +@@ -24,6 +41,34 @@ msgstr "" + "X-Generator: Gtranslator 2.91.7\n" + "X-Project-Style: gnome\n" + "X-POT-Import-Date: 2012-03-05 15:06:12+0000\n" ++"#-#-#-#-# fi.po (desktop-icons master) #-#-#-#-#\n" ++"Project-Id-Version: desktop-icons master\n" ++"Report-Msgid-Bugs-To: https://gitlab.gnome.org/World/ShellExtensions/desktop-" ++"icons/issues\n" ++"POT-Creation-Date: 2018-10-14 08:09+0000\n" ++"PO-Revision-Date: 2018-10-14 12:44+0300\n" ++"Language-Team: Finnish \n" ++"Language: fi\n" ++"MIME-Version: 1.0\n" ++"Content-Type: text/plain; charset=UTF-8\n" ++"Content-Transfer-Encoding: 8bit\n" ++"Plural-Forms: nplurals=2; plural=(n != 1);\n" ++"Last-Translator: Jiri Grönroos \n" ++"X-Generator: Poedit 2.0.6\n" ++"#-#-#-#-# fi.po (desktop-icons master) #-#-#-#-#\n" ++"Project-Id-Version: desktop-icons master\n" ++"Report-Msgid-Bugs-To: https://gitlab.gnome.org/World/ShellExtensions/desktop-" ++"icons/issues\n" ++"POT-Creation-Date: 2018-10-14 08:09+0000\n" ++"PO-Revision-Date: 2018-10-14 12:44+0300\n" ++"Language-Team: Finnish \n" ++"Language: fi\n" ++"MIME-Version: 1.0\n" ++"Content-Type: text/plain; charset=UTF-8\n" ++"Content-Transfer-Encoding: 8bit\n" ++"Plural-Forms: nplurals=2; plural=(n != 1);\n" ++"Last-Translator: Jiri Grönroos \n" ++"X-Generator: Poedit 2.0.6\n" + + #: data/gnome-classic.desktop.in:3 data/gnome-classic.session.desktop.in:3 + msgid "GNOME Classic" +@@ -352,6 +397,122 @@ msgstr "Nimi" + msgid "Workspace %d" + msgstr "Työtila %d" + ++#: desktopGrid.js:307 ++msgid "Display Settings" ++msgstr "Näytön asetukset" ++ ++#: schemas/org.gnome.shell.extensions.desktop-icons.gschema.xml:12 ++msgid "Icon size" ++msgstr "Kuvakekoko" ++ ++#: prefs.js:89 ++msgid "Size for the desktop icons" ++msgstr "Työpöytäkuvakkeiden koko" ++ ++#: prefs.js:89 ++msgid "Small" ++msgstr "Pieni" ++ ++#: prefs.js:89 ++msgid "Standard" ++msgstr "Normaali" ++ ++#: prefs.js:89 ++msgid "Large" ++msgstr "Suuri" ++ ++#: prefs.js:89 ++msgid "Huge" ++msgstr "Valtava" ++ ++#: prefs.js:90 ++msgid "Show the personal folder in the desktop" ++msgstr "Näytä kotikansio työpöydällä" ++ ++#: prefs.js:91 ++msgid "Show the trash icon in the desktop" ++msgstr "Näytä roskakorin kuvake työpöydällä" ++ ++#: desktopGrid.js:178 desktopGrid.js:297 ++msgid "New Folder" ++msgstr "Uusi kansio" ++ ++#: desktopGrid.js:299 ++msgid "Paste" ++msgstr "Liitä" ++ ++#: desktopGrid.js:300 ++msgid "Undo" ++msgstr "Kumoa" ++ ++#: desktopGrid.js:301 ++msgid "Redo" ++msgstr "Tee uudeleen" ++ ++#: desktopGrid.js:303 ++msgid "Open Desktop in Files" ++msgstr "Avaa työpöytä tiedostonhallinnassa" ++ ++#: desktopGrid.js:304 ++msgid "Open Terminal" ++msgstr "Avaa pääte" ++ ++#: desktopGrid.js:306 ++msgid "Change Background…" ++msgstr "Vaihda taustakuvaa…" ++ ++#: desktopGrid.js:308 ++msgid "Settings" ++msgstr "Asetukset" ++ ++#: fileItem.js:211 ++msgid "Open" ++msgstr "Avaa" ++ ++#: fileItem.js:214 ++msgid "Cut" ++msgstr "Leikkaa" ++ ++#: fileItem.js:215 ++msgid "Copy" ++msgstr "Kopioi" ++ ++#: fileItem.js:216 ++msgid "Move to Trash" ++msgstr "Siirrä roskakoriin" ++ ++#: fileItem.js:220 ++msgid "Empty trash" ++msgstr "Tyhjennä roskakori" ++ ++#: fileItem.js:226 ++msgid "Properties" ++msgstr "Ominaisuudet" ++ ++#: fileItem.js:228 ++msgid "Show in Files" ++msgstr "Näytä tiedostonhallinnassa" ++ ++#: schemas/org.gnome.shell.extensions.desktop-icons.gschema.xml:13 ++msgid "Set the size for the desktop icons." ++msgstr "Aseta työpöytäkuvakkeiden koko." ++ ++#: schemas/org.gnome.shell.extensions.desktop-icons.gschema.xml:17 ++msgid "Show personal folder" ++msgstr "Näytä kotikansio" ++ ++#: schemas/org.gnome.shell.extensions.desktop-icons.gschema.xml:18 ++msgid "Show the personal folder in the desktop." ++msgstr "Näytä kotikansio työpöydällä." ++ ++#: schemas/org.gnome.shell.extensions.desktop-icons.gschema.xml:22 ++msgid "Show trash icon" ++msgstr "Näytä roskakorin kuvake" ++ ++#: schemas/org.gnome.shell.extensions.desktop-icons.gschema.xml:23 ++msgid "Show the trash icon in the desktop." ++msgstr "Näytä roskakorin kuvake työpöydällä." ++ + #~ msgid "CPU" + #~ msgstr "Suoritin" + +@@ -388,9 +549,6 @@ msgstr "Työtila %d" + #~ msgid "Display" + #~ msgstr "Näyttö" + +-#~ msgid "Display Settings" +-#~ msgstr "Näytön asetukset" +- + #~ msgid "Drag here to add favorites" + #~ msgstr "Raahaa tähän lisätäksesi suosikkeihin" + +@@ -412,9 +570,6 @@ msgstr "Työtila %d" + #~ msgstr "" + #~ "Asettaa telakan sijainnin näytöllä. Sallitut arvot ovat 'right' tai 'left'" + +-#~ msgid "Icon size" +-#~ msgstr "Kuvakkeiden koko" +- + #~ msgid "Sets icon size of the dock." + #~ msgstr "Asettaa telakan kuvakkeiden koon." + +diff --git a/po/fr.po b/po/fr.po +index 2b01d33..3f9826f 100644 +--- a/po/fr.po ++++ b/po/fr.po +@@ -1,11 +1,30 @@ ++# #-#-#-#-# fr.po (gnome-shell-extensions master) #-#-#-#-# ++# #-#-#-#-# fr.po (gnome-shell-extensions master) #-#-#-#-# + # French translation for gnome-shell-extensions. + # Copyright (C) 2011-12 Listed translators + # This file is distributed under the same license as the gnome-shell-extensions package. + # Claude Paroz , 2011. + # Alain Lojewski , 2012-2013. + # ++# #-#-#-#-# fr.po (desktop-icons master) #-#-#-#-# ++# French translation for desktop-icons. ++# Copyright (C) 2018 desktop-icons's COPYRIGHT HOLDER ++# This file is distributed under the same license as the desktop-icons package. ++# ghentdebian , 2018. ++# Charles Monzat , 2018. ++# ++# #-#-#-#-# fr.po (desktop-icons master) #-#-#-#-# ++# French translation for desktop-icons. ++# Copyright (C) 2018 desktop-icons's COPYRIGHT HOLDER ++# This file is distributed under the same license as the desktop-icons package. ++# ghentdebian , 2018. ++# Charles Monzat , 2018. ++# ++#, fuzzy + msgid "" + msgstr "" ++"#-#-#-#-# fr.po (gnome-shell-extensions master) #-#-#-#-#\n" ++"#-#-#-#-# fr.po (gnome-shell-extensions master) #-#-#-#-#\n" + "Project-Id-Version: gnome-shell-extensions master\n" + "Report-Msgid-Bugs-To: https://bugzilla.gnome.org/enter_bug.cgi?product=gnome-" + "shell&keywords=I18N+L10N&component=extensions\n" +@@ -18,6 +37,34 @@ msgstr "" + "Content-Type: text/plain; charset=UTF-8\n" + "Content-Transfer-Encoding: 8bit\n" + "Plural-Forms: nplurals=2; plural=(n > 1);\n" ++"#-#-#-#-# fr.po (desktop-icons master) #-#-#-#-#\n" ++"Project-Id-Version: desktop-icons master\n" ++"Report-Msgid-Bugs-To: https://gitlab.gnome.org/World/ShellExtensions/desktop-" ++"icons/issues\n" ++"POT-Creation-Date: 2018-12-14 09:12+0000\n" ++"PO-Revision-Date: 2018-12-16 17:47+0100\n" ++"Last-Translator: Charles Monzat \n" ++"Language-Team: GNOME French Team \n" ++"Language: fr\n" ++"MIME-Version: 1.0\n" ++"Content-Type: text/plain; charset=UTF-8\n" ++"Content-Transfer-Encoding: 8bit\n" ++"Plural-Forms: nplurals=2; plural=(n > 1)\n" ++"X-Generator: Gtranslator 3.30.0\n" ++"#-#-#-#-# fr.po (desktop-icons master) #-#-#-#-#\n" ++"Project-Id-Version: desktop-icons master\n" ++"Report-Msgid-Bugs-To: https://gitlab.gnome.org/World/ShellExtensions/desktop-" ++"icons/issues\n" ++"POT-Creation-Date: 2018-12-14 09:12+0000\n" ++"PO-Revision-Date: 2018-12-16 17:47+0100\n" ++"Last-Translator: Charles Monzat \n" ++"Language-Team: GNOME French Team \n" ++"Language: fr\n" ++"MIME-Version: 1.0\n" ++"Content-Type: text/plain; charset=UTF-8\n" ++"Content-Transfer-Encoding: 8bit\n" ++"Plural-Forms: nplurals=2; plural=(n > 1)\n" ++"X-Generator: Gtranslator 3.30.0\n" + + #: data/gnome-classic.desktop.in:3 data/gnome-classic.session.desktop.in:3 + msgid "GNOME Classic" +@@ -362,8 +409,151 @@ msgstr "Nom" + msgid "Workspace %d" + msgstr "Espace de travail %d" + ++#: prefs.js:102 ++msgid "Size for the desktop icons" ++msgstr "Taille des icônes du bureau" ++ ++#: prefs.js:102 ++msgid "Small" ++msgstr "Petite" ++ ++#: prefs.js:102 ++msgid "Standard" ++msgstr "Normale" ++ ++#: prefs.js:102 ++msgid "Large" ++msgstr "Grande" ++ ++#: prefs.js:102 ++msgid "Huge" ++msgstr "Immense" ++ ++#: prefs.js:103 ++msgid "Show the personal folder in the desktop" ++msgstr "Montrer le dossier personnel sur le bureau" ++ ++#: prefs.js:104 ++msgid "Show the trash icon in the desktop" ++msgstr "Montrer la corbeille sur le bureau" ++ ++#: desktopGrid.js:182 desktopGrid.js:301 ++msgid "New Folder" ++msgstr "Nouveau dossier" ++ ++#: desktopGrid.js:303 ++msgid "Paste" ++msgstr "Coller" ++ ++#: desktopGrid.js:304 ++msgid "Undo" ++msgstr "Annuler" ++ ++#: desktopGrid.js:305 ++msgid "Redo" ++msgstr "Refaire" ++ ++#: desktopGrid.js:307 ++msgid "Open Desktop in Files" ++msgstr "Ouvrir le bureau dans Fichiers" ++ ++#: desktopGrid.js:308 ++msgid "Open Terminal" ++msgstr "Ouvrir un terminal" ++ ++#: desktopGrid.js:310 ++msgid "Change Background…" ++msgstr "Changer l’arrière-plan…" ++ ++#: desktopGrid.js:311 ++msgid "Display Settings" ++msgstr "Configuration d’affichage" ++ ++#: desktopGrid.js:312 ++msgid "Settings" ++msgstr "Paramètres" ++ ++#: desktopGrid.js:568 ++msgid "Enter file name…" ++msgstr "Saisir un nom de fichier…" ++ ++#: desktopGrid.js:572 ++msgid "OK" ++msgstr "Valider" ++ ++#: desktopGrid.js:578 ++msgid "Cancel" ++msgstr "Annuler" ++ ++#: fileItem.js:485 ++msgid "Don’t Allow Launching" ++msgstr "Ne pas autoriser le lancement" ++ ++#: fileItem.js:487 ++msgid "Allow Launching" ++msgstr "Autoriser le lancement" ++ ++#: fileItem.js:550 ++msgid "Open" ++msgstr "Ouvrir" ++ ++#: fileItem.js:553 ++msgid "Cut" ++msgstr "Couper" ++ ++#: fileItem.js:554 ++msgid "Copy" ++msgstr "Copier" ++ ++#: fileItem.js:556 ++msgid "Rename" ++msgstr "Renommer" ++ ++#: fileItem.js:557 ++msgid "Move to Trash" ++msgstr "Mettre à la corbeille" ++ ++#: fileItem.js:567 ++msgid "Empty Trash" ++msgstr "Vider la corbeille" ++ ++#: fileItem.js:573 ++msgid "Properties" ++msgstr "Propriétés" ++ ++#: fileItem.js:575 ++msgid "Show in Files" ++msgstr "Montrer dans Fichiers" ++ ++#: schemas/org.gnome.shell.extensions.desktop-icons.gschema.xml:12 ++msgid "Icon size" ++msgstr "Taille d’icônes" ++ ++#: schemas/org.gnome.shell.extensions.desktop-icons.gschema.xml:13 ++msgid "Set the size for the desktop icons." ++msgstr "Définir la taille des icônes du bureau." ++ ++#: schemas/org.gnome.shell.extensions.desktop-icons.gschema.xml:17 ++msgid "Show personal folder" ++msgstr "Montrer le dossier personnel" ++ ++#: schemas/org.gnome.shell.extensions.desktop-icons.gschema.xml:18 ++msgid "Show the personal folder in the desktop." ++msgstr "Montrer le dossier personnel sur le bureau." ++ ++#: schemas/org.gnome.shell.extensions.desktop-icons.gschema.xml:22 ++msgid "Show trash icon" ++msgstr "Montrer l’icône de la corbeille" ++ ++#: schemas/org.gnome.shell.extensions.desktop-icons.gschema.xml:23 ++msgid "Show the trash icon in the desktop." ++msgstr "Montrer la corbeille sur le bureau." ++ + #~ msgid "CPU" + #~ msgstr "CPU" + + #~ msgid "Memory" + #~ msgstr "Mémoire" ++ ++#~ msgid "Ok" ++#~ msgstr "Valider" +diff --git a/po/id.po b/po/id.po +index 8986a5e..033ae90 100644 +--- a/po/id.po ++++ b/po/id.po +@@ -1,11 +1,28 @@ ++# #-#-#-#-# id.po (gnome-shell-extensions master) #-#-#-#-# ++# #-#-#-#-# id.po (gnome-shell-extensions master) #-#-#-#-# + # Indonesian translation for gnome-shell-extensions. + # Copyright (C) 2012 gnome-shell-extensions's COPYRIGHT HOLDER + # This file is distributed under the same license as the gnome-shell-extensions package. + # + # Andika Triwidada , 2012, 2013. + # Dirgita , 2012. ++# #-#-#-#-# id.po (desktop-icons master) #-#-#-#-# ++# Indonesian translation for desktop-icons. ++# Copyright (C) 2018 desktop-icons's COPYRIGHT HOLDER ++# This file is distributed under the same license as the desktop-icons package. ++# FIRST AUTHOR , YEAR. ++# ++# #-#-#-#-# id.po (desktop-icons master) #-#-#-#-# ++# Indonesian translation for desktop-icons. ++# Copyright (C) 2018 desktop-icons's COPYRIGHT HOLDER ++# This file is distributed under the same license as the desktop-icons package. ++# FIRST AUTHOR , YEAR. ++# ++#, fuzzy + msgid "" + msgstr "" ++"#-#-#-#-# id.po (gnome-shell-extensions master) #-#-#-#-#\n" ++"#-#-#-#-# id.po (gnome-shell-extensions master) #-#-#-#-#\n" + "Project-Id-Version: gnome-shell-extensions master\n" + "Report-Msgid-Bugs-To: https://bugzilla.gnome.org/enter_bug.cgi?product=gnome-" + "shell&keywords=I18N+L10N&component=extensions\n" +@@ -20,6 +37,32 @@ msgstr "" + "Plural-Forms: nplurals=1; plural=0;\n" + "X-Poedit-SourceCharset: UTF-8\n" + "X-Generator: Poedit 2.0.2\n" ++"#-#-#-#-# id.po (desktop-icons master) #-#-#-#-#\n" ++"Project-Id-Version: desktop-icons master\n" ++"Report-Msgid-Bugs-To: https://gitlab.gnome.org/World/ShellExtensions/desktop-" ++"icons/issues\n" ++"POT-Creation-Date: 2018-10-02 09:18+0000\n" ++"PO-Revision-Date: 2018-10-02 20:30+0700\n" ++"Language-Team: Indonesian \n" ++"Language: id\n" ++"MIME-Version: 1.0\n" ++"Content-Type: text/plain; charset=UTF-8\n" ++"Content-Transfer-Encoding: 8bit\n" ++"Last-Translator: Kukuh Syafaat \n" ++"X-Generator: Poedit 2.0.6\n" ++"#-#-#-#-# id.po (desktop-icons master) #-#-#-#-#\n" ++"Project-Id-Version: desktop-icons master\n" ++"Report-Msgid-Bugs-To: https://gitlab.gnome.org/World/ShellExtensions/desktop-" ++"icons/issues\n" ++"POT-Creation-Date: 2018-10-02 09:18+0000\n" ++"PO-Revision-Date: 2018-10-02 20:30+0700\n" ++"Language-Team: Indonesian \n" ++"Language: id\n" ++"MIME-Version: 1.0\n" ++"Content-Type: text/plain; charset=UTF-8\n" ++"Content-Transfer-Encoding: 8bit\n" ++"Last-Translator: Kukuh Syafaat \n" ++"X-Generator: Poedit 2.0.6\n" + + #: data/gnome-classic.desktop.in:3 data/gnome-classic.session.desktop.in:3 + msgid "GNOME Classic" +@@ -358,6 +401,122 @@ msgstr "Nama" + msgid "Workspace %d" + msgstr "Ruang Kerja %d" + ++#: prefs.js:89 ++msgid "Size for the desktop icons" ++msgstr "Ukuran untuk ikon destop" ++ ++#: prefs.js:89 ++msgid "Small" ++msgstr "Kecil" ++ ++#: prefs.js:89 ++msgid "Standard" ++msgstr "Standar" ++ ++#: prefs.js:89 ++msgid "Large" ++msgstr "Besar" ++ ++#: prefs.js:89 ++msgid "Huge" ++msgstr "Sangat besar" ++ ++#: prefs.js:90 ++msgid "Show the personal folder in the desktop" ++msgstr "Tampilkan folder pribadi di destop" ++ ++#: prefs.js:91 ++msgid "Show the trash icon in the desktop" ++msgstr "Tampilkan ikon tong sampah di destop" ++ ++#: desktopGrid.js:178 desktopGrid.js:297 ++msgid "New Folder" ++msgstr "Folder Baru" ++ ++#: desktopGrid.js:299 ++msgid "Paste" ++msgstr "Tempel" ++ ++#: desktopGrid.js:300 ++msgid "Undo" ++msgstr "Tak Jadi" ++ ++#: desktopGrid.js:301 ++msgid "Redo" ++msgstr "Jadi Lagi" ++ ++#: desktopGrid.js:303 ++msgid "Open Desktop in Files" ++msgstr "Buka Destop pada Berkas" ++ ++#: desktopGrid.js:304 ++msgid "Open Terminal" ++msgstr "Buka Terminal" ++ ++#: desktopGrid.js:306 ++msgid "Change Background…" ++msgstr "Ubah Latar Belakang…" ++ ++#: desktopGrid.js:307 ++msgid "Display Settings" ++msgstr "Pengaturan Tampilan" ++ ++#: desktopGrid.js:308 ++msgid "Settings" ++msgstr "Pengaturan" ++ ++#: fileItem.js:226 ++msgid "Open" ++msgstr "Buka" ++ ++#: fileItem.js:229 ++msgid "Cut" ++msgstr "Potong" ++ ++#: fileItem.js:230 ++msgid "Copy" ++msgstr "Salin" ++ ++#: fileItem.js:231 ++msgid "Move to Trash" ++msgstr "Pindahkan ke Tong Sampah" ++ ++#: fileItem.js:235 ++msgid "Empty trash" ++msgstr "Kosongkan Tong Sampah" ++ ++#: fileItem.js:241 ++msgid "Properties" ++msgstr "Properti" ++ ++#: fileItem.js:243 ++msgid "Show in Files" ++msgstr "Tampilkan pada Berkas" ++ ++#: schemas/org.gnome.shell.extensions.desktop-icons.gschema.xml:12 ++msgid "Icon size" ++msgstr "Ukuran ikon" ++ ++#: schemas/org.gnome.shell.extensions.desktop-icons.gschema.xml:13 ++msgid "Set the size for the desktop icons." ++msgstr "Set ukuran untuk ikon destop." ++ ++#: schemas/org.gnome.shell.extensions.desktop-icons.gschema.xml:17 ++msgid "Show personal folder" ++msgstr "Tampilkan folder pribadi" ++ ++#: schemas/org.gnome.shell.extensions.desktop-icons.gschema.xml:18 ++msgid "Show the personal folder in the desktop." ++msgstr "Tampilkan folder pribadi di destop." ++ ++#: schemas/org.gnome.shell.extensions.desktop-icons.gschema.xml:22 ++msgid "Show trash icon" ++msgstr "Tampilkan ikon tong sampah" ++ ++#: schemas/org.gnome.shell.extensions.desktop-icons.gschema.xml:23 ++msgid "Show the trash icon in the desktop." ++msgstr "Tampilkan ikon tong sampah di destop." ++ + #~ msgid "CPU" + #~ msgstr "CPU" + +diff --git a/po/it.po b/po/it.po +index 4e3a59c..1d6fd26 100644 +--- a/po/it.po ++++ b/po/it.po +@@ -1,3 +1,5 @@ ++# #-#-#-#-# it.po (gnome-shell-extensions) #-#-#-#-# ++# #-#-#-#-# it.po (gnome-shell-extensions) #-#-#-#-# + # Italian translations for GNOME Shell extensions + # Copyright (C) 2011 Giovanni Campagna et al. + # Copyright (C) 2012, 2013, 2014, 2015, 2017 The Free Software Foundation, Inc. +@@ -6,8 +8,23 @@ + # Milo Casagrande , 2013, 2014, 2015, 2017. + # Gianvito Cavasoli , 2017. + # ++# #-#-#-#-# it.po (desktop-icons master) #-#-#-#-# ++# SOME DESCRIPTIVE TITLE. ++# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER ++# This file is distributed under the same license as the PACKAGE package. ++# Massimo Branchini , 2018. ++# ++# #-#-#-#-# it.po (desktop-icons master) #-#-#-#-# ++# SOME DESCRIPTIVE TITLE. ++# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER ++# This file is distributed under the same license as the PACKAGE package. ++# Massimo Branchini , 2018. ++# ++#, fuzzy + msgid "" + msgstr "" ++"#-#-#-#-# it.po (gnome-shell-extensions) #-#-#-#-#\n" ++"#-#-#-#-# it.po (gnome-shell-extensions) #-#-#-#-#\n" + "Project-Id-Version: gnome-shell-extensions\n" + "Report-Msgid-Bugs-To: https://bugzilla.gnome.org/enter_bug.cgi?product=gnome-" + "shell&keywords=I18N+L10N&component=extensions\n" +@@ -21,6 +38,34 @@ msgstr "" + "Content-Transfer-Encoding: 8bit\n" + "Plural-Forms: nplurals=2; plural=(n != 1);\n" + "X-Generator: Poedit 1.8.12\n" ++"#-#-#-#-# it.po (desktop-icons master) #-#-#-#-#\n" ++"Project-Id-Version: desktop-icons master\n" ++"Report-Msgid-Bugs-To: https://gitlab.gnome.org/World/ShellExtensions/desktop-" ++"icons/issues\n" ++"POT-Creation-Date: 2018-11-29 09:07+0000\n" ++"PO-Revision-Date: 2018-12-05 11:14+0100\n" ++"Last-Translator: Massimo Branchini \n" ++"Language-Team: Italian \n" ++"Language: it\n" ++"MIME-Version: 1.0\n" ++"Content-Type: text/plain; charset=UTF-8\n" ++"Content-Transfer-Encoding: 8bit\n" ++"Plural-Forms: nplurals=2; plural=(n != 1);\n" ++"X-Generator: Poedit 2.2\n" ++"#-#-#-#-# it.po (desktop-icons master) #-#-#-#-#\n" ++"Project-Id-Version: desktop-icons master\n" ++"Report-Msgid-Bugs-To: https://gitlab.gnome.org/World/ShellExtensions/desktop-" ++"icons/issues\n" ++"POT-Creation-Date: 2018-11-29 09:07+0000\n" ++"PO-Revision-Date: 2018-12-05 11:14+0100\n" ++"Last-Translator: Massimo Branchini \n" ++"Language-Team: Italian \n" ++"Language: it\n" ++"MIME-Version: 1.0\n" ++"Content-Type: text/plain; charset=UTF-8\n" ++"Content-Transfer-Encoding: 8bit\n" ++"Plural-Forms: nplurals=2; plural=(n != 1);\n" ++"X-Generator: Poedit 2.2\n" + + #: data/gnome-classic.desktop.in:3 data/gnome-classic.session.desktop.in:3 + msgid "GNOME Classic" +@@ -362,3 +407,135 @@ msgstr "Nome" + #, javascript-format + msgid "Workspace %d" + msgstr "Spazio di lavoro %d" ++ ++#: prefs.js:93 ++msgid "Size for the desktop icons" ++msgstr "Dimensione delle icone della scrivania" ++ ++#: prefs.js:93 ++msgid "Small" ++msgstr "Piccola" ++ ++#: prefs.js:93 ++msgid "Standard" ++msgstr "Normale" ++ ++#: prefs.js:93 ++msgid "Large" ++msgstr "Grande" ++ ++#: prefs.js:93 ++msgid "Huge" ++msgstr "Enorme" ++ ++#: prefs.js:94 ++msgid "Show the personal folder in the desktop" ++msgstr "Mostra la cartella personale sulla scrivania" ++ ++#: prefs.js:95 ++msgid "Show the trash icon in the desktop" ++msgstr "Mostra il cestino sulla scrivania" ++ ++#: desktopGrid.js:185 desktopGrid.js:304 ++msgid "New Folder" ++msgstr "Nuova cartella" ++ ++#: desktopGrid.js:306 ++msgid "Paste" ++msgstr "Incolla" ++ ++#: desktopGrid.js:307 ++msgid "Undo" ++msgstr "Annulla" ++ ++#: desktopGrid.js:308 ++msgid "Redo" ++msgstr "Ripeti" ++ ++#: desktopGrid.js:310 ++msgid "Open Desktop in Files" ++msgstr "Apri la scrivania in File" ++ ++#: desktopGrid.js:311 ++msgid "Open Terminal" ++msgstr "Apri un terminale" ++ ++#: desktopGrid.js:313 ++msgid "Change Background…" ++msgstr "Cambia lo sfondo…" ++ ++#: desktopGrid.js:314 ++msgid "Display Settings" ++msgstr "Impostazioni dello schermo" ++ ++#: desktopGrid.js:315 ++msgid "Settings" ++msgstr "Impostazioni" ++ ++#: desktopGrid.js:569 ++msgid "Enter file name…" ++msgstr "Indicare un nome per il file…" ++ ++#: desktopGrid.js:573 ++msgid "Ok" ++msgstr "Ok" ++ ++#: desktopGrid.js:579 ++msgid "Cancel" ++msgstr "Annulla" ++ ++#: fileItem.js:393 ++msgid "Open" ++msgstr "Apri" ++ ++#: fileItem.js:396 ++msgid "Cut" ++msgstr "Taglia" ++ ++#: fileItem.js:397 ++msgid "Copy" ++msgstr "Copia" ++ ++#: fileItem.js:398 ++msgid "Rename" ++msgstr "Rinomina" ++ ++#: fileItem.js:399 ++msgid "Move to Trash" ++msgstr "Sposta nel cestino" ++ ++#: fileItem.js:403 ++msgid "Empty Trash" ++msgstr "Svuota il cestino" ++ ++#: fileItem.js:409 ++msgid "Properties" ++msgstr "Proprietà" ++ ++#: fileItem.js:411 ++msgid "Show in Files" ++msgstr "Mostra in File" ++ ++#: schemas/org.gnome.shell.extensions.desktop-icons.gschema.xml:12 ++msgid "Icon size" ++msgstr "Dimensione dell'icona" ++ ++#: schemas/org.gnome.shell.extensions.desktop-icons.gschema.xml:13 ++msgid "Set the size for the desktop icons." ++msgstr "Imposta la grandezza delle icone della scrivania." ++ ++#: schemas/org.gnome.shell.extensions.desktop-icons.gschema.xml:17 ++msgid "Show personal folder" ++msgstr "Mostra la cartella personale" ++ ++#: schemas/org.gnome.shell.extensions.desktop-icons.gschema.xml:18 ++msgid "Show the personal folder in the desktop." ++msgstr "Mostra la cartella personale sulla scrivania." ++ ++#: schemas/org.gnome.shell.extensions.desktop-icons.gschema.xml:22 ++msgid "Show trash icon" ++msgstr "Mostra il cestino" ++ ++#: schemas/org.gnome.shell.extensions.desktop-icons.gschema.xml:23 ++msgid "Show the trash icon in the desktop." ++msgstr "Mostra il cestino sulla scrivania." +diff --git a/po/pl.po b/po/pl.po +index 35799ee..94dd40b 100644 +--- a/po/pl.po ++++ b/po/pl.po +@@ -1,11 +1,30 @@ ++# #-#-#-#-# pl.po (gnome-shell-extensions) #-#-#-#-# ++# #-#-#-#-# pl.po (gnome-shell-extensions) #-#-#-#-# + # Polish translation for gnome-shell-extensions. + # Copyright © 2011-2017 the gnome-shell-extensions authors. + # This file is distributed under the same license as the gnome-shell-extensions package. + # Piotr Drąg , 2011-2017. + # Aviary.pl , 2011-2017. + # ++# #-#-#-#-# pl.po (desktop-icons) #-#-#-#-# ++# Polish translation for desktop-icons. ++# Copyright © 2018-2019 the desktop-icons authors. ++# This file is distributed under the same license as the desktop-icons package. ++# Piotr Drąg , 2018-2019. ++# Aviary.pl , 2018-2019. ++# ++# #-#-#-#-# pl.po (desktop-icons) #-#-#-#-# ++# Polish translation for desktop-icons. ++# Copyright © 2018-2019 the desktop-icons authors. ++# This file is distributed under the same license as the desktop-icons package. ++# Piotr Drąg , 2018-2019. ++# Aviary.pl , 2018-2019. ++# ++#, fuzzy + msgid "" + msgstr "" ++"#-#-#-#-# pl.po (gnome-shell-extensions) #-#-#-#-#\n" ++"#-#-#-#-# pl.po (gnome-shell-extensions) #-#-#-#-#\n" + "Project-Id-Version: gnome-shell-extensions\n" + "Report-Msgid-Bugs-To: https://bugzilla.gnome.org/enter_bug.cgi?product=gnome-" + "shell&keywords=I18N+L10N&component=extensions\n" +@@ -19,6 +38,34 @@ msgstr "" + "Content-Transfer-Encoding: 8bit\n" + "Plural-Forms: nplurals=3; plural=(n==1 ? 0 : n%10>=2 && n%10<=4 && (n%100<10 " + "|| n%100>=20) ? 1 : 2);\n" ++"#-#-#-#-# pl.po (desktop-icons) #-#-#-#-#\n" ++"Project-Id-Version: desktop-icons\n" ++"Report-Msgid-Bugs-To: https://gitlab.gnome.org/World/ShellExtensions/desktop-" ++"icons/issues\n" ++"POT-Creation-Date: 2019-01-15 10:59+0000\n" ++"PO-Revision-Date: 2019-01-15 20:57+0100\n" ++"Last-Translator: Piotr Drąg \n" ++"Language-Team: Polish \n" ++"Language: pl\n" ++"MIME-Version: 1.0\n" ++"Content-Type: text/plain; charset=UTF-8\n" ++"Content-Transfer-Encoding: 8bit\n" ++"Plural-Forms: nplurals=3; plural=(n==1 ? 0 : n%10>=2 && n%10<=4 && (n%100<10 " ++"|| n%100>=20) ? 1 : 2);\n" ++"#-#-#-#-# pl.po (desktop-icons) #-#-#-#-#\n" ++"Project-Id-Version: desktop-icons\n" ++"Report-Msgid-Bugs-To: https://gitlab.gnome.org/World/ShellExtensions/desktop-" ++"icons/issues\n" ++"POT-Creation-Date: 2019-01-15 10:59+0000\n" ++"PO-Revision-Date: 2019-01-15 20:57+0100\n" ++"Last-Translator: Piotr Drąg \n" ++"Language-Team: Polish \n" ++"Language: pl\n" ++"MIME-Version: 1.0\n" ++"Content-Type: text/plain; charset=UTF-8\n" ++"Content-Transfer-Encoding: 8bit\n" ++"Plural-Forms: nplurals=3; plural=(n==1 ? 0 : n%10>=2 && n%10<=4 && (n%100<10 " ++"|| n%100>=20) ? 1 : 2);\n" + + #: data/gnome-classic.desktop.in:3 data/gnome-classic.session.desktop.in:3 + msgid "GNOME Classic" +@@ -358,3 +405,139 @@ msgstr "Nazwa" + #, javascript-format + msgid "Workspace %d" + msgstr "%d. obszar roboczy" ++ ++#: prefs.js:102 ++msgid "Size for the desktop icons" ++msgstr "Rozmiar ikon na pulpicie" ++ ++#: prefs.js:102 ++msgid "Small" ++msgstr "Mały" ++ ++#: prefs.js:102 ++msgid "Standard" ++msgstr "Standardowy" ++ ++#: prefs.js:102 ++msgid "Large" ++msgstr "Duży" ++ ++#: prefs.js:103 ++msgid "Show the personal folder in the desktop" ++msgstr "Katalog domowy na pulpicie" ++ ++#: prefs.js:104 ++msgid "Show the trash icon in the desktop" ++msgstr "Kosz na pulpicie" ++ ++#: desktopGrid.js:187 desktopGrid.js:306 ++msgid "New Folder" ++msgstr "Nowy katalog" ++ ++#: desktopGrid.js:308 ++msgid "Paste" ++msgstr "Wklej" ++ ++#: desktopGrid.js:309 ++msgid "Undo" ++msgstr "Cofnij" ++ ++#: desktopGrid.js:310 ++msgid "Redo" ++msgstr "Ponów" ++ ++#: desktopGrid.js:312 ++msgid "Show Desktop in Files" ++msgstr "Wyświetl pulpit w menedżerze plików" ++ ++#: desktopGrid.js:313 fileItem.js:586 ++msgid "Open in Terminal" ++msgstr "Otwórz w terminalu" ++ ++#: desktopGrid.js:315 ++msgid "Change Background…" ++msgstr "Zmień tło…" ++ ++#: desktopGrid.js:317 ++msgid "Display Settings" ++msgstr "Ustawienia ekranu" ++ ++#: desktopGrid.js:318 ++msgid "Settings" ++msgstr "Ustawienia" ++ ++#: desktopGrid.js:559 ++msgid "Enter file name…" ++msgstr "Nazwa pliku…" ++ ++#: desktopGrid.js:563 ++msgid "OK" ++msgstr "OK" ++ ++#: desktopGrid.js:569 ++msgid "Cancel" ++msgstr "Anuluj" ++ ++#: fileItem.js:490 ++msgid "Don’t Allow Launching" ++msgstr "Nie zezwalaj na uruchamianie" ++ ++#: fileItem.js:492 ++msgid "Allow Launching" ++msgstr "Zezwól na uruchamianie" ++ ++#: fileItem.js:559 ++msgid "Open" ++msgstr "Otwórz" ++ ++#: fileItem.js:562 ++msgid "Cut" ++msgstr "Wytnij" ++ ++#: fileItem.js:563 ++msgid "Copy" ++msgstr "Skopiuj" ++ ++#: fileItem.js:565 ++msgid "Rename…" ++msgstr "Zmień nazwę…" ++ ++#: fileItem.js:566 ++msgid "Move to Trash" ++msgstr "Przenieś do kosza" ++ ++#: fileItem.js:576 ++msgid "Empty Trash" ++msgstr "Opróżnij kosz" ++ ++#: fileItem.js:582 ++msgid "Properties" ++msgstr "Właściwości" ++ ++#: fileItem.js:584 ++msgid "Show in Files" ++msgstr "Wyświetl w menedżerze plików" ++ ++#: schemas/org.gnome.shell.extensions.desktop-icons.gschema.xml:11 ++msgid "Icon size" ++msgstr "Rozmiar ikon" ++ ++#: schemas/org.gnome.shell.extensions.desktop-icons.gschema.xml:12 ++msgid "Set the size for the desktop icons." ++msgstr "Ustawia rozmiar ikon na pulpicie." ++ ++#: schemas/org.gnome.shell.extensions.desktop-icons.gschema.xml:16 ++msgid "Show personal folder" ++msgstr "Katalog domowy" ++ ++#: schemas/org.gnome.shell.extensions.desktop-icons.gschema.xml:17 ++msgid "Show the personal folder in the desktop." ++msgstr "Wyświetla katalog domowy na pulpicie." ++ ++#: schemas/org.gnome.shell.extensions.desktop-icons.gschema.xml:21 ++msgid "Show trash icon" ++msgstr "Kosz" ++ ++#: schemas/org.gnome.shell.extensions.desktop-icons.gschema.xml:22 ++msgid "Show the trash icon in the desktop." ++msgstr "Wyświetla kosz na pulpicie." +diff --git a/po/pt_BR.po b/po/pt_BR.po +index d029648..77f7a0d 100644 +--- a/po/pt_BR.po ++++ b/po/pt_BR.po +@@ -1,3 +1,5 @@ ++# #-#-#-#-# pt_BR.po (gnome-shell-extensions master) #-#-#-#-# ++# #-#-#-#-# pt_BR.po (gnome-shell-extensions master) #-#-#-#-# + # Brazilian Portuguese translation for gnome-shell-extensions. + # Copyright (C) 2017 gnome-shell-extensions's COPYRIGHT HOLDER + # This file is distributed under the same license as the gnome-shell-extensions package. +@@ -9,8 +11,23 @@ + # Og Maciel , 2012. + # Enrico Nicoletto , 2013, 2014. + # Rafael Fontenelle , 2013, 2017. ++# #-#-#-#-# pt_BR.po (desktop-icons master) #-#-#-#-# ++# Brazilian Portuguese translation for desktop-icons. ++# Copyright (C) 2018 desktop-icons's COPYRIGHT HOLDER ++# This file is distributed under the same license as the desktop-icons package. ++# Enrico Nicoletto , 2018. ++# Rafael Fontenelle , 2018. ++# #-#-#-#-# pt_BR.po (desktop-icons master) #-#-#-#-# ++# Brazilian Portuguese translation for desktop-icons. ++# Copyright (C) 2018 desktop-icons's COPYRIGHT HOLDER ++# This file is distributed under the same license as the desktop-icons package. ++# Enrico Nicoletto , 2018. ++# Rafael Fontenelle , 2018. ++#, fuzzy + msgid "" + msgstr "" ++"#-#-#-#-# pt_BR.po (gnome-shell-extensions master) #-#-#-#-#\n" ++"#-#-#-#-# pt_BR.po (gnome-shell-extensions master) #-#-#-#-#\n" + "Project-Id-Version: gnome-shell-extensions master\n" + "Report-Msgid-Bugs-To: https://bugzilla.gnome.org/enter_bug.cgi?product=gnome-" + "shell&keywords=I18N+L10N&component=extensions\n" +@@ -25,6 +42,34 @@ msgstr "" + "Plural-Forms: nplurals=2; plural=(n > 1);\n" + "X-Generator: Virtaal 1.0.0-beta1\n" + "X-Project-Style: gnome\n" ++"#-#-#-#-# pt_BR.po (desktop-icons master) #-#-#-#-#\n" ++"Project-Id-Version: desktop-icons master\n" ++"Report-Msgid-Bugs-To: https://gitlab.gnome.org/World/ShellExtensions/desktop-" ++"icons/issues\n" ++"POT-Creation-Date: 2018-12-14 09:12+0000\n" ++"PO-Revision-Date: 2018-12-17 01:01-0200\n" ++"Last-Translator: Rafael Fontenelle \n" ++"Language-Team: Brazilian Portuguese \n" ++"Language: pt_BR\n" ++"MIME-Version: 1.0\n" ++"Content-Type: text/plain; charset=UTF-8\n" ++"Content-Transfer-Encoding: 8bit\n" ++"Plural-Forms: nplurals=2; plural=(n > 1);\n" ++"X-Generator: Virtaal 1.0.0-beta1\n" ++"#-#-#-#-# pt_BR.po (desktop-icons master) #-#-#-#-#\n" ++"Project-Id-Version: desktop-icons master\n" ++"Report-Msgid-Bugs-To: https://gitlab.gnome.org/World/ShellExtensions/desktop-" ++"icons/issues\n" ++"POT-Creation-Date: 2018-12-14 09:12+0000\n" ++"PO-Revision-Date: 2018-12-17 01:01-0200\n" ++"Last-Translator: Rafael Fontenelle \n" ++"Language-Team: Brazilian Portuguese \n" ++"Language: pt_BR\n" ++"MIME-Version: 1.0\n" ++"Content-Type: text/plain; charset=UTF-8\n" ++"Content-Transfer-Encoding: 8bit\n" ++"Plural-Forms: nplurals=2; plural=(n > 1);\n" ++"X-Generator: Virtaal 1.0.0-beta1\n" + + #: data/gnome-classic.desktop.in:3 data/gnome-classic.session.desktop.in:3 + msgid "GNOME Classic" +@@ -366,6 +411,146 @@ msgstr "Nome" + msgid "Workspace %d" + msgstr "Espaço de trabalho %d" + ++#: desktopGrid.js:311 ++msgid "Display Settings" ++msgstr "Configurações de exibição" ++ ++#: schemas/org.gnome.shell.extensions.desktop-icons.gschema.xml:12 ++msgid "Icon size" ++msgstr "Tamanho do ícone" ++ ++#: desktopGrid.js:578 ++msgid "Cancel" ++msgstr "Cancelar" ++ ++#: prefs.js:102 ++msgid "Size for the desktop icons" ++msgstr "Tamanho para os ícones da área de trabalho" ++ ++#: prefs.js:102 ++msgid "Small" ++msgstr "Pequeno" ++ ++#: prefs.js:102 ++msgid "Standard" ++msgstr "Padrão" ++ ++#: prefs.js:102 ++msgid "Large" ++msgstr "Grande" ++ ++#: prefs.js:102 ++msgid "Huge" ++msgstr "Enorme" ++ ++#: prefs.js:103 ++msgid "Show the personal folder in the desktop" ++msgstr "Mostrar a pasta pessoal na área de trabalho" ++ ++#: prefs.js:104 ++msgid "Show the trash icon in the desktop" ++msgstr "Mostrar o ícone da lixeira na área de trabalho" ++ ++#: desktopGrid.js:182 desktopGrid.js:301 ++msgid "New Folder" ++msgstr "Nova pasta" ++ ++#: desktopGrid.js:303 ++msgid "Paste" ++msgstr "Colar" ++ ++#: desktopGrid.js:304 ++msgid "Undo" ++msgstr "Desfazer" ++ ++#: desktopGrid.js:305 ++msgid "Redo" ++msgstr "Refazer" ++ ++#: desktopGrid.js:307 ++msgid "Open Desktop in Files" ++msgstr "Abrir área de trabalho no Arquivos" ++ ++#: desktopGrid.js:308 ++msgid "Open Terminal" ++msgstr "Abrir terminal" ++ ++#: desktopGrid.js:310 ++msgid "Change Background…" ++msgstr "Alterar plano de fundo…" ++ ++#: desktopGrid.js:312 ++msgid "Settings" ++msgstr "Configurações" ++ ++#: desktopGrid.js:568 ++msgid "Enter file name…" ++msgstr "Insira um nome de arquivo…" ++ ++#: desktopGrid.js:572 ++msgid "OK" ++msgstr "OK" ++ ++#: fileItem.js:485 ++msgid "Don’t Allow Launching" ++msgstr "Não permitir iniciar" ++ ++#: fileItem.js:487 ++msgid "Allow Launching" ++msgstr "Permitir iniciar" ++ ++#: fileItem.js:550 ++msgid "Open" ++msgstr "Abrir" ++ ++#: fileItem.js:553 ++msgid "Cut" ++msgstr "Recortar" ++ ++#: fileItem.js:554 ++msgid "Copy" ++msgstr "Copiar" ++ ++#: fileItem.js:556 ++msgid "Rename" ++msgstr "Renomear" ++ ++#: fileItem.js:557 ++msgid "Move to Trash" ++msgstr "Mover para a lixeira" ++ ++#: fileItem.js:567 ++msgid "Empty Trash" ++msgstr "Esvaziar lixeira" ++ ++#: fileItem.js:573 ++msgid "Properties" ++msgstr "Propriedades" ++ ++#: fileItem.js:575 ++msgid "Show in Files" ++msgstr "Mostrar no Arquivos" ++ ++#: schemas/org.gnome.shell.extensions.desktop-icons.gschema.xml:13 ++msgid "Set the size for the desktop icons." ++msgstr "Define o tamanho para os ícones da área de trabalho." ++ ++#: schemas/org.gnome.shell.extensions.desktop-icons.gschema.xml:17 ++msgid "Show personal folder" ++msgstr "Mostrar pasta pessoal" ++ ++#: schemas/org.gnome.shell.extensions.desktop-icons.gschema.xml:18 ++msgid "Show the personal folder in the desktop." ++msgstr "Mostra a pasta pessoal na área de trabalho." ++ ++#: schemas/org.gnome.shell.extensions.desktop-icons.gschema.xml:22 ++msgid "Show trash icon" ++msgstr "Mostrar ícone da lixeira" ++ ++#: schemas/org.gnome.shell.extensions.desktop-icons.gschema.xml:23 ++msgid "Show the trash icon in the desktop." ++msgstr "Mostra o ícone da lixeira na área de trabalho." ++ + #~ msgid "CPU" + #~ msgstr "CPU" + +@@ -414,9 +599,6 @@ msgstr "Espaço de trabalho %d" + #~ msgid "Display" + #~ msgstr "Tela" + +-#~ msgid "Display Settings" +-#~ msgstr "Configurações de tela" +- + #~ msgid "The application icon mode." + #~ msgstr "O modo de ícone do aplicativo." + +@@ -451,9 +633,6 @@ msgstr "Espaço de trabalho %d" + #~ "Define a posição do dock na tela. Os valores permitidos são \"right\" ou " + #~ "\"left\"" + +-#~ msgid "Icon size" +-#~ msgstr "Tamanho do ícone" +- + #~ msgid "Sets icon size of the dock." + #~ msgstr "Define o tamanho do ícone do dock." + +@@ -613,9 +792,6 @@ msgstr "Espaço de trabalho %d" + #~ msgid "Alt Tab Behaviour" + #~ msgstr "Comportamento do Alt Tab" + +-#~ msgid "Cancel" +-#~ msgstr "Cancelar" +- + #~ msgid "Ask the user for a default behaviour if true." + #~ msgstr "Pergunte ao usuário por um comportamento padrão se marcado." + +@@ -639,3 +815,6 @@ msgstr "Espaço de trabalho %d" + + #~ msgid "Log Out..." + #~ msgstr "Encerrar sessão..." ++ ++#~ msgid "Ok" ++#~ msgstr "Ok" +diff --git a/po/ru.po b/po/ru.po +index c18c0ba..5e48a26 100644 +--- a/po/ru.po ++++ b/po/ru.po +@@ -1,11 +1,28 @@ ++# #-#-#-#-# ru.po (gnome-shell-extensions gnome-3-0) #-#-#-#-# ++# #-#-#-#-# ru.po (gnome-shell-extensions gnome-3-0) #-#-#-#-# + # Russian translation for gnome-shell-extensions. + # Copyright (C) 2011 gnome-shell-extensions's COPYRIGHT HOLDER + # This file is distributed under the same license as the gnome-shell-extensions package. + # Yuri Myasoedov , 2011, 2012, 2013. + # Stas Solovey , 2011, 2012, 2013, 2015, 2017. + # ++# #-#-#-#-# ru.po #-#-#-#-# ++# SOME DESCRIPTIVE TITLE. ++# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER ++# This file is distributed under the same license as the PACKAGE package. ++# Eaglers , 2018. ++# ++# #-#-#-#-# ru.po #-#-#-#-# ++# SOME DESCRIPTIVE TITLE. ++# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER ++# This file is distributed under the same license as the PACKAGE package. ++# Eaglers , 2018. ++# ++#, fuzzy + msgid "" + msgstr "" ++"#-#-#-#-# ru.po (gnome-shell-extensions gnome-3-0) #-#-#-#-#\n" ++"#-#-#-#-# ru.po (gnome-shell-extensions gnome-3-0) #-#-#-#-#\n" + "Project-Id-Version: gnome-shell-extensions gnome-3-0\n" + "Report-Msgid-Bugs-To: https://bugzilla.gnome.org/enter_bug.cgi?product=gnome-" + "shell&keywords=I18N+L10N&component=extensions\n" +@@ -20,6 +37,36 @@ msgstr "" + "Plural-Forms: nplurals=3; plural=(n%10==1 && n%100!=11 ? 0 : n%10>=2 && n" + "%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2);\n" + "X-Generator: Poedit 2.0.3\n" ++"#-#-#-#-# ru.po #-#-#-#-#\n" ++"Project-Id-Version: \n" ++"Report-Msgid-Bugs-To: https://gitlab.gnome.org/World/ShellExtensions/desktop-" ++"icons/issues\n" ++"POT-Creation-Date: 2018-11-22 08:42+0000\n" ++"PO-Revision-Date: 2018-11-22 22:02+0300\n" ++"Last-Translator: Stas Solovey \n" ++"Language-Team: Russian \n" ++"Language: ru\n" ++"MIME-Version: 1.0\n" ++"Content-Type: text/plain; charset=UTF-8\n" ++"Content-Transfer-Encoding: 8bit\n" ++"Plural-Forms: nplurals=3; plural=(n%10==1 && n%100!=11 ? 0 : n%10>=2 && n" ++"%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2);\n" ++"X-Generator: Poedit 2.2\n" ++"#-#-#-#-# ru.po #-#-#-#-#\n" ++"Project-Id-Version: \n" ++"Report-Msgid-Bugs-To: https://gitlab.gnome.org/World/ShellExtensions/desktop-" ++"icons/issues\n" ++"POT-Creation-Date: 2018-11-22 08:42+0000\n" ++"PO-Revision-Date: 2018-11-22 22:02+0300\n" ++"Last-Translator: Stas Solovey \n" ++"Language-Team: Russian \n" ++"Language: ru\n" ++"MIME-Version: 1.0\n" ++"Content-Type: text/plain; charset=UTF-8\n" ++"Content-Transfer-Encoding: 8bit\n" ++"Plural-Forms: nplurals=3; plural=(n%10==1 && n%100!=11 ? 0 : n%10>=2 && n" ++"%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2);\n" ++"X-Generator: Poedit 2.2\n" + + #: data/gnome-classic.desktop.in:3 data/gnome-classic.session.desktop.in:3 + msgid "GNOME Classic" +@@ -360,6 +407,138 @@ msgstr "Название" + msgid "Workspace %d" + msgstr "Рабочая область %d" + ++#: prefs.js:89 ++msgid "Size for the desktop icons" ++msgstr "Размер значков" ++ ++#: prefs.js:89 ++msgid "Small" ++msgstr "Маленький" ++ ++#: prefs.js:89 ++msgid "Standard" ++msgstr "Стандартный" ++ ++#: prefs.js:89 ++msgid "Large" ++msgstr "Большой" ++ ++#: prefs.js:89 ++msgid "Huge" ++msgstr "Огромный" ++ ++#: prefs.js:90 ++msgid "Show the personal folder in the desktop" ++msgstr "Показывать домашнюю папку на рабочем столе" ++ ++#: prefs.js:91 ++msgid "Show the trash icon in the desktop" ++msgstr "Показывать «Корзину» на рабочем столе" ++ ++#: desktopGrid.js:185 desktopGrid.js:304 ++msgid "New Folder" ++msgstr "Создать папку" ++ ++#: desktopGrid.js:306 ++msgid "Paste" ++msgstr "Вставить" ++ ++#: desktopGrid.js:307 ++msgid "Undo" ++msgstr "Отменить" ++ ++#: desktopGrid.js:308 ++msgid "Redo" ++msgstr "Повторить" ++ ++#: desktopGrid.js:310 ++msgid "Open Desktop in Files" ++msgstr "Открыть «Рабочий стол» в «Файлах»" ++ ++#: desktopGrid.js:311 ++msgid "Open Terminal" ++msgstr "Открыть терминал" ++ ++#: desktopGrid.js:313 ++msgid "Change Background…" ++msgstr "Изменить фон…" ++ ++#: desktopGrid.js:314 ++msgid "Display Settings" ++msgstr "Настройки дисплея" ++ ++#: desktopGrid.js:315 ++msgid "Settings" ++msgstr "Параметры" ++ ++#: desktopGrid.js:569 ++msgid "Enter file name…" ++msgstr "Ввести имя файла…" ++ ++#: desktopGrid.js:573 ++msgid "Ok" ++msgstr "ОК" ++ ++#: desktopGrid.js:579 ++msgid "Cancel" ++msgstr "Отмена" ++ ++#: fileItem.js:390 ++msgid "Open" ++msgstr "Открыть" ++ ++#: fileItem.js:393 ++msgid "Cut" ++msgstr "Вырезать" ++ ++#: fileItem.js:394 ++msgid "Copy" ++msgstr "Вставить" ++ ++#: fileItem.js:395 ++msgid "Rename" ++msgstr "Переименовать" ++ ++#: fileItem.js:396 ++msgid "Move to Trash" ++msgstr "Переместить в корзину" ++ ++#: fileItem.js:400 ++msgid "Empty trash" ++msgstr "Очистить корзину" ++ ++#: fileItem.js:406 ++msgid "Properties" ++msgstr "Свойства" ++ ++#: fileItem.js:408 ++msgid "Show in Files" ++msgstr "Показать в «Файлах»" ++ ++#: schemas/org.gnome.shell.extensions.desktop-icons.gschema.xml:12 ++msgid "Icon size" ++msgstr "Размер значков" ++ ++#: schemas/org.gnome.shell.extensions.desktop-icons.gschema.xml:13 ++msgid "Set the size for the desktop icons." ++msgstr "Установить размер значков на рабочем столе." ++ ++#: schemas/org.gnome.shell.extensions.desktop-icons.gschema.xml:17 ++msgid "Show personal folder" ++msgstr "Показывать домашнюю папку" ++ ++#: schemas/org.gnome.shell.extensions.desktop-icons.gschema.xml:18 ++msgid "Show the personal folder in the desktop." ++msgstr "Показывать значок домашней папки на рабочем столе." ++ ++#: schemas/org.gnome.shell.extensions.desktop-icons.gschema.xml:22 ++msgid "Show trash icon" ++msgstr "Показывать значок корзины" ++ ++#: schemas/org.gnome.shell.extensions.desktop-icons.gschema.xml:23 ++msgid "Show the trash icon in the desktop." ++msgstr "Показывать значок корзины на рабочем столе." ++ + #~ msgid "CPU" + #~ msgstr "ЦП" + +diff --git a/po/zh_TW.po b/po/zh_TW.po +index 74a95f8..8a675aa 100644 +--- a/po/zh_TW.po ++++ b/po/zh_TW.po +@@ -1,10 +1,27 @@ ++# #-#-#-#-# zh_TW.po (gnome-shell-extensions gnome-3-0) #-#-#-#-# ++# #-#-#-#-# zh_TW.po (gnome-shell-extensions gnome-3-0) #-#-#-#-# + # Chinese (Taiwan) translation for gnome-shell-extensions. + # Copyright (C) 2011 gnome-shell-extensions's COPYRIGHT HOLDER + # This file is distributed under the same license as the gnome-shell-extensions package. + # Cheng-Chia Tseng , 2011. + # ++# #-#-#-#-# zh_TW.po (desktop-icons master) #-#-#-#-# ++# Chinese (Taiwan) translation for desktop-icons. ++# Copyright (C) 2018 desktop-icons's COPYRIGHT HOLDER ++# This file is distributed under the same license as the desktop-icons package. ++# Yi-Jyun Pan , 2018. ++# ++# #-#-#-#-# zh_TW.po (desktop-icons master) #-#-#-#-# ++# Chinese (Taiwan) translation for desktop-icons. ++# Copyright (C) 2018 desktop-icons's COPYRIGHT HOLDER ++# This file is distributed under the same license as the desktop-icons package. ++# Yi-Jyun Pan , 2018. ++# ++#, fuzzy + msgid "" + msgstr "" ++"#-#-#-#-# zh_TW.po (gnome-shell-extensions gnome-3-0) #-#-#-#-#\n" ++"#-#-#-#-# zh_TW.po (gnome-shell-extensions gnome-3-0) #-#-#-#-#\n" + "Project-Id-Version: gnome-shell-extensions gnome-3-0\n" + "Report-Msgid-Bugs-To: https://bugzilla.gnome.org/enter_bug.cgi?product=gnome-" + "shell&keywords=I18N+L10N&component=extensions\n" +@@ -17,6 +34,32 @@ msgstr "" + "Content-Type: text/plain; charset=UTF-8\n" + "Content-Transfer-Encoding: 8bit\n" + "X-Generator: Poedit 2.0.3\n" ++"#-#-#-#-# zh_TW.po (desktop-icons master) #-#-#-#-#\n" ++"Project-Id-Version: desktop-icons master\n" ++"Report-Msgid-Bugs-To: https://gitlab.gnome.org/World/ShellExtensions/desktop-" ++"icons/issues\n" ++"POT-Creation-Date: 2018-10-22 14:12+0000\n" ++"PO-Revision-Date: 2018-10-24 21:31+0800\n" ++"Language-Team: Chinese (Taiwan) \n" ++"Language: zh_TW\n" ++"MIME-Version: 1.0\n" ++"Content-Type: text/plain; charset=UTF-8\n" ++"Content-Transfer-Encoding: 8bit\n" ++"Last-Translator: pan93412 \n" ++"X-Generator: Poedit 2.2\n" ++"#-#-#-#-# zh_TW.po (desktop-icons master) #-#-#-#-#\n" ++"Project-Id-Version: desktop-icons master\n" ++"Report-Msgid-Bugs-To: https://gitlab.gnome.org/World/ShellExtensions/desktop-" ++"icons/issues\n" ++"POT-Creation-Date: 2018-10-22 14:12+0000\n" ++"PO-Revision-Date: 2018-10-24 21:31+0800\n" ++"Language-Team: Chinese (Taiwan) \n" ++"Language: zh_TW\n" ++"MIME-Version: 1.0\n" ++"Content-Type: text/plain; charset=UTF-8\n" ++"Content-Transfer-Encoding: 8bit\n" ++"Last-Translator: pan93412 \n" ++"X-Generator: Poedit 2.2\n" + + #: data/gnome-classic.desktop.in:3 data/gnome-classic.session.desktop.in:3 + msgid "GNOME Classic" +@@ -344,6 +387,122 @@ msgstr "名稱" + msgid "Workspace %d" + msgstr "工作區 %d" + ++#: desktopGrid.js:307 ++msgid "Display Settings" ++msgstr "顯示設定" ++ ++#: schemas/org.gnome.shell.extensions.desktop-icons.gschema.xml:12 ++msgid "Icon size" ++msgstr "圖示大小" ++ ++#: prefs.js:89 ++msgid "Size for the desktop icons" ++msgstr "桌面圖示的大小" ++ ++#: prefs.js:89 ++msgid "Small" ++msgstr "小圖示" ++ ++#: prefs.js:89 ++msgid "Standard" ++msgstr "標準大小圖示" ++ ++#: prefs.js:89 ++msgid "Large" ++msgstr "大圖示" ++ ++#: prefs.js:89 ++msgid "Huge" ++msgstr "巨大圖示" ++ ++#: prefs.js:90 ++msgid "Show the personal folder in the desktop" ++msgstr "在桌面顯示個人資料夾" ++ ++#: prefs.js:91 ++msgid "Show the trash icon in the desktop" ++msgstr "在桌面顯示垃圾桶圖示" ++ ++#: desktopGrid.js:178 desktopGrid.js:297 ++msgid "New Folder" ++msgstr "新增資料夾" ++ ++#: desktopGrid.js:299 ++msgid "Paste" ++msgstr "貼上" ++ ++#: desktopGrid.js:300 ++msgid "Undo" ++msgstr "復原" ++ ++#: desktopGrid.js:301 ++msgid "Redo" ++msgstr "重做" ++ ++#: desktopGrid.js:303 ++msgid "Open Desktop in Files" ++msgstr "在《檔案》中開啟桌面" ++ ++#: desktopGrid.js:304 ++msgid "Open Terminal" ++msgstr "開啟終端器" ++ ++#: desktopGrid.js:306 ++msgid "Change Background…" ++msgstr "變更背景圖片…" ++ ++#: desktopGrid.js:308 ++msgid "Settings" ++msgstr "設定" ++ ++#: fileItem.js:223 ++msgid "Open" ++msgstr "開啟" ++ ++#: fileItem.js:226 ++msgid "Cut" ++msgstr "剪下" ++ ++#: fileItem.js:227 ++msgid "Copy" ++msgstr "複製" ++ ++#: fileItem.js:228 ++msgid "Move to Trash" ++msgstr "移動到垃圾桶" ++ ++#: fileItem.js:232 ++msgid "Empty trash" ++msgstr "清空回收桶" ++ ++#: fileItem.js:238 ++msgid "Properties" ++msgstr "屬性" ++ ++#: fileItem.js:240 ++msgid "Show in Files" ++msgstr "在《檔案》中顯示" ++ ++#: schemas/org.gnome.shell.extensions.desktop-icons.gschema.xml:13 ++msgid "Set the size for the desktop icons." ++msgstr "設定桌面圖示的大小。" ++ ++#: schemas/org.gnome.shell.extensions.desktop-icons.gschema.xml:17 ++msgid "Show personal folder" ++msgstr "顯示個人資料夾" ++ ++#: schemas/org.gnome.shell.extensions.desktop-icons.gschema.xml:18 ++msgid "Show the personal folder in the desktop." ++msgstr "在桌面顯示個人資料夾。" ++ ++#: schemas/org.gnome.shell.extensions.desktop-icons.gschema.xml:22 ++msgid "Show trash icon" ++msgstr "顯示垃圾桶圖示" ++ ++#: schemas/org.gnome.shell.extensions.desktop-icons.gschema.xml:23 ++msgid "Show the trash icon in the desktop." ++msgstr "在桌面顯示垃圾桶圖示。" ++ + #~ msgid "CPU" + #~ msgstr "CPU" + +@@ -371,9 +530,6 @@ msgstr "工作區 %d" + #~ msgid "Display" + #~ msgstr "顯示" + +-#~ msgid "Display Settings" +-#~ msgstr "顯示設定值" +- + #~ msgid "Suspend" + #~ msgstr "暫停" + +@@ -481,9 +637,6 @@ msgstr "工作區 %d" + #~ msgid "Enable/disable autohide" + #~ msgstr "啟用/停用自動隱藏" + +-#~ msgid "Icon size" +-#~ msgstr "圖示大小" +- + #~ msgid "Position of the dock" + #~ msgstr "Dock 的位置" + +-- +2.20.1 + diff --git a/SOURCES/gnome-classic-wayland.desktop b/SOURCES/gnome-classic-wayland.desktop new file mode 100644 index 0000000..bf02a49 --- /dev/null +++ b/SOURCES/gnome-classic-wayland.desktop @@ -0,0 +1,27 @@ +[Desktop Entry] +Name[de]=Klassisch (Wayland Anzeige-Server) +Name[es]=Clásico (servidor gráfico Wayland) +Name[fr]=Classic (serveur affichage Wayland) +Name[it]=Classico (server grafico Wayland) +Name[ja]=クラシック (Wayland ディスプレイサーバー) +Name[ko]=클래식 (Wayland 디스플레이 서버) +Name[pt_BR]=Clássico (servidor de exibição Wayland) +Name[ru]=Классический (дисплейный сервер Wayland) +Name[zh_CN]=经典(Wayland 显现服务器) +Name[zh_TW]=經典(Wayland顯示服務器) +Name=Classic (Wayland display server) +Comment[de]=Diese Sitzung meldet Sie in GNOME Classic an +Comment[es]=Esta sesión inicia GNOME clásico +Comment[fr]=Cette session vous connnecte à GNOME Classique +Comment[it]=Questa sessione si avvia con GNOME classico +Comment[ja]=GNOME クラシックモードでログインします +Comment[ko]=이 세션을 사용하면 그놈 클래식에 로그인합니다 +Comment[pt_BR]=Essa sessão se inicia como GNOME Clássico +Comment[ru]=Данный сеанс использует классический рабочий стол GNOME +Comment[zh_CN]=该会话将登录到“GNOME 经典模式” +Comment[zh_TW]=這個作業階段讓您登入 GNOME Classic +Comment=This session logs you into GNOME Classic +Exec=env GNOME_SHELL_SESSION_MODE=classic gnome-session --session gnome-classic +TryExec=gnome-session +Type=Application +DesktopNames=GNOME-Classic;GNOME; diff --git a/SOURCES/gnome-classic.desktop b/SOURCES/gnome-classic.desktop new file mode 100644 index 0000000..b59c0d9 --- /dev/null +++ b/SOURCES/gnome-classic.desktop @@ -0,0 +1,27 @@ +[Desktop Entry] +Name[de]=Klassisch (X11 Anzeige-Server) +Name[es]=Clásico (servidor gráfico X11) +Name[fr]=Classic (serveur affichage X11) +Name[it]=Classico (server grafico X11) +Name[ja]=クラシック (X11 ディスプレイサーバー) +Name[ko]=클래식 (X11 디스플레이 서버) +Name[pt_BR]=Clássico (servidor de exibição X11) +Name[ru]=Классический (дисплейный сервер X11) +Name[zh_CN]=经典(X11 显示服务器) +Name[zh_TW]=經典(X11顯示服務器) +Name=Classic (X11 display server) +Comment[de]=Diese Sitzung meldet Sie in GNOME Classic an +Comment[es]=Esta sesión inicia GNOME clásico +Comment[fr]=Cette session vous connnecte à GNOME Classique +Comment[it]=Questa sessione si avvia con GNOME classico +Comment[ja]=GNOME クラシックモードでログインします +Comment[ko]=이 세션을 사용하면 그놈 클래식에 로그인합니다 +Comment[pt_BR]=Essa sessão se inicia como GNOME Clássico +Comment[ru]=Данный сеанс использует классический рабочий стол GNOME +Comment[zh_CN]=该会话将登录到“GNOME 经典模式” +Comment[zh_TW]=這個作業階段讓您登入 GNOME Classic +Comment=This session logs you into GNOME Classic +Exec=env GNOME_SHELL_SESSION_MODE=classic gnome-session --session gnome-classic +TryExec=gnome-session +Type=Application +DesktopNames=GNOME-Classic;GNOME; diff --git a/SOURCES/resurrect-system-monitor.patch b/SOURCES/resurrect-system-monitor.patch new file mode 100644 index 0000000..5e8829e --- /dev/null +++ b/SOURCES/resurrect-system-monitor.patch @@ -0,0 +1,767 @@ +From 901e1b32939d27b0b0edabe99705fd3066d360ba Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Florian=20M=C3=BCllner?= +Date: Wed, 17 May 2017 19:13:50 +0200 +Subject: [PATCH 1/4] extensions: Resurrect systemMonitor extension + +The extension was removed upstream because: + - it hooks into the message tray that was removed + - it was known to have performance issues + - there are plenty of alternatives + +Those aren't good enough reasons for dropping it downstream +as well though, so we need to bring it back ... + +This reverts commit c9a6421f362cd156cf731289eadc11f44f6970ac. +--- + extensions/systemMonitor/extension.js | 376 ++++++++++++++++++++++ + extensions/systemMonitor/meson.build | 5 + + extensions/systemMonitor/metadata.json.in | 11 + + extensions/systemMonitor/stylesheet.css | 35 ++ + meson.build | 1 + + 5 files changed, 428 insertions(+) + create mode 100644 extensions/systemMonitor/extension.js + create mode 100644 extensions/systemMonitor/meson.build + create mode 100644 extensions/systemMonitor/metadata.json.in + create mode 100644 extensions/systemMonitor/stylesheet.css + +diff --git a/extensions/systemMonitor/extension.js b/extensions/systemMonitor/extension.js +new file mode 100644 +index 0000000..7b09df0 +--- /dev/null ++++ b/extensions/systemMonitor/extension.js +@@ -0,0 +1,376 @@ ++/* -*- mode: js2; js2-basic-offset: 4; indent-tabs-mode: nil -*- */ ++ ++const Clutter = imports.gi.Clutter; ++const GTop = imports.gi.GTop; ++const Lang = imports.lang; ++const Mainloop = imports.mainloop; ++const St = imports.gi.St; ++const Shell = imports.gi.Shell; ++ ++const Main = imports.ui.main; ++const Tweener = imports.ui.tweener; ++ ++const Gettext = imports.gettext.domain('gnome-shell-extensions'); ++const _ = Gettext.gettext; ++ ++const ExtensionUtils = imports.misc.extensionUtils; ++const Me = ExtensionUtils.getCurrentExtension(); ++const Convenience = Me.imports.convenience; ++ ++const INDICATOR_UPDATE_INTERVAL = 500; ++const INDICATOR_NUM_GRID_LINES = 3; ++ ++const ITEM_LABEL_SHOW_TIME = 0.15; ++const ITEM_LABEL_HIDE_TIME = 0.1; ++const ITEM_HOVER_TIMEOUT = 300; ++ ++const Indicator = new Lang.Class({ ++ Name: 'SystemMonitor.Indicator', ++ ++ _init: function() { ++ this._initValues(); ++ this.drawing_area = new St.DrawingArea({ reactive: true }); ++ this.drawing_area.connect('repaint', Lang.bind(this, this._draw)); ++ this.drawing_area.connect('button-press-event', function() { ++ let app = Shell.AppSystem.get_default().lookup_app('gnome-system-monitor.desktop'); ++ app.open_new_window(-1); ++ return true; ++ }); ++ ++ this.actor = new St.Bin({ style_class: "extension-systemMonitor-indicator-area", ++ reactive: true, track_hover: true, ++ x_fill: true, y_fill: true }); ++ this.actor.add_actor(this.drawing_area); ++ ++ this._timeout = Mainloop.timeout_add(INDICATOR_UPDATE_INTERVAL, Lang.bind(this, function () { ++ this._updateValues(); ++ this.drawing_area.queue_repaint(); ++ return true; ++ })); ++ }, ++ ++ showLabel: function() { ++ if (this.label == null) ++ return; ++ ++ this.label.opacity = 0; ++ this.label.show(); ++ ++ let [stageX, stageY] = this.actor.get_transformed_position(); ++ ++ let itemWidth = this.actor.allocation.x2 - this.actor.allocation.x1; ++ let itemHeight = this.actor.allocation.y2 - this.actor.allocation.y1; ++ ++ let labelWidth = this.label.width; ++ let labelHeight = this.label.height; ++ let xOffset = Math.floor((itemWidth - labelWidth) / 2) ++ ++ let x = stageX + xOffset; ++ ++ let node = this.label.get_theme_node(); ++ let yOffset = node.get_length('-y-offset'); ++ ++ let y = stageY - this.label.get_height() - yOffset; ++ ++ this.label.set_position(x, y); ++ Tweener.addTween(this.label, ++ { opacity: 255, ++ time: ITEM_LABEL_SHOW_TIME, ++ transition: 'easeOutQuad', ++ }); ++ }, ++ ++ setLabelText: function(text) { ++ if (this.label == null) ++ this.label = new St.Label({ style_class: 'extension-systemMonitor-indicator-label'}); ++ ++ this.label.set_text(text); ++ Main.layoutManager.addChrome(this.label); ++ this.label.hide(); ++ }, ++ ++ hideLabel: function () { ++ Tweener.addTween(this.label, ++ { opacity: 0, ++ time: ITEM_LABEL_HIDE_TIME, ++ transition: 'easeOutQuad', ++ onComplete: Lang.bind(this, function() { ++ this.label.hide(); ++ }) ++ }); ++ }, ++ ++ destroy: function() { ++ Mainloop.source_remove(this._timeout); ++ ++ this.actor.destroy(); ++ if (this.label) ++ this.label.destroy(); ++ }, ++ ++ _initValues: function() { ++ }, ++ ++ _updateValues: function() { ++ }, ++ ++ _draw: function(area) { ++ let [width, height] = area.get_surface_size(); ++ let themeNode = this.actor.get_theme_node(); ++ let cr = area.get_context(); ++ ++ //draw the background grid ++ let color = themeNode.get_color(this.gridColor); ++ let gridOffset = Math.floor(height / (INDICATOR_NUM_GRID_LINES + 1)); ++ for (let i = 1; i <= INDICATOR_NUM_GRID_LINES; ++i) { ++ cr.moveTo(0, i * gridOffset + .5); ++ cr.lineTo(width, i * gridOffset + .5); ++ } ++ Clutter.cairo_set_source_color(cr, color); ++ cr.setLineWidth(1); ++ cr.setDash([4,1], 0); ++ cr.stroke(); ++ ++ //draw the foreground ++ ++ function makePath(values, reverse, nudge) { ++ if (nudge == null) { ++ nudge = 0; ++ } ++ //if we are going in reverse, we are completing the bottom of a chart, so use lineTo ++ if (reverse) { ++ cr.lineTo(values.length - 1, (1 - values[values.length - 1]) * height + nudge); ++ for (let k = values.length - 2; k >= 0; --k) { ++ cr.lineTo(k, (1 - values[k]) * height + nudge); ++ } ++ } else { ++ cr.moveTo(0, (1 - values[0]) * height + nudge); ++ for (let k = 1; k < values.length; ++k) { ++ cr.lineTo(k, (1 - values[k]) * height + nudge); ++ } ++ ++ } ++ } ++ ++ let renderStats = this.renderStats; ++ ++ // Make sure we don't have more sample points than pixels ++ renderStats.map(Lang.bind(this, function(k){ ++ let stat = this.stats[k]; ++ if (stat.values.length > width) { ++ stat.values = stat.values.slice(stat.values.length - width, stat.values.length); ++ } ++ })); ++ ++ for (let i = 0; i < renderStats.length; ++i) { ++ let stat = this.stats[renderStats[i]]; ++ // We outline at full opacity and fill with 40% opacity ++ let outlineColor = themeNode.get_color(stat.color); ++ let color = new Clutter.Color(outlineColor); ++ color.alpha = color.alpha * .4; ++ ++ // Render the background between us and the next level ++ makePath(stat.values, false); ++ // If there is a process below us, render the cpu between us and it, otherwise, ++ // render to the bottom of the chart ++ if (i == renderStats.length - 1) { ++ cr.lineTo(stat.values.length - 1, height); ++ cr.lineTo(0, height); ++ cr.closePath(); ++ } else { ++ let nextStat = this.stats[renderStats[i+1]]; ++ makePath(nextStat.values, true); ++ } ++ cr.closePath() ++ Clutter.cairo_set_source_color(cr, color); ++ cr.fill(); ++ ++ // Render the outline of this level ++ makePath(stat.values, false, .5); ++ Clutter.cairo_set_source_color(cr, outlineColor); ++ cr.setLineWidth(1.0); ++ cr.setDash([], 0); ++ cr.stroke(); ++ } ++ } ++}); ++ ++const CpuIndicator = new Lang.Class({ ++ Name: 'SystemMonitor.CpuIndicator', ++ Extends: Indicator, ++ ++ _init: function() { ++ this.parent(); ++ ++ this.gridColor = '-grid-color'; ++ this.renderStats = [ 'cpu-user', 'cpu-sys', 'cpu-iowait' ]; ++ ++ // Make sure renderStats is sorted as necessary for rendering ++ let renderStatOrder = {'cpu-total': 0, 'cpu-user': 1, 'cpu-sys': 2, 'cpu-iowait': 3}; ++ this.renderStats = this.renderStats.sort(function(a,b) { ++ return renderStatOrder[a] - renderStatOrder[b]; ++ }); ++ ++ this.setLabelText(_("CPU")); ++ }, ++ ++ _initValues: function() { ++ this._prev = new GTop.glibtop_cpu; ++ GTop.glibtop_get_cpu(this._prev); ++ ++ this.stats = { ++ 'cpu-user': {color: '-cpu-user-color', values: []}, ++ 'cpu-sys': {color: '-cpu-sys-color', values: []}, ++ 'cpu-iowait': {color: '-cpu-iowait-color', values: []}, ++ 'cpu-total': {color: '-cpu-total-color', values: []} ++ }; ++ }, ++ ++ _updateValues: function() { ++ let cpu = new GTop.glibtop_cpu; ++ let t = 0.0; ++ GTop.glibtop_get_cpu(cpu); ++ let total = cpu.total - this._prev.total; ++ let user = cpu.user - this._prev.user; ++ let sys = cpu.sys - this._prev.sys; ++ let iowait = cpu.iowait - this._prev.iowait; ++ let idle = cpu.idle - this._prev.idle; ++ ++ t += iowait / total; ++ this.stats['cpu-iowait'].values.push(t); ++ t += sys / total; ++ this.stats['cpu-sys'].values.push(t); ++ t += user / total; ++ this.stats['cpu-user'].values.push(t); ++ this.stats['cpu-total'].values.push(1 - idle / total); ++ ++ this._prev = cpu; ++ } ++}); ++ ++const MemoryIndicator = new Lang.Class({ ++ Name: 'SystemMonitor.MemoryIndicator', ++ Extends: Indicator, ++ ++ _init: function() { ++ this.parent(); ++ ++ this.gridColor = '-grid-color'; ++ this.renderStats = [ 'mem-user', 'mem-other', 'mem-cached' ]; ++ ++ // Make sure renderStats is sorted as necessary for rendering ++ let renderStatOrder = { 'mem-cached': 0, 'mem-other': 1, 'mem-user': 2 }; ++ this.renderStats = this.renderStats.sort(function(a,b) { ++ return renderStatOrder[a] - renderStatOrder[b]; ++ }); ++ ++ this.setLabelText(_("Memory")); ++ }, ++ ++ _initValues: function() { ++ this.mem = new GTop.glibtop_mem; ++ this.stats = { ++ 'mem-user': { color: "-mem-user-color", values: [] }, ++ 'mem-other': { color: "-mem-other-color", values: [] }, ++ 'mem-cached': { color: "-mem-cached-color", values: [] } ++ }; ++ }, ++ ++ _updateValues: function() { ++ GTop.glibtop_get_mem(this.mem); ++ ++ let t = this.mem.user / this.mem.total; ++ this.stats['mem-user'].values.push(t); ++ t += (this.mem.used - this.mem.user - this.mem.cached) / this.mem.total; ++ this.stats['mem-other'].values.push(t); ++ t += this.mem.cached / this.mem.total; ++ this.stats['mem-cached'].values.push(t); ++ } ++}); ++ ++const INDICATORS = [CpuIndicator, MemoryIndicator]; ++ ++const Extension = new Lang.Class({ ++ Name: 'SystemMonitor.Extension', ++ ++ _init: function() { ++ Convenience.initTranslations(); ++ ++ this._showLabelTimeoutId = 0; ++ this._resetHoverTimeoutId = 0; ++ this._labelShowing = false; ++ }, ++ ++ enable: function() { ++ this._box = new St.BoxLayout({ style_class: 'extension-systemMonitor-container', ++ x_align: Clutter.ActorAlign.START, ++ x_expand: true }); ++ this._indicators = [ ]; ++ ++ for (let i = 0; i < INDICATORS.length; i++) { ++ let indicator = new (INDICATORS[i])(); ++ ++ indicator.actor.connect('notify::hover', Lang.bind(this, function() { ++ this._onHover(indicator); ++ })); ++ this._box.add_actor(indicator.actor); ++ this._indicators.push(indicator); ++ } ++ ++ this._boxHolder = new St.BoxLayout({ x_expand: true, ++ y_expand: true, ++ x_align: Clutter.ActorAlign.START, ++ }); ++ let menuButton = Main.messageTray._messageTrayMenuButton.actor; ++ Main.messageTray.actor.remove_child(menuButton); ++ Main.messageTray.actor.add_child(this._boxHolder); ++ ++ this._boxHolder.add_child(this._box); ++ this._boxHolder.add_child(menuButton); ++ }, ++ ++ disable: function() { ++ this._indicators.forEach(function(i) { i.destroy(); }); ++ ++ let menuButton = Main.messageTray._messageTrayMenuButton.actor; ++ this._boxHolder.remove_child(menuButton); ++ Main.messageTray.actor.add_child(menuButton); ++ ++ this._box.destroy(); ++ this._boxHolder.destroy(); ++ }, ++ ++ _onHover: function (item) { ++ if (item.actor.get_hover()) { ++ if (this._showLabelTimeoutId == 0) { ++ let timeout = this._labelShowing ? 0 : ITEM_HOVER_TIMEOUT; ++ this._showLabelTimeoutId = Mainloop.timeout_add(timeout, ++ Lang.bind(this, function() { ++ this._labelShowing = true; ++ item.showLabel(); ++ return false; ++ })); ++ if (this._resetHoverTimeoutId > 0) { ++ Mainloop.source_remove(this._resetHoverTimeoutId); ++ this._resetHoverTimeoutId = 0; ++ } ++ } ++ } else { ++ if (this._showLabelTimeoutId > 0) ++ Mainloop.source_remove(this._showLabelTimeoutId); ++ this._showLabelTimeoutId = 0; ++ item.hideLabel(); ++ if (this._labelShowing) { ++ this._resetHoverTimeoutId = Mainloop.timeout_add(ITEM_HOVER_TIMEOUT, ++ Lang.bind(this, function() { ++ this._labelShowing = false; ++ return false; ++ })); ++ } ++ } ++ }, ++}); ++ ++function init() { ++ return new Extension(); ++} +diff --git a/extensions/systemMonitor/meson.build b/extensions/systemMonitor/meson.build +new file mode 100644 +index 0000000..48504f6 +--- /dev/null ++++ b/extensions/systemMonitor/meson.build +@@ -0,0 +1,5 @@ ++extension_data += configure_file( ++ input: metadata_name + '.in', ++ output: metadata_name, ++ configuration: metadata_conf ++) +diff --git a/extensions/systemMonitor/metadata.json.in b/extensions/systemMonitor/metadata.json.in +new file mode 100644 +index 0000000..fa75007 +--- /dev/null ++++ b/extensions/systemMonitor/metadata.json.in +@@ -0,0 +1,11 @@ ++{ ++ "shell-version": ["@shell_current@" ], ++ "uuid": "@uuid@", ++ "extension-id": "@extension_id@", ++ "settings-schema": "@gschemaname@", ++ "gettext-domain": "@gettext_domain@", ++ "original-author": "zaspire@rambler.ru", ++ "name": "SystemMonitor", ++ "description": "System monitor showing CPU and memory usage in the message tray.", ++ "url": "@url@" ++} +diff --git a/extensions/systemMonitor/stylesheet.css b/extensions/systemMonitor/stylesheet.css +new file mode 100644 +index 0000000..13f95ec +--- /dev/null ++++ b/extensions/systemMonitor/stylesheet.css +@@ -0,0 +1,35 @@ ++.extension-systemMonitor-container { ++ spacing: 5px; ++ padding-left: 5px; ++ padding-right: 5px; ++ padding-bottom: 10px; ++ padding-top: 10px; ++} ++ ++.extension-systemMonitor-indicator-area { ++ border: 1px solid #8d8d8d; ++ border-radius: 3px; ++ width: 100px; ++ /* message tray is 72px, so 20px padding of the container, ++ 2px of border, makes it 50px */ ++ height: 50px; ++ -grid-color: #575757; ++ -cpu-total-color: rgb(0,154,62); ++ -cpu-user-color: rgb(69,154,0); ++ -cpu-sys-color: rgb(255,253,81); ++ -cpu-iowait-color: rgb(210,148,0); ++ -mem-user-color: rgb(210,148,0); ++ -mem-cached-color: rgb(90,90,90); ++ -mem-other-color: rgb(205,203,41); ++ background-color: #1e1e1e; ++} ++ ++.extension-systemMonitor-indicator-label { ++ border-radius: 7px; ++ padding: 4px 12px; ++ background-color: rgba(0,0,0,0.9); ++ text-align: center; ++ -y-offset: 8px; ++ font-size: 9pt; ++ font-weight: bold; ++} +diff --git a/meson.build b/meson.build +index 201c484..cde2d34 100644 +--- a/meson.build ++++ b/meson.build +@@ -57,6 +57,7 @@ all_extensions += [ + 'native-window-placement', + 'no-hot-corner', + 'panel-favorites', ++ 'systemMonitor', + 'top-icons', + 'updates-dialog', + 'user-theme' +-- +2.20.1 + + +From a0583c021dd74378618139d760b2c4d6d528f11a Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Florian=20M=C3=BCllner?= +Date: Wed, 17 May 2017 19:31:58 +0200 +Subject: [PATCH 2/4] systemMonitor: Move indicators to calendar + +The message tray joined the invisible choir, so we have to find +a new home for the extension UI. The message list in the calendar +drop-down looks like the best option, given that it replaced the +old tray (and also took over the old keyboard shortcut to bring +it up quickly). +--- + extensions/systemMonitor/extension.js | 56 ++++++++++++------------- + extensions/systemMonitor/stylesheet.css | 14 ------- + 2 files changed, 28 insertions(+), 42 deletions(-) + +diff --git a/extensions/systemMonitor/extension.js b/extensions/systemMonitor/extension.js +index 7b09df0..1388a1f 100644 +--- a/extensions/systemMonitor/extension.js ++++ b/extensions/systemMonitor/extension.js +@@ -4,10 +4,12 @@ const Clutter = imports.gi.Clutter; + const GTop = imports.gi.GTop; + const Lang = imports.lang; + const Mainloop = imports.mainloop; ++const Signals = imports.signals; + const St = imports.gi.St; + const Shell = imports.gi.Shell; + + const Main = imports.ui.main; ++const MessageList = imports.ui.messageList; + const Tweener = imports.ui.tweener; + + const Gettext = imports.gettext.domain('gnome-shell-extensions'); +@@ -29,18 +31,21 @@ const Indicator = new Lang.Class({ + + _init: function() { + this._initValues(); +- this.drawing_area = new St.DrawingArea({ reactive: true }); ++ this.drawing_area = new St.DrawingArea(); + this.drawing_area.connect('repaint', Lang.bind(this, this._draw)); +- this.drawing_area.connect('button-press-event', function() { ++ ++ this.actor = new St.Button({ style_class: "message message-content extension-systemMonitor-indicator-area", ++ x_expand: true, x_fill: true, ++ y_fill: true, can_focus: true }); ++ this.actor.add_actor(this.drawing_area); ++ ++ this.actor.connect('clicked', function() { + let app = Shell.AppSystem.get_default().lookup_app('gnome-system-monitor.desktop'); + app.open_new_window(-1); +- return true; +- }); + +- this.actor = new St.Bin({ style_class: "extension-systemMonitor-indicator-area", +- reactive: true, track_hover: true, +- x_fill: true, y_fill: true }); +- this.actor.add_actor(this.drawing_area); ++ Main.overview.hide(); ++ Main.panel.closeCalendar(); ++ }); + + this._timeout = Mainloop.timeout_add(INDICATOR_UPDATE_INTERVAL, Lang.bind(this, function () { + this._updateValues(); +@@ -73,6 +78,7 @@ const Indicator = new Lang.Class({ + let y = stageY - this.label.get_height() - yOffset; + + this.label.set_position(x, y); ++ this.label.get_parent().set_child_above_sibling(this.label, null); + Tweener.addTween(this.label, + { opacity: 255, + time: ITEM_LABEL_SHOW_TIME, +@@ -100,6 +106,14 @@ const Indicator = new Lang.Class({ + }); + }, + ++ /* MessageList.Message boilerplate */ ++ canClose: function() { ++ return false; ++ }, ++ ++ clear: function() { ++ }, ++ + destroy: function() { + Mainloop.source_remove(this._timeout); + +@@ -194,6 +208,7 @@ const Indicator = new Lang.Class({ + } + } + }); ++Signals.addSignalMethods(Indicator.prototype); // For MessageList.Message compat + + const CpuIndicator = new Lang.Class({ + Name: 'SystemMonitor.CpuIndicator', +@@ -302,9 +317,7 @@ const Extension = new Lang.Class({ + }, + + enable: function() { +- this._box = new St.BoxLayout({ style_class: 'extension-systemMonitor-container', +- x_align: Clutter.ActorAlign.START, +- x_expand: true }); ++ this._section = new MessageList.MessageListSection(_("System Monitor")); + this._indicators = [ ]; + + for (let i = 0; i < INDICATORS.length; i++) { +@@ -313,31 +326,18 @@ const Extension = new Lang.Class({ + indicator.actor.connect('notify::hover', Lang.bind(this, function() { + this._onHover(indicator); + })); +- this._box.add_actor(indicator.actor); ++ this._section.addMessage(indicator, false); + this._indicators.push(indicator); + } + +- this._boxHolder = new St.BoxLayout({ x_expand: true, +- y_expand: true, +- x_align: Clutter.ActorAlign.START, +- }); +- let menuButton = Main.messageTray._messageTrayMenuButton.actor; +- Main.messageTray.actor.remove_child(menuButton); +- Main.messageTray.actor.add_child(this._boxHolder); +- +- this._boxHolder.add_child(this._box); +- this._boxHolder.add_child(menuButton); ++ Main.panel.statusArea.dateMenu._messageList._addSection(this._section); ++ this._section.actor.get_parent().set_child_at_index(this._section.actor, 0); + }, + + disable: function() { + this._indicators.forEach(function(i) { i.destroy(); }); + +- let menuButton = Main.messageTray._messageTrayMenuButton.actor; +- this._boxHolder.remove_child(menuButton); +- Main.messageTray.actor.add_child(menuButton); +- +- this._box.destroy(); +- this._boxHolder.destroy(); ++ Main.panel.statusArea.dateMenu._messageList._removeSection(this._section); + }, + + _onHover: function (item) { +diff --git a/extensions/systemMonitor/stylesheet.css b/extensions/systemMonitor/stylesheet.css +index 13f95ec..978ac12 100644 +--- a/extensions/systemMonitor/stylesheet.css ++++ b/extensions/systemMonitor/stylesheet.css +@@ -1,17 +1,4 @@ +-.extension-systemMonitor-container { +- spacing: 5px; +- padding-left: 5px; +- padding-right: 5px; +- padding-bottom: 10px; +- padding-top: 10px; +-} +- + .extension-systemMonitor-indicator-area { +- border: 1px solid #8d8d8d; +- border-radius: 3px; +- width: 100px; +- /* message tray is 72px, so 20px padding of the container, +- 2px of border, makes it 50px */ + height: 50px; + -grid-color: #575757; + -cpu-total-color: rgb(0,154,62); +@@ -21,7 +8,6 @@ + -mem-user-color: rgb(210,148,0); + -mem-cached-color: rgb(90,90,90); + -mem-other-color: rgb(205,203,41); +- background-color: #1e1e1e; + } + + .extension-systemMonitor-indicator-label { +-- +2.20.1 + + +From a56d2c1c5546b6f1a6bf66f168874860b427bf9f Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Florian=20M=C3=BCllner?= +Date: Thu, 18 May 2017 16:20:07 +0200 +Subject: [PATCH 3/4] systemMonitor: Handle clicks on section title + +While on 3.24.x only the event section still has a clickable title, +it's a generic message list feature in previous versions. It's easy +enough to support with a small subclass, so use that instead of +the generic baseclass. + +Fixes: #3 +--- + extensions/systemMonitor/extension.js | 20 +++++++++++++++++++- + 1 file changed, 19 insertions(+), 1 deletion(-) + +diff --git a/extensions/systemMonitor/extension.js b/extensions/systemMonitor/extension.js +index 1388a1f..9c010d8 100644 +--- a/extensions/systemMonitor/extension.js ++++ b/extensions/systemMonitor/extension.js +@@ -303,6 +303,24 @@ const MemoryIndicator = new Lang.Class({ + } + }); + ++const SystemMonitorSection = new Lang.Class({ ++ Name: 'SystemMonitorSection', ++ Extends: MessageList.MessageListSection, ++ ++ _init: function() { ++ this.parent(_("System Monitor")); ++ }, ++ ++ _onTitleClicked: function() { ++ this.parent(); ++ ++ let appSys = Shell.AppSystem.get_default(); ++ let app = appSys.lookup_app('gnome-system-monitor.desktop'); ++ if (app) ++ app.open_new_window(-1); ++ } ++}); ++ + const INDICATORS = [CpuIndicator, MemoryIndicator]; + + const Extension = new Lang.Class({ +@@ -317,7 +335,7 @@ const Extension = new Lang.Class({ + }, + + enable: function() { +- this._section = new MessageList.MessageListSection(_("System Monitor")); ++ this._section = new SystemMonitorSection(); + this._indicators = [ ]; + + for (let i = 0; i < INDICATORS.length; i++) { +-- +2.20.1 + + +From 0b22b3fb2f05a098408437d9cd48482fddc24766 Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Florian=20M=C3=BCllner?= +Date: Thu, 18 May 2017 18:00:17 +0200 +Subject: [PATCH 4/4] systemMonitor: Provide classic styling + +The indicator tooltips currently don't work out in classic mode +(dark text on dark background), so provide some mode-specific +style. + +Fixes: #4 +--- + extensions/systemMonitor/classic.css | 6 ++++++ + extensions/systemMonitor/meson.build | 4 ++++ + 2 files changed, 10 insertions(+) + create mode 100644 extensions/systemMonitor/classic.css + +diff --git a/extensions/systemMonitor/classic.css b/extensions/systemMonitor/classic.css +new file mode 100644 +index 0000000..946863d +--- /dev/null ++++ b/extensions/systemMonitor/classic.css +@@ -0,0 +1,6 @@ ++@import url("stylesheet.css"); ++ ++.extension-systemMonitor-indicator-label { ++ background-color: rgba(237,237,237,0.9); ++ border: 1px solid #a1a1a1; ++} +diff --git a/extensions/systemMonitor/meson.build b/extensions/systemMonitor/meson.build +index 48504f6..b6548b1 100644 +--- a/extensions/systemMonitor/meson.build ++++ b/extensions/systemMonitor/meson.build +@@ -3,3 +3,7 @@ extension_data += configure_file( + output: metadata_name, + configuration: metadata_conf + ) ++ ++if classic_mode_enabled ++ extension_data += files('classic.css') ++endif +-- +2.20.1 + diff --git a/SPECS/gnome-shell-extensions.spec b/SPECS/gnome-shell-extensions.spec new file mode 100644 index 0000000..76b4678 --- /dev/null +++ b/SPECS/gnome-shell-extensions.spec @@ -0,0 +1,929 @@ +%global major_version %%(cut -d "." -f 1-2 <<<%{version}) +# Minimum GNOME Shell version supported +%global min_gs_version %%(cut -d "." -f 1-3 <<<%{version}) + +%global pkg_prefix gnome-shell-extension + +Name: gnome-shell-extensions +Version: 3.28.1 +Release: 8%{?dist} +Summary: Modify and extend GNOME Shell functionality and behavior + +Group: User Interface/Desktops +# The entire source code is GPLv2+ except lib/convenience.js which is BSD +License: GPLv2+ and BSD +URL: http://wiki.gnome.org/Projects/GnomeShell/Extensions +Source0: http://ftp.gnome.org/pub/GNOME/sources/%{name}/%{major_version}/%{name}-%{version}.tar.xz +Source1: gnome-classic.desktop +Source2: gnome-classic-wayland.desktop + +# BuildRequires: gnome-common +BuildRequires: meson +BuildRequires: sassc +BuildRequires: git +BuildRequires: gettext >= 0.19.6 +BuildRequires: pkgconfig(gnome-desktop-3.0) +BuildRequires: pkgconfig(libgtop-2.0) +Requires: gnome-shell >= %{min_gs_version} +BuildArch: noarch + +Patch0001: 0001-Update-style.patch +Patch0002: 0001-classic-Shade-panel-in-overview.patch +Patch0003: 0001-apps-menu-add-logo-icon-to-Applications-menu.patch +Patch0004: add-extra-extensions.patch +Patch0005: 0001-apps-menu-Explicitly-set-label_actor.patch +Patch0006: resurrect-system-monitor.patch +Patch0007: 0001-common-get-rid-of-weird-drop-shadow-nex-to-app-menu.patch +Patch0008: 0001-Include-top-icons-in-classic-session.patch + +%description +GNOME Shell Extensions is a collection of extensions providing additional and +optional functionality to GNOME Shell. + +Enabled extensions: + * alternate-tab + * apps-menu + * auto-move-windows + * dash-to-dock + * desktop-icons + * drive-menu + * 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 + + +%package -n %{pkg_prefix}-common +Summary: Files common to GNOME Shell Extensions +Group: User Interface/Desktops +License: GPLv2+ +Requires: gnome-shell >= %{min_gs_version} +# Dock extension no longer provided by GNOME Shell extensions >= 3.7.1 +Obsoletes: %{pkg_prefix}-dock < 3.7.1 +# Alternative-status-menu extension no longer provided by GNOME Shell extensions >= 3.9.5 +Obsoletes: %{pkg_prefix}-alternative-status-menu < 3.9.5 +# Xrandr-indicator extension no longer provided by GNOME Shell extensions >= 3.9.5 +Obsoletes: %{pkg_prefix}-xrandr-indicator < 3.9.90 +# Obsolete extensions dropped in favor of schema overrides by upstream +Obsoletes: %{pkg_prefix}-default-min-max < 3.9.3-1 +Obsoletes: %{pkg_prefix}-static-workspaces < 3.9.3-1 +Obsoletes: %{pkg_prefix}-systemMonitor < 3.15.91 + +%description -n %{pkg_prefix}-common +GNOME Shell Extensions is a collection of extensions providing additional and +optional functionality to GNOME Shell. + +This package provides common data files shared by various extensions. + + +%package -n gnome-classic-session +Summary: GNOME "classic" mode session +Group: User Interface/Desktops +License: GPLv2+ +Requires: %{pkg_prefix}-alternate-tab = %{version}-%{release} +Requires: %{pkg_prefix}-apps-menu = %{version}-%{release} +Requires: %{pkg_prefix}-desktop-icons = %{version}-%{release} +Requires: %{pkg_prefix}-launch-new-instance = %{version}-%{release} +Requires: %{pkg_prefix}-places-menu = %{version}-%{release} +Requires: %{pkg_prefix}-window-list = %{version}-%{release} +Requires: nautilus +# Obsolete fallback mode components +Obsoletes: gnome-applets < 1:3.5.92-5 +%global gnome_applet_sensors_obsolete_ver 3.0.0-6 +Obsoletes: gnome-applet-sensors < %{gnome_applet_sensors_obsolete_ver} +Obsoletes: gnome-applet-sensors-devel < %{gnome_applet_sensors_obsolete_ver} +%global gnome_panel_obsolete_ver 3.6.2-7 +Obsoletes: gnome-panel < %{gnome_panel_obsolete_ver} +Obsoletes: gnome-panel-devel < %{gnome_panel_obsolete_ver} +Obsoletes: gnome-panel-libs < %{gnome_panel_obsolete_ver} + +%description -n gnome-classic-session +This package contains the required components for the GNOME Shell "classic" +mode, which aims to provide a GNOME 2-like user interface. + + +%package -n %{pkg_prefix}-alternate-tab +Summary: Classic Alt+Tab behavior for GNOME Shell +Group: User Interface/Desktops +License: GPLv2+ +Requires: %{pkg_prefix}-common = %{version}-%{release} + +%description -n %{pkg_prefix}-alternate-tab +This GNOME Shell extension changes Alt+Tab to be window-based instead of +app-based. + + +%package -n %{pkg_prefix}-apps-menu +Summary: Application menu for GNOME Shell +Group: User Interface/Desktops +License: GPLv2+ +Requires: %{pkg_prefix}-common = %{version}-%{release} +Requires: gnome-menus + +%description -n %{pkg_prefix}-apps-menu +This GNOME Shell extension adds a GNOME 2.x style menu for applications. + + +%package -n %{pkg_prefix}-auto-move-windows +Summary: Assign specific workspaces to applications in GNOME Shell +Group: User Interface/Desktops +License: GPLv2+ +Requires: %{pkg_prefix}-common = %{version}-%{release} + +%description -n %{pkg_prefix}-auto-move-windows +This GNOME Shell extension enables easy workspace management. A specific +workspace can be assigned to each application as soon as it creates a window, in +a manner configurable with a GSettings key. + + +%package -n %{pkg_prefix}-dash-to-dock +Summary: Show the dash outside the activities overview +Group: User Interface/Desktops +License: GPLv2+ +Requires: %{pkg_prefix}-common = %{version}-%{release} + +%description -n %{pkg_prefix}-dash-to-dock +This GNOME Shell extension makes the dash available outside the activities overview. + + +%package -n %{pkg_prefix}-desktop-icons +Summary: Desktop icons support for the classic experience +Group: User Interface/Desktops +License: GPLv3+ +Requires: %{pkg_prefix}-common = %{version}-%{release} + +%description -n %{pkg_prefix}-desktop-icons +This GNOME Shell extension adds desktop icons support as seen in GNOME 2 + + +%package -n %{pkg_prefix}-drive-menu +Summary: Drive status menu for GNOME Shell +Group: User Interface/Desktops +License: GPLv2+ +Requires: %{pkg_prefix}-common = %{version}-%{release} + +%description -n %{pkg_prefix}-drive-menu +This GNOME Shell extension provides a panel status menu for accessing and +unmounting removable devices. + + +%package -n %{pkg_prefix}-launch-new-instance +Summary: Always launch a new application instance for GNOME Shell +Group: User Interface/Desktops +License: GPLv2+ +Requires: %{pkg_prefix}-common = %{version}-%{release} + +%description -n %{pkg_prefix}-launch-new-instance +This GNOME Shell extension modifies the behavior of clicking in the dash and app +launcher to always launch a new application instance. + + +%package -n %{pkg_prefix}-native-window-placement +Summary: Native window placement for GNOME Shell +Group: User Interface/Desktops +License: GPLv2+ +Requires: %{pkg_prefix}-common = %{version}-%{release} + +%description -n %{pkg_prefix}-native-window-placement +This GNOME Shell extension provides additional configurability for the window +layout in the overview, including a mechanism similar to KDE4. + + +%package -n %{pkg_prefix}-no-hot-corner +Summary: Disable the hot corner in GNOME Shell +Group: User Interface/Desktops +License: GPLv2+ +Requires: %{pkg_prefix}-common = %{version}-%{release} + +%description -n %{pkg_prefix}-no-hot-corner +This GNOME Shell extension disables the hot corner in the top bar. + + +%package -n %{pkg_prefix}-panel-favorites +Summary: Favorite launchers in GNOME Shell's top bar +Group: User Interface/Desktops +License: GPLv2+ +Requires: %{pkg_prefix}-common = %{version}-%{release} + +%description -n %{pkg_prefix}-panel-favorites +This GNOME Shell extension adds favorite launchers to the top bar. + + +%package -n %{pkg_prefix}-places-menu +Summary: Places status menu for GNOME Shell +Group: User Interface/Desktops +License: GPLv2+ +Requires: %{pkg_prefix}-common = %{version}-%{release} + +%description -n %{pkg_prefix}-places-menu +This GNOME Shell extension add a system status menu for quickly navigating +places in the system. + + +%package -n %{pkg_prefix}-screenshot-window-sizer +Summary: Screenshot window sizer for GNOME Shell +Group: User Interface/Desktops +License: GPLv2+ +Requires: %{pkg_prefix}-common = %{version}-%{release} + +%description -n %{pkg_prefix}-screenshot-window-sizer +This GNOME Shell extension allows to easily resize windows for GNOME Software +screenshots. + + +%package -n %{pkg_prefix}-systemMonitor +Summary: System Monitor for GNOME Shell +Group: User Interface/Desktops +License: GPLv2+ +Requires: %{pkg_prefix}-common = %{version}-%{release} +# Should be pulled in by control-center, but in case someone tries for a +# minimalist gnome-shell installation +Requires: libgtop2 + +%description -n %{pkg_prefix}-systemMonitor +This GNOME Shell extension is a message tray indicator for CPU and memory usage + + +%package -n %{pkg_prefix}-top-icons +Summary: Show legacy icons on top +Group: User Interface/Desktops +License: GPLv2+ +Requires: %{pkg_prefix}-common = %{version}-%{release} + +%description -n %{pkg_prefix}-top-icons +This GNOME Shell extension moves legacy tray icons into the top bar. + + +%package -n %{pkg_prefix}-updates-dialog +Summary: Show a modal dialog when there are software updates +Group: User Interface/Desktops +License: GPLv2+ +Requires: %{pkg_prefix}-common = %{version}-%{release} + +%description -n %{pkg_prefix}-updates-dialog +This GNOME Shell extension shows a modal dialog when there are software updates + + +%package -n %{pkg_prefix}-user-theme +Summary: Support for custom themes in GNOME Shell +Group: User Interface/Desktops +License: GPLv2+ +Requires: %{pkg_prefix}-common = %{version}-%{release} + +%description -n %{pkg_prefix}-user-theme +This GNOME Shell extension enables loading a GNOME Shell theme from +~/.themes//gnome-shell/. + + +%package -n %{pkg_prefix}-window-list +Summary: Display a window list at the bottom of the screen in GNOME Shell +Group: User Interface/Desktops +License: GPLv2+ +Requires: %{pkg_prefix}-common = %{version}-%{release} + +%description -n %{pkg_prefix}-window-list +This GNOME Shell extension displays a window list at the bottom of the screen. + + +%package -n %{pkg_prefix}-windowsNavigator +Summary: Support for keyboard selection of windows and workspaces in GNOME Shell +Group: User Interface/Desktops +License: GPLv2+ +Requires: %{pkg_prefix}-common = %{version}-%{release} + +%description -n %{pkg_prefix}-windowsNavigator +This GNOME Shell extension enables keyboard selection of windows and workspaces +in overlay mode, by pressing the Alt and Ctrl key respectively. + + +%package -n %{pkg_prefix}-workspace-indicator +Summary: Workspace indicator for GNOME Shell +Group: User Interface/Desktops +License: GPLv2+ +Requires: %{pkg_prefix}-common = %{version}-%{release} + +%description -n %{pkg_prefix}-workspace-indicator +This GNOME Shell extension add a system status menu for quickly changing +workspaces. + + +%prep +%autosetup -S git + + +%build +%meson -Dextension_set="all" -Dclassic_mode=true +%meson_build + +%install +%meson_install + +# rename GNOME Classic to Classic and provide a wayland variant +mkdir -p $RPM_BUILD_ROOT%{_datadir}/wayland-sessions +cp $RPM_BUILD_ROOT%{_datadir}/xsessions/gnome-classic.desktop \ + $RPM_BUILD_ROOT%{_datadir}/wayland-sessions/gnome-classic-wayland.desktop + +cp $RPM_SOURCE_DIR/gnome-classic-wayland.desktop $RPM_BUILD_ROOT%{_datadir}/wayland-sessions +cp $RPM_SOURCE_DIR/gnome-classic.desktop $RPM_BUILD_ROOT%{_datadir}/xsessions + +# Drop useless example extension +rm -r $RPM_BUILD_ROOT%{_datadir}/gnome-shell/extensions/example*/ +rm $RPM_BUILD_ROOT%{_datadir}/glib-2.0/schemas/org.gnome.shell.extensions.example.gschema.xml + +%find_lang %{name} + + +%files -n %{pkg_prefix}-common -f %{name}.lang +%doc COPYING NEWS README.md + + +%files -n gnome-classic-session +%{_datadir}/gnome-session/sessions/gnome-classic.session +%{_datadir}/gnome-shell/modes/classic.json +%{_datadir}/gnome-shell/theme/*.svg +%{_datadir}/gnome-shell/theme/gnome-classic-high-contrast.css +%{_datadir}/gnome-shell/theme/gnome-classic.css +%{_datadir}/xsessions/gnome-classic.desktop +%{_datadir}/wayland-sessions/gnome-classic-wayland.desktop +%{_datadir}/glib-2.0/schemas/org.gnome.shell.extensions.classic-overrides.gschema.xml + +%files -n %{pkg_prefix}-alternate-tab +%{_datadir}/gnome-shell/extensions/alternate-tab*/ + + +%files -n %{pkg_prefix}-apps-menu +%{_datadir}/gnome-shell/extensions/apps-menu*/ + + +%files -n %{pkg_prefix}-auto-move-windows +%{_datadir}/glib-2.0/schemas/org.gnome.shell.extensions.auto-move-windows.gschema.xml +%{_datadir}/gnome-shell/extensions/auto-move-windows*/ + + +%files -n %{pkg_prefix}-dash-to-dock +%{_datadir}/glib-2.0/schemas/org.gnome.shell.extensions.dash-to-dock.gschema.xml +%{_datadir}/gnome-shell/extensions/dash-to-dock*/ + + +%files -n %{pkg_prefix}-desktop-icons +%{_datadir}/glib-2.0/schemas/org.gnome.shell.extensions.desktop-icons.gschema.xml +%{_datadir}/gnome-shell/extensions/desktop-icons*/ + + +%files -n %{pkg_prefix}-drive-menu +%{_datadir}/gnome-shell/extensions/drive-menu*/ + + +%files -n %{pkg_prefix}-launch-new-instance +%{_datadir}/gnome-shell/extensions/launch-new-instance*/ + + +%files -n %{pkg_prefix}-native-window-placement +%{_datadir}/glib-2.0/schemas/org.gnome.shell.extensions.native-window-placement.gschema.xml +%{_datadir}/gnome-shell/extensions/native-window-placement*/ + + +%files -n %{pkg_prefix}-no-hot-corner +%{_datadir}/gnome-shell/extensions/no-hot-corner*/ + + +%files -n %{pkg_prefix}-panel-favorites +%{_datadir}/gnome-shell/extensions/panel-favorites*/ + + +%files -n %{pkg_prefix}-places-menu +%{_datadir}/gnome-shell/extensions/places-menu*/ + + +%files -n %{pkg_prefix}-screenshot-window-sizer +%{_datadir}/glib-2.0/schemas/org.gnome.shell.extensions.screenshot-window-sizer.gschema.xml +%{_datadir}/gnome-shell/extensions/screenshot-window-sizer*/ + + +%files -n %{pkg_prefix}-systemMonitor +%{_datadir}/gnome-shell/extensions/systemMonitor*/ + + +%files -n %{pkg_prefix}-top-icons +%{_datadir}/gnome-shell/extensions/top-icons*/ + + +%files -n %{pkg_prefix}-updates-dialog +%{_datadir}/glib-2.0/schemas/org.gnome.shell.extensions.updates-dialog.gschema.xml +%{_datadir}/gnome-shell/extensions/updates-dialog*/ + + +%files -n %{pkg_prefix}-user-theme +%{_datadir}/glib-2.0/schemas/org.gnome.shell.extensions.user-theme.gschema.xml +%{_datadir}/gnome-shell/extensions/user-theme*/ + + +%files -n %{pkg_prefix}-window-list +%{_datadir}/gnome-shell/extensions/window-list*/ +%{_datadir}/glib-2.0/schemas/org.gnome.shell.extensions.window-list.gschema.xml + + +%files -n %{pkg_prefix}-windowsNavigator +%{_datadir}/gnome-shell/extensions/windowsNavigator*/ + + +%files -n %{pkg_prefix}-workspace-indicator +%{_datadir}/gnome-shell/extensions/workspace-indicator*/ + + +%changelog +* Mon Feb 11 2019 Florian Müllner - 3.28.1-8 +- Update desktop-icons extension to 19.01 + Resolves: #1666739 + +* Fri Feb 08 2019 Florian Müllner - 3.28.1-7 +- Re-add dropped downstream patches + Resolves: #1668885 + +* Mon Jan 14 2019 Ray Strode - 3.28.1-6 +- Update desktop file names + Related: #1647713 + +* Thu Dec 06 2018 Ray Strode - 3.28.1-5 +- Add requires on desktop-icons extension for classic session + Resolves: #1648863 + +* Tue Sep 04 2018 Ray Strode - 3.28.1-4 +- Add back corporate logo on the left of Activities +- Remove shadow remnants of app logo to the right of Activities + Resolves: #1620241 + +* Wed Aug 22 2018 Ray Strode - 3.28.1-3 +- Add a wayland variant of gnome-classic + Also change up the names to Standard and Classic to match UX design + + Related: #1612915 1595825 + +* Tue Aug 21 2018 Carlos Soriano - 3.28.1-2 +- Add desktop icons extension + +* Fri Apr 13 2018 Florian Müllner - 3.28.1-1 +- Update to 3.28.1 + +* Mon Mar 12 2018 Florian Müllner - 3.28.0-1 +- Update to 3.28.0 + +* Mon Mar 05 2018 Florian Müllner - 3.27.92-1 +- Update to 3.27.92 + +* Thu Feb 22 2018 Florian Müllner - 3.27.91-1 +- Update to 3.27.91 + +* Wed Feb 07 2018 Fedora Release Engineering - 3.27.1-3 +- Rebuilt for https://fedoraproject.org/wiki/Fedora_28_Mass_Rebuild + +* Sat Jan 06 2018 Igor Gnatenko - 3.27.1-2 +- Remove obsolete scriptlets + +* Tue Oct 17 2017 Florian Müllner - 3.27.1-1 +- Update to 3.27.1 + +* Wed Oct 04 2017 Florian Müllner - 3.26.1-1 +- Update to 3.26.1 + +* Tue Sep 12 2017 Florian Müllner - 3.26.0-1 +- Update to 3.26.0 + +* Tue Aug 22 2017 Florian Müllner - 3.25.91-1 +- Update to 3.25.91 + +* Fri Aug 11 2017 Kevin Fenzi - 3.25.90-2 +- Rebuild with older working rpm + +* Thu Aug 10 2017 Florian Müllner - 3.25.90-1 +- Update to 3.25.90 + +* Wed Jul 26 2017 Fedora Release Engineering - 3.25.4-2 +- Rebuilt for https://fedoraproject.org/wiki/Fedora_27_Mass_Rebuild + +* Thu Jul 20 2017 Florian Müllner - 3.25.4-1 +- Update to 3.25.4 + +* Wed Jun 21 2017 Florian Müllner - 3.25.3-1 +- Update to 3.25.3 + +* Thu May 25 2017 Florian Müllner - 3.25.2-1 +- Update to 3.25.2 + +* Thu Apr 27 2017 Florian Müllner - 3.25.1-1 +- Update to 3.25.1 + +* Tue Apr 11 2017 Florian Müllner - 3.24.1-1 +- Update to 3.24.1 + +* Mon Mar 20 2017 Florian Müllner - 3.24.0-1 +- Update to 3.24.0 + +* Tue Mar 14 2017 Florian Müllner - 3.23.92-1 +- Update to 3.23.92 + +* Wed Mar 01 2017 Florian Müllner - 3.23.91-1 +- Update to 3.23.91 + +* Thu Feb 16 2017 Florian Müllner - 3.23.90-1 +- Update to 3.23.90 + +* Fri Feb 10 2017 Fedora Release Engineering - 3.23.2-2 +- Rebuilt for https://fedoraproject.org/wiki/Fedora_26_Mass_Rebuild + +* Wed Nov 23 2016 Florian Müllner - 3.23.2-1 +- Update to 3.23.2 + +* Tue Oct 11 2016 Florian Müllner - 3.22.1-1 +- Update to 3.22.1 + +* Mon Sep 19 2016 Florian Müllner - 3.22.0-1 +- Update to 3.22.0 + +* Tue Sep 13 2016 Florian Müllner - 3.21.92-1 +- Update to 3.21.92 + +* Tue Aug 30 2016 Florian Müllner - 3.21.91-1 +- Update to 3.21.91 + +* Fri Aug 19 2016 Florian Müllner - 3.21.90-1 +- Update to 3.21.90 + +* Wed Jul 20 2016 Florian Müllner - 3.21.4-1 +- Update to 3.21.4 + +* Tue Jun 21 2016 Florian Müllner - 3.21.3-1 +- Update to 3.21.3 + +* Fri May 27 2016 Florian Müllner - 3.21.2-1 +- Update to 3.21.2 + +* Tue May 10 2016 Florian Müllner - 3.20.1-1 +- Update to 3.20.1 + +* Tue Mar 22 2016 Florian Müllner - 3.20.0-1 +- Update to 3.20.0 + +* Wed Mar 16 2016 Florian Müllner - 3.19.92-1 +- Update to 3.19.92 + +* Thu Mar 03 2016 Florian Müllner - 3.19.91-1 +- Update to 3.19.91 + +* Fri Feb 19 2016 Florian Müllner - 3.19.90-1 +- Update to 3.19.90 + +* Wed Feb 03 2016 Fedora Release Engineering - 3.19.4-2 +- Rebuilt for https://fedoraproject.org/wiki/Fedora_24_Mass_Rebuild + +* Thu Jan 21 2016 Florian Müllner - 3.19.4-1 +- Update to 3.19.4 + +* Thu Dec 17 2015 Florian Müllner - 3.19.3-1 +- Update to 3.19.3 + +* Wed Nov 25 2015 Florian Müllner - 3.19.2-1 +- Update to 3.19.2 + +* Thu Oct 29 2015 Florian Müllner - 3.19.1-1 +- Update to 3.19.1 + +* Thu Oct 15 2015 Florian Müllner - 3.18.1-1 +- Update to 3.18.1 + +* Mon Sep 21 2015 Florian Müllner - 3.18.0-1 +- Update to 3.18.0 + +* Wed Sep 16 2015 Florian Müllner - 3.17.92-1 +- Update to 3.17.92 + +* Thu Sep 03 2015 Florian Müllner - 3.17.91-1 +- Update to 3.17.91 + +* Thu Aug 20 2015 Florian Müllner - 3.17.90-1 +- Update to 3.17.90 + +* Wed Aug 19 2015 Kalev Lember - 3.17.4-2 +- Don't own /usr/share/gnome-shell/extensions directory: now part of + gnome-shell package + +* Thu Jul 23 2015 Florian Müllner - 3.17.4-1 +- Update to 3.17.4 + +* Thu Jul 02 2015 Florian Müllner - 3.17.3-1 +- Update to 3.17.3 + +* Wed Jun 17 2015 Fedora Release Engineering - 3.17.2-2 +- Rebuilt for https://fedoraproject.org/wiki/Fedora_23_Mass_Rebuild + +* Wed May 27 2015 Florian Müllner - 3.17.2-1 +- Update to 3.17.2 + +* Fri May 01 2015 Kalev Lember - 3.17.1-2 +- Add glib-compile-schemas rpm scripts for screenshot-window-sizer + +* Thu Apr 30 2015 Florian Müllner - 3.17.1-1 +- Update to 3.17.1 + +* Tue Apr 14 2015 Florian Müllner - 3.16.1-1 +- Update to 3.16.1 + +* Mon Mar 23 2015 Florian Müllner - 3.16.0-1 +- Update to 3.16.0 + +* Tue Mar 17 2015 Florian Müllner - 3.15.92-1 +- Update to 3.15.92 + +* Thu Mar 05 2015 Kalev Lember - 3.15.91-2 +- Obsolete the systemMonitor extension that was dropped in 3.15.91 + +* Thu Mar 05 2015 Florian Müllner - 3.15.91-1 +- Update to 3.15.91 + +* Fri Feb 20 2015 Florian Müllner - 3.15.90-1 +- Update to 3.15.90 + +* Wed Jan 21 2015 Florian Müllner - 3.15.4-1 +- Update to 3.15.4 + +* Fri Dec 19 2014 Florian Müllner - 3.15.3.1-1 +- Update to 3.15.3.1 + +* Fri Dec 19 2014 Florian Müllner - 3.15.3-1 +- Update to 3.15.3 + +* Thu Nov 27 2014 Florian Müllner - 3.15.2-1 +- Update to 3.15.2 + +* Thu Oct 30 2014 Florian Müllner - 3.15.1-1 +- Update to 3.15.1 + +* Tue Oct 14 2014 Florian Müllner - 3.14.1-1 +- Update to 3.14.1 + +* Mon Sep 22 2014 Florian Müllner - 3.14.0-1 +- Update to 3.14.0 + +* Wed Sep 17 2014 Florian Müllner - 3.13.92-1 +- Update to 3.13.92 + +* Wed Sep 03 2014 Florian Müllner - 3.13.91-1 +- Update to 3.13.91 + +* Wed Aug 20 2014 Mohamed El Morabity - 3.13.90-1 +- Update to 3.13.90 + +* Thu Jul 24 2014 Kalev Lember - 3.13.4-1 +- Update to 3.13.4 + +* Thu Jun 26 2014 Richard Hughes - 3.13.3-1 +- Update to 3.13.3 + +* Sat Jun 07 2014 Fedora Release Engineering - 3.13.2-2 +- Rebuilt for https://fedoraproject.org/wiki/Fedora_21_Mass_Rebuild + +* Wed May 28 2014 Mohamed El Morabity - 3.13.2-1 +- Update to 3.13.2 + +* Fri May 02 2014 Kalev Lember - 3.13.1-1 +- Update to 3.13.1 + +* Tue Mar 25 2014 Richard Hughes - 3.12.0-1 +- Update to 3.12.0 + +* Thu Mar 20 2014 Mohamed El Morabity - 3.11.92-1 +- Update to 3.11.92 + +* Thu Mar 06 2014 Mohamed El Morabity - 3.11.91-1 +- Update to 3.11.91 + +* Thu Feb 20 2014 Mohamed El Morabity - 3.11.90-1 +- Update to 3.11.90 + +* Wed Feb 05 2014 Mohamed El Morabity - 3.11.5-1 +- Update to 3.11.5 + +* Mon Feb 03 2014 Mohamed El Morabity - 3.11.4-1 +- Update to 3.11.4 + +* Sun Dec 22 2013 Mohamed El Morabity - 3.11.3-1 +- Update to 3.11.3 + +* Wed Nov 13 2013 Mohamed El Morabity - 3.11.2-1 +- Update to 3.11.2 + +* Wed Oct 16 2013 Mohamed El Morabity - 3.10.1-1 +- Update to 3.10.1 + +* Tue Sep 24 2013 Mohamed El Morabity - 3.10.0-1 +- Update to 3.10.0 + +* Tue Sep 17 2013 Mohamed El Morabity - 3.9.92-1 +- Update to 3.9.92 + +* Tue Sep 03 2013 Mohamed El Morabity - 3.9.91-1 +- Update to 3.9.91 + +* Thu Aug 22 2013 Mohamed El Morabity - 3.9.90-1 +- Update to 3.9.90 +- Drop xrand-indicator subpackage, no longer provided upstream + +* Mon Aug 12 2013 Mohamed El Morabity - 3.9.5-3 +- Fix alternative-status-menu subpackage obsoleting + +* Mon Aug 12 2013 Nils Philippsen - 3.9.5-2 +- obsolete alternative-status-menu subpackage to allow smooth upgrades + +* Sun Aug 04 2013 Mohamed El Morabity - 3.9.5-1 +- Update to 3.9.5 +- Drop alternative-status-menu subpackage, no longer provided upstream + +* Sat Aug 03 2013 Fedora Release Engineering - 3.9.3-2 +- Rebuilt for https://fedoraproject.org/wiki/Fedora_20_Mass_Rebuild + +* Thu Jun 20 2013 Rahul Sundaram - 3.9.3-1 +- Update to 3.9.3 +- Obsolete default-min-max and static workspaces extensions +- Use make_install macro +- Fix bogus dates in spec changelog + +* Tue May 28 2013 Mohamed El Morabity - 3.9.2-1 +- Update to 3.9.2 + +* Fri May 10 2013 Mohamed El Morabity - 3.9.1-1 +- Update to 3.9.1 + +* Fri May 10 2013 Kalev Lember - 3.8.1-3 +- Obsolete gnome-applet-sensors + +* Wed May 01 2013 Kalev Lember - 3.8.1-2 +- Obsolete a few more fallback mode packages +- Remove gnome-panel provides + +* Tue Apr 16 2013 Matthias Clasen - 3.8.1-1 +- Update to 3.8.1 + +* Tue Mar 26 2013 Mohamed El Morabity - 3.8.0-1 +- Update to 3.8.0 + +* Tue Mar 19 2013 Ray Strode 3.7.92-1 +- Update to 3.7.92 + +* Tue Mar 05 2013 Mohamed El Morabity - 3.7.91-1 +- Update to 3.7.91 + +* Sat Mar 02 2013 Adel Gadllah - 3.7.90-2 +- Obsolete gnome-panel + +* Fri Feb 22 2013 Kalev Lember - 3.7.90-1 +- Update to 3.7.90 + +* Thu Feb 07 2013 Kalev Lember - 3.7.5.1-2 +- Depend on gnome-shell 3.7.5, there's no 3.7.5.1 + +* Thu Feb 07 2013 Mohamed El Morabity - 3.7.5.1-1 +- Update to 3.7.5 +- Enable new launch-new-instance and window-list extensions, and add them in the + classic-mode extension set +- Re-add places-menu in the classic-mode extension set + +* Wed Jan 16 2013 Mohamed El Morabity - 3.7.4-1 +- Update to 3.7.4 +- places-menu extension no longer part of the classic-mode extension set + +* Tue Jan 01 2013 Mohamed El Morabity - 3.7.3-1 +- Update to 3.7.3 +- Enable new default-min-max and static-workspaces extensions +- Provide new subpackage gnome-classic-session +- Revamp summaries and descriptions + +* Tue Oct 30 2012 Mohamed El Morabity - 3.7.1-1 +- Update to 3.7.1 +- Drop dock and gajim extensions, no longer provided + +* Tue Oct 30 2012 Mohamed El Morabity - 3.6.1-1 +- Update to 3.6.1 + +* Tue Oct 02 2012 Mohamed El Morabity - 3.6.0-1 +- Update to 3.6.0 + +* Thu Sep 06 2012 Mohamed El Morabity - 3.5.91-1 +- Update to 3.5.91 + +* Wed Aug 29 2012 Mohamed El Morabity - 3.5.90-1 +- Update to 3.5.90 + +* Sat Aug 11 2012 Mohamed El Morabity - 3.5.5-1 +- Update to 3.5.5 + +* Sun Jul 22 2012 Mohamed El Morabity - 3.5.4-1 +- Update to 3.5.4 + +* Wed Jul 18 2012 Mohamed El Morabity - 3.5.2-1 +- Update to 3.5.2 +- Drop useless Provides/Obsoletes + +* Sat Mar 24 2012 Mohamed El Morabity - 3.4.0-1 +- Update to 3.4.0 +- Minor spec fixes + +* Sat Mar 24 2012 Mohamed El Morabity - 3.3.92-1 +- Update to 3.3.92 + +* Tue Feb 28 2012 Mohamed El Morabity - 3.3.90-1 +- Update to 3.3.90 + +* Thu Feb 16 2012 Mohamed El Morabity - 3.3.5-1 +- Update to 3.3.5 +- Spec cleanup + +* Fri Jan 13 2012 Fedora Release Engineering - 3.3.2-2 +- Rebuilt for https://fedoraproject.org/wiki/Fedora_17_Mass_Rebuild + +* Wed Nov 30 2011 Mohamed El Morabity - 3.3.2-1 +- Update to 3.3.2 + +* Wed Nov 30 2011 Mohamed El Morabity - 3.2.1-1 +- Update to 3.2.1 +- Fix alternative-status-menu extension crash when login + +* Wed Nov 09 2011 Mohamed El Morabity - 3.2.0-2 +- Fix dock and alternate-tab extensions +- Fix GNOME Shell version to work with GS 3.2.1 + +* Mon Oct 03 2011 Mohamed El Morabity - 3.2.0-1 +- Update to 3.2.0 + +* Mon Sep 26 2011 Mohamed El Morabity - 3.1.91-3.20111001gite102c0c6 +- Update to a newer git snapshot +- Fix GNOME Shell version to work with GS 3.2.0 +- Add Requires on GS 3.2.0 or above to gnome-shell-common + +* Wed Sep 14 2011 Mohamed El Morabity - 3.1.91-2 +- Enable xrandr-indicator and workspace-indicator extensions + +* Mon Sep 12 2011 Michel Salim - 3.1.91-1 +- Update to 3.1.91 +- add more documentation + +* Thu Sep 1 2011 Michel Salim - 3.1.4-3.20110830git6b5e3a3e +- Update to git snapshot, for gnome-shell 3.1.90 + +* Sun Aug 21 2011 Michel Salim - 3.1.4-2 +- Enable apps-menu extension +- Spec cleanup + +* Sun Aug 21 2011 Michel Salim - 3.1.4-1 +- Update to 3.1.4 +- Enable systemMonitor extension +- Prepare xrandr-indicator, commenting out since it does not seem to work yet +- Rename subpackages in line with new guidelines (# 715367) +- Sort subpackages in alphabetical order + +* Sat May 28 2011 Timur Kristóf - 3.0.2-1.g63dd27cgit +- Update to a newer git snapshot +- Fix RHBZ bug #708230 +- Enabled systemMonitor extension, but commented out since the requirements are not available + +* Fri May 13 2011 Mohamed El Morabity - 3.0.1-3.03660fgit +- Update to a newer git snapshot +- Enable native-window-placement extension + +* Fri May 06 2011 Rahul Sundaram - 3.0.1-2b20cbagit +- Fix description + +* Thu May 5 2011 Elad Alfassa - 3.0.1-1.b20cbagit +- Update to a newer git snapshot +- Enabled the places-menu extension + +* Tue Apr 26 2011 Mohamed El Morabity - 3.0.1-1.f016b9git +- Update to a newer git snapshot (post-3.0.1 release) +- Enable drive-menu extension + +* Mon Apr 11 2011 Mohamed El Morabity - 3.0.0-5.6d56cfgit +- Enable auto-move-windows extension + +* Mon Apr 11 2011 Rahul Sundaram - 3.0.0-4.6d56cfgit +- Add glib2-devel as build requires + +* Mon Apr 11 2011 Rahul Sundaram - 3.0.0-3.6d56cfgit +- Tweak description +- Fix typo in configure + +* Mon Apr 11 2011 Rahul Sundaram - 3.0.0-2.6d56cfgit +- Added the user-theme extension +- Patch from Timur Kristóf + +* Fri Apr 08 2011 Rahul Sundaram - 3.0.0-1.6d56cfgit +- Make sure configure doesn't get called twice + +* Fri Apr 08 2011 Rahul Sundaram - 3.0.0-0.6d56cfgit +- Initial build