diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..3529e4d --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +SOURCES/gnome-shell-extensions-3.32.1.tar.xz diff --git a/.gnome-shell-extensions.metadata b/.gnome-shell-extensions.metadata new file mode 100644 index 0000000..4e2ddcc --- /dev/null +++ b/.gnome-shell-extensions.metadata @@ -0,0 +1 @@ +51c1c16bcd0dc9125834b32d7c539c38fa9c4f52 SOURCES/gnome-shell-extensions-3.32.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..9c5283f --- /dev/null +++ b/SOURCES/0001-Include-top-icons-in-classic-session.patch @@ -0,0 +1,32 @@ +From a3db60786407481efbfc4875f887d03b58f0a7b7 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 6764f9a..32743ed 100644 +--- a/meson.build ++++ b/meson.build +@@ -36,6 +36,7 @@ classic_extensions = [ + 'desktop-icons', + 'places-menu', + 'launch-new-instance', ++ 'top-icons', + 'window-list' + ] + +@@ -56,7 +57,6 @@ all_extensions += [ + 'no-hot-corner', + 'panel-favorites', + 'systemMonitor', +- 'top-icons', + 'updates-dialog', + 'user-theme', + 'window-grouper' +-- +2.21.0 + diff --git a/SOURCES/0001-Update-style.patch b/SOURCES/0001-Update-style.patch new file mode 100644 index 0000000..4999c50 --- /dev/null +++ b/SOURCES/0001-Update-style.patch @@ -0,0 +1,79 @@ +From 4137197669569a05d2b4f936c9e5872a8c6a96a5 Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Florian=20M=C3=BCllner?= +Date: Sat, 18 May 2019 19:37:05 +0200 +Subject: [PATCH] Update style + +--- + data/gnome-shell-sass | 2 +- + 1 file changed, 1 insertion(+), 1 deletion(-) + +Submodule data/gnome-shell-sass 1a56956..0116ded: +diff --git a/data/gnome-shell-sass/_common.scss b/data/gnome-shell-sass/_common.scss +index a6357ba..b48f4fc 100644 +--- a/data/gnome-shell-sass/_common.scss ++++ b/data/gnome-shell-sass/_common.scss +@@ -680,7 +680,8 @@ StScrollBar { + spacing: 8px; + } + +- .ws-switcher-active-up, .ws-switcher-active-down { ++ .ws-switcher-active-up, .ws-switcher-active-down, ++ .ws-switcher-active-left, .ws-switcher-active-right { + height: 50px; + background-color: $selected_bg_color; + color: $selected_fg_color; +@@ -781,6 +782,11 @@ StScrollBar { + color: lighten($fg_color,10%); + } + ++ .panel-logo-icon { ++ padding-right: .4em; ++ icon-size: 1em; ++ } ++ + .system-status-icon { icon-size: 1.09em; padding: 0 5px; } + .unlock-screen &, + .login-screen &, +@@ -1406,6 +1412,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; +@@ -1801,7 +1815,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; +@@ -1856,6 +1875,10 @@ StScrollBar { + padding-bottom: 12px; + spacing: 8px; + width: 23em; ++ .login-dialog-timed-login-indicator { ++ height: 2px; ++ background-color: darken($fg_color,40%); ++ } + } + + .login-dialog-prompt-label { +-- +2.21.0 + diff --git a/SOURCES/0001-apps-menu-Explicitly-set-label_actor.patch b/SOURCES/0001-apps-menu-Explicitly-set-label_actor.patch new file mode 100644 index 0000000..131ea79 --- /dev/null +++ b/SOURCES/0001-apps-menu-Explicitly-set-label_actor.patch @@ -0,0 +1,40 @@ +From 52ee25effa3debb21307e33ac223cf48ac7bc57a 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 d62e3d7..cc399c6 100644 +--- a/extensions/apps-menu/extension.js ++++ b/extensions/apps-menu/extension.js +@@ -29,7 +29,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) { +@@ -120,7 +122,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.21.0 + 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..973c077 --- /dev/null +++ b/SOURCES/0001-apps-menu-add-logo-icon-to-Applications-menu.patch @@ -0,0 +1,32 @@ +From 3e3634b59455da0cbae1de4af0ce5cf97be8b80d 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 | 8 ++++++++ + 1 file changed, 8 insertions(+) + +diff --git a/extensions/apps-menu/extension.js b/extensions/apps-menu/extension.js +index d7ba570..d62e3d7 100644 +--- a/extensions/apps-menu/extension.js ++++ b/extensions/apps-menu/extension.js +@@ -390,6 +390,14 @@ class ApplicationsButton extends PanelMenu.Button { + + 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, +-- +2.21.0 + 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..348a13d --- /dev/null +++ b/SOURCES/0001-classic-Shade-panel-in-overview.patch @@ -0,0 +1,34 @@ +From babe8815e035e3cfe99b67e4d450676e43db7353 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.21.0 + diff --git a/SOURCES/add-extra-extensions.patch b/SOURCES/add-extra-extensions.patch new file mode 100644 index 0000000..108707a --- /dev/null +++ b/SOURCES/add-extra-extensions.patch @@ -0,0 +1,24410 @@ +From d97e40a53d1844c84c895ede9a102e6d9d73ec1d 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/8] Add top-icons extension + +--- + extensions/top-icons/extension.js | 96 +++++++++++++++++++++++++++ + 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, 113 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..a8eec13 +--- /dev/null ++++ b/extensions/top-icons/extension.js +@@ -0,0 +1,96 @@ ++// -*- mode: js; js-indent-level: 4; indent-tabs-mode: nil -*- ++/* exported init */ ++ ++const { Clutter, Shell, St } = imports.gi; ++const Main = imports.ui.main; ++const PanelMenu = imports.ui.panelMenu; ++const System = imports.system; ++ ++const PANEL_ICON_SIZE = 16; ++ ++const STANDARD_TRAY_ICON_IMPLEMENTATIONS = [ ++ 'bluetooth-applet', ++ 'gnome-sound-applet', ++ 'nm-applet', ++ 'gnome-power-manager', ++ 'keyboard', ++ 'a11y-keyboard', ++ 'kbd-scrolllock', ++ 'kbd-numlock', ++ 'kbd-capslock', ++ 'ibus-ui-gtk' ++]; ++ ++class SysTray { ++ constructor() { ++ this._icons = []; ++ this._tray = null; ++ } ++ ++ _onTrayIconAdded(o, icon) { ++ let wmClass = icon.wm_class ? icon.wm_class.toLowerCase() : ''; ++ if (STANDARD_TRAY_ICON_IMPLEMENTATIONS.includes(wmClass)) ++ return; ++ ++ let button = new PanelMenu.Button(0.5, null, true); ++ ++ let scaleFactor = St.ThemeContext.get_for_stage(global.stage).scale_factor; ++ let iconSize = PANEL_ICON_SIZE * scaleFactor; ++ ++ icon.set({ ++ width: iconSize, ++ height: iconSize, ++ x_align: Clutter.ActorAlign.CENTER, ++ y_align: Clutter.ActorAlign.CENTER ++ }); ++ ++ let iconBin = new St.Widget({ ++ layout_manager: new Clutter.BinLayout() ++ }); ++ iconBin.add_actor(icon); ++ button.add_actor(iconBin); ++ ++ this._icons.push(icon); ++ ++ button.connect('button-release-event', (actor, event) => { ++ icon.click(event); ++ }); ++ button.connect('key-press-event', (actor, event) => { ++ icon.click(event); ++ }); ++ ++ icon.connect('destroy', () => { ++ button.destroy(); ++ }); ++ ++ let role = wmClass || `${icon}`; ++ Main.panel.addToStatusArea(role, button); ++ } ++ ++ _onTrayIconRemoved(o, icon) { ++ let parent = icon.get_parent(); ++ parent.destroy(); ++ this._icons.splice(this._icons.indexOf(icon), 1); ++ } ++ ++ enable() { ++ this._tray = new Shell.TrayManager(); ++ this._tray.connect('tray-icon-added', ++ this._onTrayIconAdded.bind(this)); ++ this._tray.connect('tray-icon-removed', ++ this._onTrayIconRemoved.bind(this)); ++ this._tray.manage_screen(Main.panel); ++ } ++ ++ disable() { ++ this._icons.forEach(icon => icon.get_parent().destroy()); ++ this._icons = []; ++ ++ this._tray = null; ++ System.gc(); // force finalizing tray to unmanage screen ++ } ++} ++ ++function init() { ++ return new SysTray(); ++} +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 b987f2d..6050c32 100644 +--- a/meson.build ++++ b/meson.build +@@ -50,6 +50,7 @@ all_extensions = default_extensions + all_extensions += [ + 'auto-move-windows', + 'native-window-placement', ++ 'top-icons', + 'user-theme' + ] + +-- +2.21.0 + + +From 3035a9e961fdae1bd242664130fbf55ce67131ce 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/8] Add dash-to-dock extension + +--- + extensions/dash-to-dock/Settings.ui | 3335 +++++++++++++++++ + extensions/dash-to-dock/appIconIndicators.js | 1102 ++++++ + extensions/dash-to-dock/appIcons.js | 1172 ++++++ + extensions/dash-to-dock/dash.js | 1171 ++++++ + extensions/dash-to-dock/docking.js | 1853 +++++++++ + extensions/dash-to-dock/extension.js | 23 + + extensions/dash-to-dock/intellihide.js | 321 ++ + extensions/dash-to-dock/launcherAPI.js | 239 ++ + extensions/dash-to-dock/media/glossy.svg | 139 + + .../media/highlight_stacked_bg.svg | 82 + + .../media/highlight_stacked_bg_h.svg | 82 + + 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 | 861 +++++ + extensions/dash-to-dock/stylesheet.css | 175 + + extensions/dash-to-dock/theming.js | 569 +++ + extensions/dash-to-dock/utils.js | 258 ++ + extensions/dash-to-dock/windowPreview.js | 578 +++ + meson.build | 1 + + 21 files changed, 13064 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/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/highlight_stacked_bg.svg + create mode 100644 extensions/dash-to-dock/media/highlight_stacked_bg_h.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..c141eff +--- /dev/null ++++ b/extensions/dash-to-dock/Settings.ui +@@ -0,0 +1,3335 @@ ++ ++ ++ ++ ++ ++ 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 ++ Focus 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 ++ Focus 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 ++ Focus 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 ++ Focus 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 ++ 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 ++ ++ ++ True ++ 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..ddff512 +--- /dev/null ++++ b/extensions/dash-to-dock/appIconIndicators.js +@@ -0,0 +1,1102 @@ ++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 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 = class DashToDock_AppIconIndicator { ++ ++ constructor(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 RunningIndicatorDefault(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() { ++ for (let i=0; i { ++ this._signalsHandler.destroy(); ++ }); ++ } ++ ++ update() { ++ } ++ ++ destroy() { ++ this._source.actor.disconnect(this._sourceDestroyId); ++ this._signalsHandler.destroy(); ++ } ++}; ++ ++/* ++ * A base indicator class for running style, from which all other EunningIndicators should derive, ++ * providing some basic methods, variables definitions and their update, css style classes handling. ++ * ++ */ ++var RunningIndicatorBase = class DashToDock_RunningIndicatorBase extends IndicatorBase { ++ ++ constructor(source, settings) { ++ super(source, settings) ++ ++ this._side = Utils.getPosition(this._settings); ++ this._nWindows = 0; ++ ++ this._dominantColorExtractor = new DominantColorExtractor(this._source.app); ++ ++ // These statuses take into account the workspace/monitor isolation ++ this._isFocused = false; ++ this._isRunning = false; ++ } ++ ++ update() { ++ // Limit to 1 to MAX_WINDOWS_CLASSES windows classes ++ this._nWindows = Math.min(this._source.getInterestingWindows().length, MAX_WINDOWS_CLASSES); ++ ++ // We need to check the number of windows, as the focus might be ++ // happening on another monitor if using isolation ++ if (tracker.focus_app == this._source.app && this._nWindows > 0) ++ this._isFocused = true; ++ else ++ this._isFocused = false; ++ ++ // 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() { ++ 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() { ++ if (this._isFocused) ++ this._source.actor.add_style_class_name('focused'); ++ else ++ this._source.actor.remove_style_class_name('focused'); ++ } ++ ++ _updateDefaultDot() { ++ if (this._isRunning) ++ this._source._dot.show(); ++ else ++ this._source._dot.hide(); ++ } ++ ++ _hideDefaultDot() { ++ // 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() { ++ this._source._dot.opacity = 255; ++ } ++ ++ _enableBacklight() { ++ ++ 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() { ++ this._source._iconContainer.set_style(null); ++ } ++ ++ destroy() { ++ 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(); ++ ++ super.destroy(); ++ } ++}; ++ ++// We add a css class so third parties themes can limit their indicaor customization ++// to the case we do nothing ++var RunningIndicatorDefault = class DashToDock_RunningIndicatorDefault extends RunningIndicatorBase { ++ ++ constructor(source, settings) { ++ super(source, settings); ++ this._source.actor.add_style_class_name('default'); ++ } ++ ++ destory() { ++ this._source.actor.remove_style_class_name('default'); ++ super.destroy(); ++ } ++}; ++ ++var RunningIndicatorDots = class DashToDock_RunningIndicatorDots extends RunningIndicatorBase { ++ ++ constructor(source, settings) { ++ super(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', this._updateIndicator.bind(this)); ++ 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, ++ this.update.bind(this) ++ ]); ++ }, this); ++ ++ // Apply glossy background ++ // TODO: move to enable/disableBacklit to apply itonly to the running apps? ++ // TODO: move to css class for theming support ++ this._glossyBackgroundStyle = 'background-image: url(\'' + Me.path + '/media/glossy.svg\');' + ++ 'background-size: contain;'; ++ } ++ ++ update() { ++ super.update(); ++ ++ // 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() { ++ ++ 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() { ++ ++ let area = this._area; ++ let cr = this._area.get_context(); ++ ++ this._computeStyle(); ++ this._drawIndicator(cr); ++ cr.$dispose(); ++ } ++ ++ _drawIndicator(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() { ++ this._area.destroy(); ++ super.destroy(); ++ } ++}; ++ ++// Adapted from dash-to-panel by Jason DeRose ++// https://github.com/jderose9/dash-to-panel ++var RunningIndicatorCiliora = class DashToDock_RunningIndicatorCiliora extends RunningIndicatorDots { ++ ++ _drawIndicator(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 ++var RunningIndicatorSegmented = class DashToDock_RunningIndicatorSegmented extends RunningIndicatorDots { ++ ++ _drawIndicator(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 ++var RunningIndicatorSolid = class DashToDock_RunningIndicatorSolid extends RunningIndicatorDots { ++ ++ _drawIndicator(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 ++var RunningIndicatorSquares = class DashToDock_RunningIndicatorSquares extends RunningIndicatorDots { ++ ++ _drawIndicator(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 ++var RunningIndicatorDashes = class DashToDock_RunningIndicatorDashes extends RunningIndicatorDots { ++ ++ _drawIndicator(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 ++var RunningIndicatorMetro = class DashToDock_RunningIndicatorMetro extends RunningIndicatorDots { ++ ++ constructor(source, settings) { ++ super(source, settings); ++ this._source.actor.add_style_class_name('metro'); ++ } ++ ++ destroy() { ++ this._source.actor.remove_style_class_name('metro'); ++ super.destroy(); ++ } ++ ++ _drawIndicator(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(); ++ } ++ } ++ } ++} ++ ++/* ++ * Unity like notification and progress indicators ++ */ ++var UnityIndicator = class DashToDock_UnityIndicator extends IndicatorBase { ++ ++ constructor(source, settings) { ++ ++ super(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', this.updateNotificationBadge.bind(this)); ++ ++ this._remoteEntries = []; ++ this._source.remoteModel.lookupById(this._source.app.id).forEach( ++ (entry) => { ++ this.insertEntryRemote(entry); ++ } ++ ); ++ ++ this._signalsHandler.add([ ++ this._source.remoteModel, ++ 'entry-added', ++ this._onLauncherEntryRemoteAdded.bind(this) ++ ], [ ++ this._source.remoteModel, ++ 'entry-removed', ++ this._onLauncherEntryRemoteRemoved.bind(this) ++ ]) ++ } ++ ++ _onLauncherEntryRemoteAdded(remoteModel, entry) { ++ if (!entry || !entry.appId()) ++ return; ++ if (this._source && this._source.app && this._source.app.id == entry.appId()) { ++ this.insertEntryRemote(entry); ++ } ++ } ++ ++ _onLauncherEntryRemoteRemoved(remoteModel, entry) { ++ if (!entry || !entry.appId()) ++ return; ++ ++ if (this._source && this._source.app && this._source.app.id == entry.appId()) { ++ this.removeEntryRemote(entry); ++ } ++ } ++ ++ updateNotificationBadge() { ++ 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(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(count) { ++ this._notificationBadgeCount = count; ++ let text = this._notificationBadgeCountToText(count); ++ this._notificationBadgeLabel.set_text(text); ++ } ++ ++ toggleNotificationBadge(activate) { ++ if (activate && this._notificationBadgeCount > 0) { ++ this.updateNotificationBadge(); ++ this._notificationBadgeBin.show(); ++ } ++ else ++ this._notificationBadgeBin.hide(); ++ } ++ ++ _showProgressOverlay() { ++ if (this._progressOverlayArea) { ++ this._updateProgressOverlay(); ++ return; ++ } ++ ++ this._progressOverlayArea = new St.DrawingArea({x_expand: true, y_expand: true}); ++ this._progressOverlayArea.connect('repaint', () => { ++ this._drawProgressOverlay(this._progressOverlayArea); ++ }); ++ ++ this._source._iconContainer.add_child(this._progressOverlayArea); ++ this._updateProgressOverlay(); ++ } ++ ++ _hideProgressOverlay() { ++ if (this._progressOverlayArea) ++ this._progressOverlayArea.destroy(); ++ this._progressOverlayArea = null; ++ } ++ ++ _updateProgressOverlay() { ++ if (this._progressOverlayArea) ++ this._progressOverlayArea.queue_repaint(); ++ } ++ ++ _drawProgressOverlay(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(progress) { ++ this._progress = Math.min(Math.max(progress, 0.0), 1.0); ++ this._updateProgressOverlay(); ++ } ++ ++ toggleProgressOverlay(activate) { ++ if (activate) { ++ this._showProgressOverlay(); ++ } ++ else { ++ this._hideProgressOverlay(); ++ } ++ } ++ ++ insertEntryRemote(remote) { ++ if (!remote || this._remoteEntries.indexOf(remote) !== -1) ++ return; ++ ++ this._remoteEntries.push(remote); ++ this._selectEntryRemote(remote); ++ } ++ ++ removeEntryRemote(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(remote) { ++ if (!remote) ++ return; ++ ++ this._signalsHandler.removeWithLabel('entry-remotes'); ++ ++ this._signalsHandler.addWithLabel('entry-remotes', ++ [ ++ remote, ++ 'count-changed', ++ (remote, value) => { ++ this.setNotificationBadge(value); ++ } ++ ], [ ++ remote, ++ 'count-visible-changed', ++ (remote, value) => { ++ this.toggleNotificationBadge(value); ++ } ++ ], [ ++ remote, ++ 'progress-changed', ++ (remote, value) => { ++ this.setProgress(value); ++ } ++ ], [ ++ remote, ++ 'progress-visible-changed', ++ (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. ++var DominantColorExtractor = class DashToDock_DominantColorExtractor { ++ ++ constructor(app) { ++ this._app = app; ++ } ++ ++ /** ++ * Try to get the pixel buffer for the current icon, if not fail gracefully ++ */ ++ _getIconPixBuf() { ++ 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() { ++ 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 (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..3b28304 +--- /dev/null ++++ b/extensions/dash-to-dock/appIcons.js +@@ -0,0 +1,1172 @@ ++// -*- 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 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, ++ FOCUS_OR_PREVIEWS: 7, ++ QUIT: 8, ++}; ++ ++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 = class DashToDock_AppIcon extends AppDisplay.AppIcon { ++ ++ // settings are required inside. ++ constructor(settings, remoteModel, app, monitorIndex, iconParams) { ++ super(app, 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._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', ++ this.onWindowsChanged.bind(this)); ++ this._focusAppChangeId = tracker.connect('notify::focus-app', ++ this._onFocusAppChanged.bind(this)); ++ ++ // 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.display, ++ 'window-entered-monitor', ++ this._onWindowEntered.bind(this) ++ ]); ++ } ++ ++ 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, ++ this._updateIndicatorStyle.bind(this) ++ ]); ++ }, this); ++ ++ this._dtdSettings.connect('changed::scroll-action', () => { ++ this._optionalScrollCycleWindows(); ++ }); ++ this._optionalScrollCycleWindows(); ++ ++ this._numberOverlay(); ++ ++ this._previewMenuManager = null; ++ this._previewMenu = null; ++ } ++ ++ _onDestroy() { ++ super._onDestroy(); ++ ++ // 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() { ++ ++ if (this._indicator !== null) { ++ this._indicator.destroy(); ++ this._indicator = null; ++ } ++ this._indicator = new AppIconIndicators.AppIconIndicator(this, this._dtdSettings); ++ this._indicator.update(); ++ } ++ ++ _onWindowEntered(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() { ++ 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', ++ this.onScrollEvent.bind(this)); ++ } ++ ++ onScrollEvent(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, () => { ++ 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() { ++ ++ if (this._menu && this._menu.isOpen) ++ this._menu.update(); ++ ++ this._indicator.update(); ++ this.updateIconGeometry(); ++ } ++ ++ /** ++ * Update taraget for minimization animation ++ */ ++ updateIconGeometry() { ++ // 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() { ++ // 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() { ++ this._removeMenuTimeout(); ++ this.actor.fake_release(); ++ this._draggable.fakeRelease(); ++ ++ if (!this._menu) { ++ this._menu = new MyAppIconMenu(this, this._dtdSettings); ++ this._menu.connect('activate-window', (menu, window) => { ++ this.activateWindow(window); ++ }); ++ this._menu.connect('open-state-changed', (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', () => { ++ 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() { ++ this._indicator.update(); ++ } ++ ++ activate(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 ++ super.activate(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.FOCUS_OR_PREVIEWS: ++ if (this.app == focusedApp && ++ (windows.length > 1 || modifiers || button != 1)) { ++ this._windowPreviews(); ++ } else { ++ // Activate the first window ++ let w = windows[0]; ++ Main.activateWindow(w); ++ } ++ 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() { ++ return this.actor.hover && (!this._menu || !this._menu.isOpen) && ++ (!this._previewMenu || !this._previewMenu.isOpen); ++ } ++ ++ _windowPreviews() { ++ 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', (menu, isPoppedUp) => { ++ if (!isPoppedUp) ++ this._onMenuPoppedDown(); ++ }); ++ let id = Main.overview.connect('hiding', () => { ++ 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(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() { ++ // 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() { ++ // 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(number) { ++ this._numberOverlayOrder = number; ++ this._numberOverlayLabel.set_text(number.toString()); ++ } ++ ++ toggleNumberOverlay(activate) { ++ if (activate && this._numberOverlayOrder > -1) { ++ this.updateNumberOverlay(); ++ this._numberOverlayBin.show(); ++ } ++ else ++ this._numberOverlayBin.hide(); ++ } ++ ++ _minimizeWindow(param) { ++ // Param true make all app windows minimize ++ let windows = this.getInterestingWindows(); ++ let current_workspace = global.workspace_manager.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() { ++ // 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.workspace_manager.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() { ++ let windows = this.getInterestingWindows(); ++ for (let i = 0; i < windows.length; i++) ++ windows[i].delete(global.get_current_time()); ++ } ++ ++ _cycleThroughWindows(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() { ++ 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() { ++ 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 = class DashToDock_MyAppIconMenu extends AppDisplay.AppIconMenu { ++ ++ constructor(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 ++ super(source); ++ ++ // Change the initialized side where required. ++ this._arrowSide = side; ++ this._boxPointer._arrowSide = side; ++ this._boxPointer._userArrowSide = side; ++ ++ this._dtdSettings = settings; ++ } ++ ++ _redisplay() { ++ 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', () => { ++ 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', () => { ++ 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', (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', () => { ++ let favs = AppFavorites.getAppFavorites(); ++ favs.removeFavorite(this._source.app.get_id()); ++ }); ++ } else { ++ let item = this._appendMenuItem(_("Add to Favorites")); ++ item.connect('activate', () => { ++ let favs = AppFavorites.getAppFavorites(); ++ favs.addFavorite(this._source.app.get_id()); ++ }); ++ } ++ } ++ ++ if (Shell.AppSystem.get_default().lookup_app('org.gnome.Software.desktop')) { ++ this._appendSeparator(); ++ let item = this._appendMenuItem(_("Show Details")); ++ item.connect('activate', () => { ++ let id = this._source.app.get_id(); ++ let args = GLib.Variant.new('(ss)', [id, '']); ++ Gio.DBus.get(Gio.BusType.SESSION, null, ++ 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 { ++ super._redisplay(); ++ } ++ ++ // quit menu ++ this._appendSeparator(); ++ this._quitfromDashMenuItem = this._appendMenuItem(_("Quit")); ++ this._quitfromDashMenuItem.connect('activate', () => { ++ 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() { ++ ++ 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 %d Windows").format(windows.length)); ++ ++ 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(this._updateSeparatorVisibility.bind(this)); ++ } ++ ++ ++ } ++ ++ _populateAllWindowMenu(windows) { ++ ++ this._allWindowsMenuItem.menu.removeAll(); ++ ++ if (windows.length > 0) { ++ ++ let activeWorkspace = global.workspace_manager.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', () => { ++ this.emit('activate-window', window); ++ }); ++ ++ // This is to achieve a more gracefull transition when the last windows is closed. ++ item.connect('destroy', () => { ++ 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.workspace_manager.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 = class DashToDock_ShowAppsIconWrapper { ++ constructor(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 = (actor, button) => { ++ this._removeMenuTimeout(); ++ }; ++ ++ this.actor.connect('leave-event', this._onLeaveEvent.bind(this)); ++ this.actor.connect('button-press-event', this._onButtonPress.bind(this)); ++ this.actor.connect('touch-event', this._onTouchEvent.bind(this)); ++ this.actor.connect('clicked', this._onClicked.bind(this)); ++ this.actor.connect('popup-menu', this._onKeyboardPopupMenu.bind(this)); ++ ++ this._menu = null; ++ this._menuManager = new PopupMenu.PopupMenuManager(this); ++ this._menuTimeoutId = 0; ++ ++ this.realShowAppsIcon._dtdSettings = settings; ++ this.realShowAppsIcon.showLabel = itemShowLabel; ++ } ++ ++ popupMenu() { ++ this._removeMenuTimeout(); ++ this.actor.fake_release(); ++ ++ if (!this._menu) { ++ this._menu = new MyShowAppsIconMenu(this, this._dtdSettings); ++ this._menu.connect('open-state-changed', (menu, isPoppedUp) => { ++ if (!isPoppedUp) ++ this._onMenuPoppedDown(); ++ }); ++ let id = Main.overview.connect('hiding', () => { ++ 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 ++ */ ++var MyShowAppsIconMenu = class DashToDock_MyShowAppsIconMenu extends MyAppIconMenu { ++ _redisplay() { ++ 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/dash.js b/extensions/dash-to-dock/dash.js +new file mode 100644 +index 0000000..a646256 +--- /dev/null ++++ b/extensions/dash-to-dock/dash.js +@@ -0,0 +1,1171 @@ ++// -*- 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 GObject = imports.gi.GObject; ++const Gtk = imports.gi.Gtk; ++const Signals = imports.signals; ++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 ++ */ ++var MyDashActor = GObject.registerClass( ++class DashToDock_MyDashActor extends St.Widget { ++ ++ _init(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 ++ }); ++ ++ super._init({ ++ name: 'dash', ++ layout_manager: layout, ++ clip_to_allocation: true ++ }); ++ ++ // Since we are usually visible but not usually changing, make sure ++ // most repaint requests don't actually require us to repaint anything. ++ // This saves significant CPU when repainting the screen. ++ this.set_offscreen_redirect(Clutter.OffscreenRedirect.ALWAYS); ++ } ++ ++ vfunc_allocate(box, flags) { ++ this.set_allocation(box, flags); ++ let contentBox = box; ++ let availWidth = contentBox.x2 - contentBox.x1; ++ let availHeight = contentBox.y2 - contentBox.y1; ++ ++ let [appIcons, showAppsButton] = this.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); ++ } ++ } ++ ++ vfunc_get_preferred_width(forHeight) { ++ // 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.layout_manager.get_preferred_width(this, forHeight); ++ ++ let themeNode = this.get_theme_node(); ++ let [, showAppsButton] = this.get_children(); ++ let [minWidth, ] = showAppsButton.get_preferred_height(forHeight); ++ ++ return [minWidth, natWidth]; ++ } ++ ++ vfunc_get_preferred_height(forWidth) { ++ // 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.layout_manager.get_preferred_height(this, forWidth); ++ ++ let themeNode = this.get_theme_node(); ++ let [, showAppsButton] = this.get_children(); ++ let [minHeight, ] = showAppsButton.get_preferred_height(forWidth); ++ ++ return [minHeight, 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 = class DashToDock_MyDash { ++ ++ constructor(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._container = new MyDashActor(settings); ++ 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', this._onScrollEvent.bind(this)); ++ ++ 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', (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', () => { ++ if (this._maxHeight != this.actor.width) ++ this._queueRedisplay(); ++ this._maxHeight = this.actor.width; ++ }); ++ } ++ else { ++ this.actor.connect('notify::height', () => { ++ 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', this._updateAppsIconGeometry.bind(this)); ++ let scrollViewAdjustment = this._isHorizontal ? this._scrollView.hscroll.adjustment : this._scrollView.vscroll.adjustment; ++ scrollViewAdjustment.connect('notify::value', this._updateAppsIconGeometry.bind(this)); ++ ++ this._workId = Main.initializeDeferredWork(this._box, this._redisplay.bind(this)); ++ ++ this._settings = new Gio.Settings({ ++ schema_id: 'org.gnome.shell' ++ }); ++ ++ this._appSystem = Shell.AppSystem.get_default(); ++ ++ this._signalsHandler.add([ ++ this._appSystem, ++ 'installed-changed', ++ () => { ++ AppFavorites.getAppFavorites().reload(); ++ this._queueRedisplay(); ++ } ++ ], [ ++ AppFavorites.getAppFavorites(), ++ 'changed', ++ this._queueRedisplay.bind(this) ++ ], [ ++ this._appSystem, ++ 'app-state-changed', ++ this._queueRedisplay.bind(this) ++ ], [ ++ Main.overview, ++ 'item-drag-begin', ++ this._onDragBegin.bind(this) ++ ], [ ++ Main.overview, ++ 'item-drag-end', ++ this._onDragEnd.bind(this) ++ ], [ ++ Main.overview, ++ 'item-drag-cancelled', ++ this._onDragCancelled.bind(this) ++ ]); ++ } ++ ++ destroy() { ++ this._signalsHandler.destroy(); ++ } ++ ++ _onScrollEvent(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() { ++ this._dragCancelled = false; ++ this._dragMonitor = { ++ dragMotion: this._onDragMotion.bind(this) ++ }; ++ 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() { ++ this._dragCancelled = true; ++ this._endDrag(); ++ } ++ ++ _onDragEnd() { ++ if (this._dragCancelled) ++ return; ++ ++ this._endDrag(); ++ } ++ ++ _endDrag() { ++ this._clearDragPlaceholder(); ++ this._clearEmptyDropTarget(); ++ this._showAppsIcon.setDragApp(null); ++ DND.removeDragMonitor(this._dragMonitor); ++ } ++ ++ _onDragMotion(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(apps) { ++ let ids = {}; ++ for (let i = 0; i < apps.length; i++) ++ ids[apps[i].get_id()] = apps[i]; ++ return ids; ++ } ++ ++ _queueRedisplay() { ++ Main.queueDeferredWork(this._workId); ++ } ++ ++ _hookUpLabel(item, appIcon) { ++ item.child.connect('notify::hover', () => { ++ this._syncLabel(item, appIcon); ++ }); ++ ++ let id = Main.overview.connect('hiding', () => { ++ this._labelShowing = false; ++ item.hideLabel(); ++ }); ++ item.child.connect('destroy', function() { ++ Main.overview.disconnect(id); ++ }); ++ ++ if (appIcon) { ++ appIcon.connect('sync-tooltip', () => { ++ this._syncLabel(item, appIcon); ++ }); ++ } ++ } ++ ++ _createAppItem(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', () => { ++ appIcon.actor.opacity = 50; ++ }); ++ appIcon._draggable.connect('drag-end', () => { ++ appIcon.actor.opacity = 255; ++ }); ++ } ++ ++ appIcon.connect('menu-state-changed', (appIcon, opened) => { ++ this._itemMenuStateChanged(item, opened); ++ }); ++ ++ let item = new Dash.DashItemContainer(); ++ ++ extendDashItemContainer(item, this._dtdSettings); ++ item.setChild(appIcon.actor); ++ ++ appIcon.actor.connect('notify::hover', () => { ++ if (appIcon.actor.hover) { ++ this._ensureAppIconVisibilityTimeoutId = Mainloop.timeout_add(100, () => { ++ 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', (actor) => { ++ ensureActorVisibleInScrollView(this._scrollView, actor); ++ }); ++ ++ appIcon.actor.connect('key-focus-in', (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() { ++ // 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() { ++ let appIcons = this.getAppIcons(); ++ appIcons.forEach(function(icon) { ++ icon.updateIconGeometry(); ++ }); ++ } ++ ++ _itemMenuStateChanged(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(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, () => { ++ 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, () => { ++ this._labelShowing = false; ++ this._resetHoverTimeoutId = 0; ++ return GLib.SOURCE_REMOVE; ++ }); ++ GLib.Source.set_name_by_id(this._resetHoverTimeoutId, '[gnome-shell] this._labelShowing'); ++ } ++ } ++ } ++ ++ _adjustIconSize() { ++ // 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() { ++ 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() { ++ 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(activate) { ++ let appIcons = this.getAppIcons(); ++ appIcons.forEach(function(icon) { ++ icon.toggleNumberOverlay(activate); ++ }); ++ } ++ ++ _initializeIconSize(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 { ++ this._animatingPlaceholdersCount--; ++ }); ++ this._dragPlaceholder = null; ++ } ++ this._dragPlaceholderPos = -1; ++ } ++ ++ _clearEmptyDropTarget() { ++ if (this._emptyDropTarget) { ++ this._emptyDropTarget.animateOutAndDestroy(); ++ this._emptyDropTarget = null; ++ } ++ } ++ ++ handleDragOver(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 DND.DragMotionResult.NO_DROP; ++ ++ if (!this._settings.is_writable('favorite-apps') || !this._dtdSettings.get_boolean('show-favorites')) ++ return DND.DragMotionResult.NO_DROP; ++ ++ let favorites = AppFavorites.getAppFavorites().getFavorites(); ++ let numFavorites = favorites.length; ++ ++ let favPos = favorites.indexOf(app); ++ ++ let children = this._box.get_children(); ++ let numChildren = children.length; ++ let boxHeight = 0; ++ for (let i = 0; i < numChildren; i++) ++ boxHeight += this._isHorizontal?children[i].width:children[i].height; ++ ++ // Keep the placeholder out of the index calculation; assuming that ++ // the remove target has the same size as "normal" items, we don't ++ // need to do the same adjustment there. ++ if (this._dragPlaceholder) { ++ boxHeight -= this._isHorizontal?this._dragPlaceholder.width:this._dragPlaceholder.height; ++ numChildren--; ++ } ++ ++ let pos; ++ if (!this._emptyDropTarget) { ++ pos = Math.floor((this._isHorizontal?x:y) * numChildren / boxHeight); ++ if (pos > 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(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, () => { ++ let appFavorites = AppFavorites.getAppFavorites(); ++ if (srcIsFavorite) ++ appFavorites.moveFavoriteToPos(id, favPos); ++ else ++ appFavorites.addFavoriteAtPos(id, favPos); ++ return false; ++ }); ++ ++ return true; ++ } ++ ++ showShowAppsButton() { ++ this.showAppsButton.visible = true ++ this.showAppsButton.set_width(-1) ++ this.showAppsButton.set_height(-1) ++ } ++ ++ hideShowAppsButton() { ++ 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..d35094b +--- /dev/null ++++ b/extensions/dash-to-dock/docking.js +@@ -0,0 +1,1853 @@ ++// -*- mode: js; js-indent-level: 4; indent-tabs-mode: nil -*- ++ ++const Clutter = imports.gi.Clutter; ++const GLib = imports.gi.GLib; ++const GObject = imports.gi.GObject; ++const Gtk = imports.gi.Gtk; ++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 ExtensionUtils = imports.misc.extensionUtils; ++const Me = ExtensionUtils.getCurrentExtension(); ++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. ++*/ ++var DashSlideContainer = GObject.registerClass( ++class DashToDock_DashSlideContainer extends St.Widget { ++ ++ _init(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]; ++ } ++ } ++ ++ super._init(params); ++ 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 ++ } ++ ++ vfunc_allocate(box, flags) { ++ this.set_allocation(box, flags); ++ ++ if (this._child == null) ++ return; ++ ++ let availWidth = box.x2 - box.x1; ++ let availHeight = box.y2 - box.y1; ++ let [, , 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 ++ */ ++ vfunc_get_preferred_width(forHeight) { ++ 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; ++ } ++ return [minWidth, natWidth]; ++ } ++ ++ /** ++ * Just the child height but taking into account the slided out part ++ */ ++ vfunc_get_preferred_height(forWidth) { ++ 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; ++ } ++ return [minHeight, natHeight]; ++ } ++ ++ /** ++ * I was expecting it to be a virtual function... stil I don't understand ++ * how things work. ++ */ ++ add_child(actor) { ++ // I'm supposed to have only on child ++ if (this._child !== null) ++ this.remove_child(actor); ++ ++ this._child = actor; ++ super.add_child(actor); ++ } ++ ++ set slidex(value) { ++ this._slidex = value; ++ this._child.queue_relayout(); ++ } ++ ++ get slidex() { ++ return this._slidex; ++ } ++}); ++ ++var DockedDash = class DashToDock { ++ ++ constructor(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', this._hoverChanged.bind(this)); ++ ++ // 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', ++ this._onDragStart.bind(this) ++ ], [ ++ Main.overview, ++ 'item-drag-end', ++ this._onDragEnd.bind(this) ++ ], [ ++ Main.overview, ++ 'item-drag-cancelled', ++ this._onDragEnd.bind(this) ++ ], [ ++ // update when workarea changes, for instance if other extensions modify the struts ++ //(like moving th panel at the bottom) ++ global.display, ++ 'workareas-changed', ++ this._resetPosition.bind(this) ++ ], [ ++ Main.overview, ++ 'showing', ++ this._onOverviewShowing.bind(this) ++ ], [ ++ Main.overview, ++ 'hiding', ++ this._onOverviewHiding.bind(this) ++ ], [ ++ // Hide on appview ++ Main.overview.viewSelector, ++ 'page-changed', ++ this._pageChanged.bind(this) ++ ], [ ++ Main.overview.viewSelector, ++ 'page-empty', ++ this._onPageEmpty.bind(this) ++ ], [ ++ // Ensure the ShowAppsButton status is kept in sync ++ Main.overview.viewSelector._showAppsButton, ++ 'notify::checked', ++ this._syncShowAppsButtonToggled.bind(this) ++ ], [ ++ global.display, ++ 'in-fullscreen-changed', ++ this._updateBarrier.bind(this) ++ ], [ ++ // Monitor windows overlapping ++ this._intellihide, ++ 'status-changed', ++ this._updateDashVisibility.bind(this) ++ ], [ ++ // Keep dragged icon consistent in size with this dash ++ this.dash, ++ 'icon-size-changed', ++ () => { 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', ++ () => { Main.overview.dashIconSize = this.dash.iconSize; } ++ ], [ ++ // sync hover after a popupmenu is closed ++ this.dash, ++ 'menu-closed', ++ () => { this._box.sync_hover() } ++ ]); ++ ++ 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', ++ Main.layoutManager._queueUpdateRegions.bind(Main.layoutManager)); ++ ++ this.dash._container.connect('allocation-changed', this._updateStaticBox.bind(this)); ++ this._slider.connect(this._isHorizontal ? 'notify::x' : 'notify::y', this._updateStaticBox.bind(this)); ++ ++ // 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', this._initialize.bind(this)); ++ ++ // 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); ++ 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, {affectsStruts: true}); ++ } ++ else ++ Main.layoutManager._trackActor(this._slider); ++ ++ // Set initial position ++ this._resetDepth(); ++ this._resetPosition(); ++ } ++ ++ _initialize() { ++ 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() { ++ // 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(); ++ ++ } ++ ++ _bindSettingsChanges() { ++ this._signalsHandler.add([ ++ this._settings, ++ 'changed::scroll-action', ++ () => { this._optionalScrollWorkspaceSwitch(); } ++ ], [ ++ this._settings, ++ 'changed::dash-max-icon-size', ++ () => { this.dash.setIconSize(this._settings.get_int('dash-max-icon-size')); } ++ ], [ ++ this._settings, ++ 'changed::icon-size-fixed', ++ () => { this.dash.setIconSize(this._settings.get_int('dash-max-icon-size')); } ++ ], [ ++ this._settings, ++ 'changed::show-favorites', ++ () => { this.dash.resetAppIcons(); } ++ ], [ ++ this._settings, ++ 'changed::show-running', ++ () => { this.dash.resetAppIcons(); } ++ ], [ ++ this._settings, ++ 'changed::show-apps-at-top', ++ () => { this.dash.resetAppIcons(); } ++ ], [ ++ this._settings, ++ 'changed::show-show-apps-button', ++ () => { ++ if (this._settings.get_boolean('show-show-apps-button')) ++ this.dash.showShowAppsButton(); ++ else ++ this.dash.hideShowAppsButton(); ++ } ++ ], [ ++ this._settings, ++ 'changed::dock-fixed', ++ () => { ++ 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); ++ Main.layoutManager._trackActor(this._slider, {affectsStruts: true}); ++ } else { ++ Main.layoutManager._untrackActor(this.actor); ++ Main.layoutManager._untrackActor(this._slider); ++ Main.layoutManager._trackActor(this._slider); ++ } ++ ++ this._resetPosition(); ++ ++ // Add or remove barrier depending on if dock-fixed ++ this._updateBarrier(); ++ ++ this._updateVisibilityMode(); ++ } ++ ], [ ++ this._settings, ++ 'changed::intellihide', ++ this._updateVisibilityMode.bind(this) ++ ], [ ++ this._settings, ++ 'changed::intellihide-mode', ++ () => { this._intellihide.forceUpdate(); } ++ ], [ ++ this._settings, ++ 'changed::autohide', ++ () => { ++ this._updateVisibilityMode(); ++ this._updateBarrier(); ++ } ++ ], [ ++ this._settings, ++ 'changed::autohide-in-fullscreen', ++ this._updateBarrier.bind(this) ++ ], ++ [ ++ this._settings, ++ 'changed::extend-height', ++ this._resetPosition.bind(this) ++ ], [ ++ this._settings, ++ 'changed::height-fraction', ++ this._resetPosition.bind(this) ++ ], [ ++ this._settings, ++ 'changed::require-pressure-to-show', ++ () => { ++ // Remove pointer watcher ++ if (this._dockWatch) { ++ PointerWatcher.getPointerWatcher()._removeWatch(this._dockWatch); ++ this._dockWatch = null; ++ } ++ this._setupDockDwellIfNeeded(); ++ this._updateBarrier(); ++ } ++ ], [ ++ this._settings, ++ 'changed::pressure-threshold', ++ () => { ++ this._updatePressureBarrier(); ++ this._updateBarrier(); ++ } ++ ]); ++ ++ } ++ ++ /** ++ * This is call when visibility settings change ++ */ ++ _updateVisibilityMode() { ++ 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() { ++ 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() { ++ this._ignoreHover = true; ++ this._intellihide.disable(); ++ this._removeAnimations(); ++ this._animateIn(this._settings.get_double('animation-time'), 0); ++ } ++ ++ _onOverviewHiding() { ++ this._ignoreHover = false; ++ this._intellihide.enable(); ++ this._updateDashVisibility(); ++ } ++ ++ _hoverChanged() { ++ 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() { ++ return this._dockState; ++ } ++ ++ _show() { ++ 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() { ++ // 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(time, delay) { ++ this._dockState = State.SHOWING; ++ ++ Tweener.addTween(this._slider, { ++ slidex: 1, ++ time: time, ++ delay: delay, ++ transition: 'easeOutQuad', ++ onComplete: () => { ++ 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, this._removeBarrier.bind(this)); ++ } ++ }); ++ } ++ ++ _animateOut(time, delay) { ++ this._dockState = State.HIDING; ++ Tweener.addTween(this._slider, { ++ slidex: 0, ++ time: time, ++ delay: delay , ++ transition: 'easeOutQuad', ++ onComplete: () => { ++ 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() { ++ // 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, this._checkDockDwell.bind(this)); ++ this._dockDwelling = false; ++ this._dockDwellUserTime = 0; ++ } ++ } ++ ++ _checkDockDwell(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, ++ this._dockDwellTimeout.bind(this)); ++ GLib.Source.set_name_by_id(this._dockDwellTimeoutId, '[dash-to-dock] this._dockDwellTimeout'); ++ } ++ this._dockDwelling = true; ++ } ++ else { ++ this._cancelDockDwell(); ++ this._dockDwelling = false; ++ } ++ } ++ ++ _cancelDockDwell() { ++ if (this._dockDwellTimeoutId != 0) { ++ Mainloop.source_remove(this._dockDwellTimeoutId); ++ this._dockDwellTimeoutId = 0; ++ } ++ } ++ ++ _dockDwellTimeout() { ++ 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() { ++ 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', (barrier) => { ++ if (!this._settings.get_boolean('autohide-in-fullscreen') && this._monitor.inFullscreen) ++ return; ++ this._onPressureSensed(); ++ }); ++ } ++ } ++ ++ /** ++ * handler for mouse pressure sensed ++ */ ++ _onPressureSensed() { ++ 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, () => { ++ 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() { ++ 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() { ++ // 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() { ++ return (this._monitorIndex == Main.layoutManager.primaryIndex); ++ } ++ ++ _resetPosition() { ++ // 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; ++ } ++ ++ // Set the dash at the correct depth in z ++ _resetDepth() { ++ // Keep the dash below the modalDialogGroup ++ Main.layoutManager.uiGroup.set_child_below_sibling(this.actor, Main.layoutManager.modalDialogGroup); ++ } ++ ++ _updateStaticBox() { ++ this.staticBox.init_rect( ++ this.actor.x + this._slider.x - (this._position == St.Side.RIGHT ? this._box.width : 0), ++ this.actor.y + this._slider.y - (this._position == St.Side.BOTTOM ? this._box.height : 0), ++ this._box.width, ++ this._box.height ++ ); ++ ++ this._intellihide.updateTargetBox(this.staticBox); ++ } ++ ++ _removeAnimations() { ++ Tweener.removeTweens(this._slider); ++ } ++ ++ _onDragStart() { ++ // 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() { ++ // 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() { ++ 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() { ++ /* 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() { ++ 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() { ++ 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() { ++ // Restore dash accessibility ++ Main.ctrlAltTabManager.addGroup( ++ this.dash.actor, _('Dash'), 'user-bookmarks-symbolic', ++ {focusCallback: this._onAccessibilityFocus.bind(this)}); ++ } ++ ++ /** ++ * Switch workspace by scrolling over the dock ++ */ ++ _optionalScrollWorkspaceSwitch() { ++ let label = 'optionalScrollWorkspaceSwitch'; ++ ++ function isEnabled() { ++ return this._settings.get_enum('scroll-action') === scrollAction.SWITCH_WORKSPACE; ++ } ++ ++ this._settings.connect('changed::scroll-action', () => { ++ if (isEnabled.bind(this)()) ++ enable.bind(this)(); ++ else ++ disable.bind(this)(); ++ }); ++ ++ if (isEnabled.bind(this)()) ++ enable.bind(this)(); ++ ++ function enable() { ++ this._signalsHandler.removeWithLabel(label); ++ ++ this._signalsHandler.addWithLabel(label, [ ++ this._box, ++ 'scroll-event', ++ onScrollEvent.bind(this) ++ ]); ++ ++ 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.workspace_manager.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, () => { ++ 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(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 DashToDock_KeyboardShortcuts_NUM_HOTKEYS = 10; ++ ++var KeyboardShortcuts = class DashToDock_KeyboardShortcuts { ++ ++ constructor(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', ++ () => { ++ if (this._settings.get_boolean('hot-keys')) ++ this._enableHotKeys.bind(this)(); ++ else ++ this._disableHotKeys.bind(this)(); ++ } ++ ]); ++ ++ this._optionalNumberOverlay(); ++ } ++ ++ destroy() { ++ // Remove keybindings ++ this._disableHotKeys(); ++ this._disableExtraShortcut(); ++ this._signalsHandler.destroy(); ++ } ++ ++ _enableHotKeys() { ++ 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 < DashToDock_KeyboardShortcuts_NUM_HOTKEYS; i++) { ++ let appNum = i; ++ Main.wm.addKeybinding(key + (i + 1), this._settings, ++ Meta.KeyBindingFlags.NONE, ++ Shell.ActionMode.NORMAL | Shell.ActionMode.OVERVIEW, ++ () => { ++ this._allDocks[0]._activateApp(appNum); ++ this._showOverlay(); ++ }); ++ } ++ }, this); ++ ++ this._hotKeysEnabled = true; ++ } ++ ++ _disableHotKeys() { ++ if (!this._hotKeysEnabled) ++ return; ++ ++ let keys = ['app-hotkey-', 'app-shift-hotkey-', 'app-ctrl-hotkey-']; ++ keys.forEach( function(key) { ++ for (let i = 0; i < DashToDock_KeyboardShortcuts_NUM_HOTKEYS; i++) ++ Main.wm.removeKeybinding(key + (i + 1)); ++ }, this); ++ ++ this._hotKeysEnabled = false; ++ } ++ ++ _optionalNumberOverlay() { ++ 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', ++ this._checkHotkeysOptions.bind(this) ++ ], [ ++ this._settings, ++ 'changed::hotkeys-overlay', ++ this._checkHotkeysOptions.bind(this) ++ ], [ ++ this._settings, ++ 'changed::hotkeys-show-dock', ++ this._checkHotkeysOptions.bind(this) ++ ]); ++ } ++ ++ _checkHotkeysOptions() { ++ 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() { ++ if (!this._shortcutIsSet) { ++ Main.wm.addKeybinding('shortcut', this._settings, ++ Meta.KeyBindingFlags.NONE, ++ Shell.ActionMode.NORMAL | Shell.ActionMode.OVERVIEW, ++ this._showOverlay.bind(this)); ++ this._shortcutIsSet = true; ++ } ++ } ++ ++ _disableExtraShortcut() { ++ if (this._shortcutIsSet) { ++ Main.wm.removeKeybinding('shortcut'); ++ this._shortcutIsSet = false; ++ } ++ } ++ ++ _showOverlay() { ++ 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, () => { ++ 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. ++ */ ++var WorkspaceIsolation = class DashToDock_WorkspaceIsolation { ++ ++ constructor(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', ++ () => { ++ this._allDocks.forEach(function(dock) { ++ dock.dash.resetAppIcons(); ++ }); ++ if (this._settings.get_boolean('isolate-workspaces') || ++ this._settings.get_boolean('isolate-monitors')) ++ this._enable.bind(this)(); ++ else ++ this._disable.bind(this)(); ++ } ++ ],[ ++ this._settings, ++ 'changed::isolate-monitors', ++ () => { ++ this._allDocks.forEach(function(dock) { ++ dock.dash.resetAppIcons(); ++ }); ++ if (this._settings.get_boolean('isolate-workspaces') || ++ this._settings.get_boolean('isolate-monitors')) ++ this._enable.bind(this)(); ++ else ++ this._disable.bind(this)(); ++ } ++ ]); ++ ++ if (this._settings.get_boolean('isolate-workspaces') || ++ this._settings.get_boolean('isolate-monitors')) ++ this._enable(); ++ ++ } ++ ++ _enable() { ++ ++ // ensure I never double-register/inject ++ // although it should never happen ++ this._disable(); ++ ++ this._allDocks.forEach(function(dock) { ++ this._signalsHandler.addWithLabel('isolation', [ ++ global.display, ++ 'restacked', ++ dock.dash._queueRedisplay.bind(dock.dash) ++ ], [ ++ global.window_manager, ++ 'switch-workspace', ++ dock.dash._queueRedisplay.bind(dock.dash) ++ ]); ++ ++ // 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.display, ++ 'window-entered-monitor', ++ dock.dash._queueRedisplay.bind(dock.dash) ++ ]); ++ ++ }, 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.workspace_manager.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.workspace_manager.get_active_workspace())) ++ return Main.activateWindow(windows[0]); ++ return this.open_new_window(-1); ++ } ++ ++ this._injectionsHandler.addWithLabel('isolation', [ ++ Shell.App.prototype, ++ 'activate', ++ IsolatedOverview ++ ]); ++ } ++ ++ _disable () { ++ this._signalsHandler.removeWithLabel('isolation'); ++ this._injectionsHandler.removeWithLabel('isolation'); ++ } ++ ++ destroy() { ++ this._signalsHandler.destroy(); ++ this._injectionsHandler.destroy(); ++ } ++}; ++ ++ ++var DockManager = class DashToDock_DockManager { ++ ++ constructor() { ++ this._remoteModel = new LauncherAPI.LauncherEntryRemoteModel(); ++ this._settings = ExtensionUtils.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() { ++ this._deleteDocks(); ++ this._createDocks(); ++ this.emit('toggled'); ++ } ++ ++ _bindSettingsChanges() { ++ // Connect relevant signals to the toggling function ++ this._signalsHandler = new Utils.GlobalSignalsHandler(); ++ this._signalsHandler.add([ ++ Meta.MonitorManager.get(), ++ 'monitors-changed', ++ this._toggle.bind(this) ++ ], [ ++ this._settings, ++ 'changed::multi-monitor', ++ this._toggle.bind(this) ++ ], [ ++ this._settings, ++ 'changed::preferred-monitor', ++ this._toggle.bind(this) ++ ], [ ++ this._settings, ++ 'changed::dock-position', ++ this._toggle.bind(this) ++ ], [ ++ this._settings, ++ 'changed::extend-height', ++ this._adjustPanelCorners.bind(this) ++ ], [ ++ this._settings, ++ 'changed::dock-fixed', ++ this._adjustPanelCorners.bind(this) ++ ]); ++ } ++ ++ _createDocks() { ++ ++ // If there are no monitors (headless configurations, but it can also happen temporary while disconnecting ++ // and reconnecting monitors), just do nothing. When a monitor will be connected we we'll be notified and ++ // and thus create the docks. This prevents pointing trying to access monitors throughout the code, were we ++ // are assuming that at least the primary monitor is present. ++ if (Main.layoutManager.monitors.length <= 0) { ++ return; ++ } ++ ++ 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', this._onShowAppsButtonToggled.bind(this)); ++ ++ // 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', this._onShowAppsButtonToggled.bind(this)); ++ } ++ } ++ ++ // 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() { ++ // 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() { ++ // 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() { ++ 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(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', () => { ++ Main.overview.disconnect(overviewShownId); ++ Meta.later_add(Meta.LaterType.BEFORE_REDRAW, () => { ++ 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, () => { ++ 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() { ++ this._signalsHandler.destroy(); ++ this._deleteDocks(); ++ this._revertPanelCorners(); ++ this._restoreDash(); ++ this._remoteModel.destroy(); ++ } ++ ++ /** ++ * Adjust Panel corners ++ */ ++ _adjustPanelCorners() { ++ 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() { ++ 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..f025fae +--- /dev/null ++++ b/extensions/dash-to-dock/extension.js +@@ -0,0 +1,23 @@ ++// -*- mode: js; js-indent-level: 4; indent-tabs-mode: nil -*- ++ ++const ExtensionUtils = imports.misc.extensionUtils; ++const Me = ExtensionUtils.getCurrentExtension(); ++const Docking = Me.imports.docking; ++ ++// We declare this with var so it can be accessed by other extensions in ++// GNOME Shell 3.26+ (mozjs52+). ++var dockManager; ++ ++function init() { ++ ExtensionUtils.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..f102ea3 +--- /dev/null ++++ b/extensions/dash-to-dock/intellihide.js +@@ -0,0 +1,321 @@ ++// -*- mode: js; js-indent-level: 4; indent-tabs-mode: nil -*- ++ ++const GLib = imports.gi.GLib; ++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 = class DashToDock_Intellihide { ++ ++ constructor(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', ++ this._windowCreated.bind(this) ++ ], [ ++ // triggered for instance when the window list order changes, ++ // included when the workspace is switched ++ global.display, ++ 'restacked', ++ this._checkOverlap.bind(this) ++ ], [ ++ // 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', ++ this._checkOverlap.bind(this) ++ ], [ ++ // update wne monitor changes, for instance in multimonitor when monitor are attached ++ Meta.MonitorManager.get(), ++ 'monitors-changed', ++ this._checkOverlap.bind(this) ++ ]); ++ } ++ ++ destroy() { ++ // Disconnect global signals ++ this._signalsHandler.destroy(); ++ ++ // Remove residual windows signals ++ this.disable(); ++ } ++ ++ enable() { ++ this._isEnabled = true; ++ this._status = OverlapStatus.UNDEFINED; ++ global.get_window_actors().forEach(function(wa) { ++ this._addWindowSignals(wa); ++ }, this); ++ this._doCheckOverlap(); ++ } ++ ++ disable() { ++ 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(display, metaWindow) { ++ this._addWindowSignals(metaWindow.get_compositor_private()); ++ } ++ ++ _addWindowSignals(wa) { ++ if (!this._handledWindow(wa)) ++ return; ++ let signalId = wa.connect('allocation-changed', this._checkOverlap.bind(this)); ++ this._trackedWindows.set(wa, signalId); ++ wa.connect('destroy', this._removeWindowSignals.bind(this)); ++ } ++ ++ _removeWindowSignals(wa) { ++ if (this._trackedWindows.get(wa)) { ++ wa.disconnect(this._trackedWindows.get(wa)); ++ this._trackedWindows.delete(wa); ++ } ++ ++ } ++ ++ updateTargetBox(box) { ++ this._targetBox = box; ++ this._checkOverlap(); ++ } ++ ++ forceUpdate() { ++ this._status = OverlapStatus.UNDEFINED; ++ this._doCheckOverlap(); ++ } ++ ++ getOverlapStatus() { ++ return (this._status == OverlapStatus.TRUE); ++ } ++ ++ _checkOverlap() { ++ 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, () => { ++ this._doCheckOverlap(); ++ if (this._checkOverlapTimeoutContinue) { ++ this._checkOverlapTimeoutContinue = false; ++ return GLib.SOURCE_CONTINUE; ++ } else { ++ this._checkOverlapTimeoutId = 0; ++ return GLib.SOURCE_REMOVE; ++ } ++ }); ++ } ++ ++ _doCheckOverlap() { ++ ++ 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(wa) { ++ let meta_win = wa.get_meta_window(); ++ if (!this._handledWindow(wa)) ++ return false; ++ ++ let currentWorkspace = global.workspace_manager.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(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..f0b6199 +--- /dev/null ++++ b/extensions/dash-to-dock/launcherAPI.js +@@ -0,0 +1,239 @@ ++// -*- mode: js; js-indent-level: 4; indent-tabs-mode: nil -*- ++ ++const Gio = imports.gi.Gio; ++const Signals = imports.signals; ++ ++var LauncherEntryRemoteModel = class DashToDock_LauncherEntryRemoteModel { ++ ++ constructor() { ++ 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, ++ this._onEntrySignalReceived.bind(this)); ++ ++ 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, ++ this._onDBusNameOwnerChanged.bind(this)); ++ ++ this._acquireUnityDBus(); ++ } ++ ++ destroy() { ++ 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() { ++ return Object.keys(this._entriesByDBusName).length; ++ } ++ ++ lookupByDBusName(dbusName) { ++ return this._entriesByDBusName.hasOwnProperty(dbusName) ? this._entriesByDBusName[dbusName] : null; ++ } ++ ++ lookupById(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(entry) { ++ let existingEntry = this.lookupByDBusName(entry.dbusName()); ++ if (existingEntry) { ++ existingEntry.update(entry); ++ } else { ++ this._entriesByDBusName[entry.dbusName()] = entry; ++ this.emit('entry-added', entry); ++ } ++ } ++ ++ removeEntry(entry) { ++ delete this._entriesByDBusName[entry.dbusName()] ++ this.emit('entry-removed', entry); ++ } ++ ++ _acquireUnityDBus() { ++ if (!this._unity_bus_id) { ++ Gio.DBus.session.own_name('com.canonical.Unity', ++ Gio.BusNameOwnerFlags.ALLOW_REPLACEMENT, null, null); ++ } ++ } ++ ++ _releaseUnityDBus() { ++ if (this._unity_bus_id) { ++ Gio.DBus.session.unown_name(this._unity_bus_id); ++ this._unity_bus_id = 0; ++ } ++ } ++ ++ _onEntrySignalReceived(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(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(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 = class DashToDock_LauncherEntryRemote { ++ ++ constructor(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() { ++ return this._appId; ++ } ++ ++ dbusName() { ++ return this._dbusName; ++ } ++ ++ count() { ++ return this._count; ++ } ++ ++ setCount(count) { ++ if (this._count != count) { ++ this._count = count; ++ this.emit('count-changed', this._count); ++ } ++ } ++ ++ countVisible() { ++ return this._countVisible; ++ } ++ ++ setCountVisible(countVisible) { ++ if (this._countVisible != countVisible) { ++ this._countVisible = countVisible; ++ this.emit('count-visible-changed', this._countVisible); ++ } ++ } ++ ++ progress() { ++ return this._progress; ++ } ++ ++ setProgress(progress) { ++ if (this._progress != progress) { ++ this._progress = progress; ++ this.emit('progress-changed', this._progress); ++ } ++ } ++ ++ progressVisible() { ++ return this._progressVisible; ++ } ++ ++ setProgressVisible(progressVisible) { ++ if (this._progressVisible != progressVisible) { ++ this._progressVisible = progressVisible; ++ this.emit('progress-visible-changed', this._progressVisible); ++ } ++ } ++ ++ setDBusName(dbusName) { ++ if (this._dbusName != dbusName) { ++ let oldName = this._dbusName; ++ this._dbusName = dbusName; ++ this.emit('dbus-name-changed', oldName); ++ } ++ } ++ ++ update(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/highlight_stacked_bg.svg b/extensions/dash-to-dock/media/highlight_stacked_bg.svg +new file mode 100644 +index 0000000..19be5a9 +--- /dev/null ++++ b/extensions/dash-to-dock/media/highlight_stacked_bg.svg +@@ -0,0 +1,82 @@ ++ ++ ++ ++ ++ ++ image/svg+xml ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ +diff --git a/extensions/dash-to-dock/media/highlight_stacked_bg_h.svg b/extensions/dash-to-dock/media/highlight_stacked_bg_h.svg +new file mode 100644 +index 0000000..eeaa869 +--- /dev/null ++++ b/extensions/dash-to-dock/media/highlight_stacked_bg_h.svg +@@ -0,0 +1,82 @@ ++ ++ ++ ++ ++ ++ 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..290374f +--- /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', ++ 'dash.js', ++ 'docking.js', ++ 'extension.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..641a935 +--- /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": 66, ++"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..9cf371b +--- /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. 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 the dynamic mode, 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 shift+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..a72b139 +--- /dev/null ++++ b/extensions/dash-to-dock/prefs.js +@@ -0,0 +1,861 @@ ++// -*- 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 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 SCALE_UPDATE_TIMEOUT = 500; ++const DEFAULT_ICONS_SIZES = [ 128, 96, 64, 48, 32, 24, 16 ]; ++ ++const TransparencyMode = { ++ DEFAULT: 0, ++ FIXED: 1, ++ 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', []); ++ } ++} ++ ++var Settings = class DashToDock_Settings { ++ ++ constructor() { ++ this._settings = ExtensionUtils.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', () => { ++ 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(this._connector.bind(this)); ++ } ++ ++ /** ++ * Connect signals ++ */ ++ _connector(builder, object, signal, handler) { ++ /**init ++ * Object containing all signals defined in the glade file ++ */ ++ const SignalHandler = { ++ dock_display_combo_changed_cb(combo) { ++ this._settings.set_int('preferred-monitor', this._monitors[combo.get_active()]); ++ }, ++ ++ position_top_button_toggled_cb(button) { ++ if (button.get_active()) ++ this._settings.set_enum('dock-position', 0); ++ }, ++ ++ position_right_button_toggled_cb(button) { ++ if (button.get_active()) ++ this._settings.set_enum('dock-position', 1); ++ }, ++ ++ position_bottom_button_toggled_cb(button) { ++ if (button.get_active()) ++ this._settings.set_enum('dock-position', 2); ++ }, ++ ++ position_left_button_toggled_cb(button) { ++ if (button.get_active()) ++ this._settings.set_enum('dock-position', 3); ++ }, ++ ++ icon_size_combo_changed_cb(combo) { ++ this._settings.set_int('dash-max-icon-size', this._allIconSizes[combo.get_active()]); ++ }, ++ ++ dock_size_scale_format_value_cb(scale, value) { ++ return Math.round(value * 100) + ' %'; ++ }, ++ ++ dock_size_scale_value_changed_cb(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, () => { ++ this._settings.set_double('height-fraction', scale.get_value()); ++ this._dock_size_timeout = 0; ++ return GLib.SOURCE_REMOVE; ++ }); ++ }, ++ ++ icon_size_scale_format_value_cb(scale, value) { ++ return value + ' px'; ++ }, ++ ++ icon_size_scale_value_changed_cb(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, () => { ++ 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(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, () => { ++ this._settings.set_double('background-opacity', scale.get_value()); ++ this._opacity_timeout = 0; ++ return GLib.SOURCE_REMOVE; ++ }); ++ }, ++ ++ min_opacity_scale_value_changed_cb(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, () => { ++ this._settings.set_double('min-alpha', scale.get_value()); ++ this._opacity_timeout = 0; ++ return GLib.SOURCE_REMOVE; ++ }); ++ }, ++ ++ max_opacity_scale_value_changed_cb(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, () => { ++ this._settings.set_double('max-alpha', scale.get_value()); ++ this._opacity_timeout = 0; ++ return GLib.SOURCE_REMOVE; ++ }); ++ }, ++ ++ custom_opacity_scale_format_value_cb(scale, value) { ++ return Math.round(value * 100) + ' %'; ++ }, ++ ++ min_opacity_scale_format_value_cb(scale, value) { ++ return Math.round(value * 100) + ' %'; ++ }, ++ ++ max_opacity_scale_format_value_cb(scale, value) { ++ return Math.round(value * 100) + ' %'; ++ }, ++ ++ all_windows_radio_button_toggled_cb(button) { ++ if (button.get_active()) ++ this._settings.set_enum('intellihide-mode', 0); ++ }, ++ ++ focus_application_windows_radio_button_toggled_cb(button) { ++ if (button.get_active()) ++ this._settings.set_enum('intellihide-mode', 1); ++ }, ++ ++ maximized_windows_radio_button_toggled_cb(button) { ++ if (button.get_active()) ++ this._settings.set_enum('intellihide-mode', 2); ++ } ++ } ++ ++ object.connect(signal, SignalHandler[handler].bind(this)); ++ } ++ ++ _bindSettings() { ++ // 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', () => { ++ ++ 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', (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', (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', (widget) => { ++ this._settings.set_enum('scroll-action', widget.get_active()); ++ }); ++ ++ this._builder.get_object('shift_click_action_combo').connect('changed', (widget) => { ++ this._settings.set_enum('shift-click-action', widget.get_active()); ++ }); ++ ++ this._builder.get_object('middle_click_action_combo').connect('changed', (widget) => { ++ this._settings.set_enum('middle-click-action', widget.get_active()); ++ }); ++ this._builder.get_object('shift_middle_click_action_combo').connect('changed', (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', () => { ++ ++ 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', () => {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', (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', () => { ++ ++ 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', (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', ++ (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', () => { ++ 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', () => { ++ ++ 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', (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', (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', (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', (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', ++ (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', () => { ++ 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.DYNAMIC) { ++ this._builder.get_object('dynamic_opacity_button').set_sensitive(false); ++ } ++ ++ this._settings.connect('changed::transparency-mode', () => { ++ if (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', () => { ++ ++ 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', (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()); ++ } ++}; ++ ++function init() { ++ ExtensionUtils.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..26cc960 +--- /dev/null ++++ b/extensions/dash-to-dock/stylesheet.css +@@ -0,0 +1,175 @@ ++/* 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.2); ++} ++ ++#dashtodockContainer.dashtodock #dash { ++ background: #2e3436; ++} ++ ++/* ++ * This is applied to a dummy actor. Only the alpha value for the background and border color ++ * and the transition-duration are used ++ */ ++#dashtodockContainer.dummy-opaque { ++ background-color: rgba(0, 0, 0, 0.8); ++ border-color: rgba(0, 0, 0, 0.4); ++ transition-duration: 300ms; ++} ++ ++/* ++ * This is applied to a dummy actor. Only the alpha value for the background and border color ++ * and the transition-duration are used ++ */ ++#dashtodockContainer.dummy-transparent { ++ background-color: rgba(0, 0, 0, 0.2); ++ border-color: rgba(0, 0, 0, 0.1); ++ transition-duration: 500ms; ++} ++ ++#dashtodockContainer.opaque { ++} ++ ++#dashtodockContainer.transparent { ++} ++ ++#dashtodockContainer .number-overlay { ++ color: rgba(255,255,255,1); ++ background-color: rgba(0,0,0,0.8); ++ text-align: center; ++} ++ ++#dashtodockContainer .notification-badge { ++ color: rgba(255,255,255,1); ++ background-color: rgba(255,0,0,1.0); ++ padding: 0.2em 0.5em; ++ border-radius: 1em; ++ font-weight: bold; ++ text-align: center; ++ margin: 2px; ++} ++ ++#dashtodockPreviewSeparator.popup-separator-menu-item-horizontal { ++ width: 1px; ++ height: auto; ++ border-right-width: 1px; ++ margin: 32px 0px; ++} ++ ++.dashtodock-app-well-preview-menu-item { ++ padding: 1em 1em 0.5em 1em; ++} ++ ++#dashtodockContainer .metro .overview-icon{ ++ border-radius: 0px; ++} ++ ++#dashtodockContainer.bottom .metro.running2.focused, ++#dashtodockContainer.bottom .metro.running3.focused, ++#dashtodockContainer.bottom .metro.running4.focused, ++#dashtodockContainer.top .metro.running2.focused, ++#dashtodockContainer.top .metro.running3.focused, ++#dashtodockContainer.top .metro.running4.focused { ++ background-image: url('./media/highlight_stacked_bg.svg'); ++ background-position: 0px 0px; ++ background-size: contain; ++} ++ ++#dashtodockContainer.left .metro.running2.focused, ++#dashtodockContainer.left .metro.running3.focused, ++#dashtodockContainer.left .metro.running4.focused, ++#dashtodockContainer.right .metro.running2.focused, ++#dashtodockContainer.right .metro.running3.focused, ++#dashtodockContainer.right .metro.running4.focused { ++ background-image: url('./media/highlight_stacked_bg_h.svg'); ++ background-position: 0px 0px; ++ background-size: contain; ++} +\ No newline at end of file +diff --git a/extensions/dash-to-dock/theming.js b/extensions/dash-to-dock/theming.js +new file mode 100644 +index 0000000..596574d +--- /dev/null ++++ b/extensions/dash-to-dock/theming.js +@@ -0,0 +1,569 @@ ++// -*- 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 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 ++ * DYNAMIC: apply 'transparent' style when no windows are close to the dock ++ * */ ++const TransparencyMode = { ++ DEFAULT: 0, ++ FIXED: 1, ++ DYNAMIC: 3 ++}; ++ ++/** ++ * Manage theme customization and custom theme support ++ */ ++var ThemeManager = class DashToDock_ThemeManager { ++ ++ constructor(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', ++ this.updateCustomTheme.bind(this) ++ ], [ ++ // update :overview pseudoclass ++ Main.overview, ++ 'showing', ++ this._onOverviewShowing.bind(this) ++ ], [ ++ Main.overview, ++ 'hiding', ++ this._onOverviewHiding.bind(this) ++ ]); ++ ++ this._updateCustomStyleClasses(); ++ ++ // destroy themeManager when the managed actor is destroyed (e.g. extension unload) ++ // in order to disconnect signals ++ this._actor.connect('destroy', this.destroy.bind(this)); ++ ++ } ++ ++ destroy() { ++ this._signalsHandler.destroy(); ++ this._transparency.destroy(); ++ } ++ ++ _onOverviewShowing() { ++ this._actor.add_style_pseudo_class('overview'); ++ } ++ ++ _onOverviewHiding() { ++ this._actor.remove_style_pseudo_class('overview'); ++ } ++ ++ _updateDashOpacity() { ++ 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() { ++ // 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() { ++ // 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 '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() { ++ 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() { ++ this._updateCustomStyleClasses(); ++ this._updateDashOpacity(); ++ this._updateDashColor(); ++ this._adjustTheme(); ++ this._dash._redisplay(); ++ } ++ ++ /** ++ * Reimported back and adapted from atomdock ++ */ ++ _adjustTheme() { ++ // 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() { ++ 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, ++ this.updateCustomTheme.bind(this) ++ ]); ++ }, 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 ++ */ ++var Transparency = class DashToDock_Transparency { ++ ++ constructor(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); ++ ++ // All these properties are replaced with the ones in the .dummy-opaque and .dummy-transparent css classes ++ 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._base_actor_style = ""; ++ ++ this._signalsHandler = new Utils.GlobalSignalsHandler(); ++ this._injectionsHandler = new Utils.InjectionsHandler(); ++ this._trackedWindows = new Map(); ++ } ++ ++ enable() { ++ // ensure I never double-register/inject ++ // although it should never happen ++ this.disable(); ++ ++ this._base_actor_style = this._actor.get_style(); ++ if (this._base_actor_style == null) { ++ this._base_actor_style = ""; ++ } ++ ++ this._signalsHandler.addWithLabel('transparency', [ ++ global.window_group, ++ 'actor-added', ++ this._onWindowActorAdded.bind(this) ++ ], [ ++ global.window_group, ++ 'actor-removed', ++ this._onWindowActorRemoved.bind(this) ++ ], [ ++ global.window_manager, ++ 'switch-workspace', ++ this._updateSolidStyle.bind(this) ++ ], [ ++ Main.overview, ++ 'hiding', ++ this._updateSolidStyle.bind(this) ++ ], [ ++ Main.overview, ++ 'showing', ++ this._updateSolidStyle.bind(this) ++ ]); ++ ++ // Window signals ++ global.window_group.get_children().filter(function(child) { ++ // An irrelevant window actor ('Gnome-shell') produces an error when the signals are ++ // disconnected, therefore do not add signals to it. ++ return child instanceof Meta.WindowActor && ++ child.get_meta_window().get_wm_class() !== 'Gnome-shell'; ++ }).forEach(function(win) { ++ this._onWindowActorAdded(null, win); ++ }, this); ++ ++ if (this._actor.get_stage()) ++ this._updateSolidStyle(); ++ ++ this._updateStyles(); ++ this._updateSolidStyle(); ++ ++ this.emit('transparency-enabled'); ++ } ++ ++ disable() { ++ // 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() { ++ this.disable(); ++ this._signalsHandler.destroy(); ++ this._injectionsHandler.destroy(); ++ } ++ ++ _onWindowActorAdded(container, metaWindowActor) { ++ let signalIds = []; ++ ['allocation-changed', 'notify::visible'].forEach(s => { ++ signalIds.push(metaWindowActor.connect(s, this._updateSolidStyle.bind(this))); ++ }); ++ this._trackedWindows.set(metaWindowActor, signalIds); ++ } ++ ++ _onWindowActorRemoved(container, metaWindowActor) { ++ if (!this._trackedWindows.get(metaWindowActor)) ++ return; ++ ++ this._trackedWindows.get(metaWindowActor).forEach(id => { ++ metaWindowActor.disconnect(id); ++ }); ++ this._trackedWindows.delete(metaWindowActor); ++ this._updateSolidStyle(); ++ } ++ ++ _updateSolidStyle() { ++ let isNear = this._dockIsNear(); ++ if (isNear) { ++ this._actor.set_style(this._opaque_style); ++ this._dockActor.remove_style_class_name('transparent'); ++ this._dockActor.add_style_class_name('opaque'); ++ } ++ else { ++ this._actor.set_style(this._transparent_style); ++ this._dockActor.remove_style_class_name('opaque'); ++ this._dockActor.add_style_class_name('transparent'); ++ } ++ ++ this.emit('solid-style-updated', isNear); ++ } ++ ++ _dockIsNear() { ++ 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.workspace_manager.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((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; ++ } ++ ++ _updateStyles() { ++ this._getAlphas(); ++ ++ this._transparent_style = this._base_actor_style + ++ 'background-color: rgba(' + ++ this._backgroundColor + ', ' + this._transparentAlpha + ');' + ++ 'border-color: rgba(' + ++ this._backgroundColor + ', ' + this._transparentAlphaBorder + ');' + ++ 'transition-duration: ' + this._transparentTransition + 'ms;'; ++ ++ this._opaque_style = this._base_actor_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(color) { ++ this._backgroundColor = color.red + ',' + color.green + ',' + color.blue; ++ this._updateStyles(); ++ } ++ ++ _getAlphas() { ++ // 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('dummy-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('dummy-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; ++ } ++ } ++}; ++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..d315bd9 +--- /dev/null ++++ b/extensions/dash-to-dock/utils.js +@@ -0,0 +1,258 @@ ++const Clutter = imports.gi.Clutter; ++const Meta = imports.gi.Meta; ++const St = imports.gi.St; ++ ++/** ++ * Simplify global signals and function injections handling ++ * abstract class ++ */ ++const BasicHandler = class DashToDock_BasicHandler { ++ ++ constructor() { ++ this._storage = new Object(); ++ } ++ ++ add(/* 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() { ++ for( let label in this._storage ) ++ this.removeWithLabel(label); ++ } ++ ++ addWithLabel(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(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(item) { ++ throw new Error('no implementation of _create in ' + this); ++ } ++ ++ /** ++ * Correctly delete single element ++ */ ++ _remove(item) { ++ throw new Error('no implementation of _remove in ' + this); ++ } ++}; ++ ++/** ++ * Manage global signals ++ */ ++var GlobalSignalsHandler = class DashToDock_GlobalSignalHandler extends BasicHandler { ++ ++ _create(item) { ++ let object = item[0]; ++ let event = item[1]; ++ let callback = item[2] ++ let id = object.connect(event, callback); ++ ++ return [object, id]; ++ } ++ ++ _remove(item) { ++ item[0].disconnect(item[1]); ++ } ++}; ++ ++/** ++ * Color manipulation utilities ++ */ ++var ColorUtils = class DashToDock_ColorUtils { ++ ++ // Darken or brigthen color by a fraction dlum ++ // Each rgb value is modified by the same fraction. ++ // Return "#rrggbb" string ++ static ColorLuminance(r, g, b, dlum) { ++ let rgbString = '#'; ++ ++ rgbString += ColorUtils._decimalToHex(Math.round(Math.min(Math.max(r*(1+dlum), 0), 255)), 2); ++ rgbString += ColorUtils._decimalToHex(Math.round(Math.min(Math.max(g*(1+dlum), 0), 255)), 2); ++ rgbString += ColorUtils._decimalToHex(Math.round(Math.min(Math.max(b*(1+dlum), 0), 255)), 2); ++ ++ return rgbString; ++ } ++ ++ // Convert decimal to an hexadecimal string adding the desired padding ++ static _decimalToHex(d, padding) { ++ let hex = d.toString(16); ++ while (hex.length < padding) ++ hex = '0'+ hex; ++ return hex; ++ } ++ ++ // 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. ++ static HSVtoRGB(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. ++ static RGBtoHSV(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 = class DashToDock_InjectionsHandler extends BasicHandler { ++ ++ _create(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(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..ea98f27 +--- /dev/null ++++ b/extensions/dash-to-dock/windowPreview.js +@@ -0,0 +1,578 @@ ++/* ++ * 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 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; ++ ++var WindowPreviewMenu = class DashToDock_WindowPreviewMenu extends PopupMenu.PopupMenu { ++ ++ constructor(source, settings) { ++ let side = Utils.getPosition(settings); ++ super(source.actor, 0.5, side); ++ ++ this._dtdSettings = settings; ++ ++ // 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', () => { ++ if (!this._source.actor.mapped) ++ this.close(); ++ }); ++ this._destroyId = this._source.actor.connect('destroy', this.destroy.bind(this)); ++ ++ Main.uiGroup.add_actor(this.actor); ++ ++ // Change the initialized side where required. ++ this._arrowSide = side; ++ this._boxPointer._arrowSide = side; ++ this._boxPointer._userArrowSide = side; ++ ++ this.connect('destroy', this._onDestroy.bind(this)); ++ } ++ ++ _redisplay() { ++ if (this._previewBox) ++ this._previewBox.destroy(); ++ this._previewBox = new WindowPreviewList(this._source, this._dtdSettings); ++ this.addMenuItem(this._previewBox); ++ this._previewBox._redisplay(); ++ } ++ ++ popup() { ++ 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'); ++ } ++ } ++ ++ _onDestroy() { ++ if (this._mappedId) ++ this._source.actor.disconnect(this._mappedId); ++ ++ if (this._destroyId) ++ this._source.actor.disconnect(this._destroyId); ++ } ++}; ++ ++var WindowPreviewList = class DashToDock_WindowPreviewList extends PopupMenu.PopupMenuSection { ++ ++ constructor(source, settings) { ++ super(); ++ this._dtdSettings = settings; ++ ++ 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', this._onScrollEvent.bind(this)); ++ ++ 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, this._redisplay.bind(this)); ++ ++ this.actor.connect('destroy', this._onDestroy.bind(this)); ++ this._stateChangedId = this.app.connect('windows-changed', ++ this._queueRedisplay.bind(this)); ++ } ++ ++ _queueRedisplay () { ++ Main.queueDeferredWork(this._redisplayId); ++ } ++ ++ _onScrollEvent(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() { ++ this.app.disconnect(this._stateChangedId); ++ this._stateChangedId = 0; ++ } ++ ++ _createPreviewItem(window) { ++ let preview = new WindowPreviewMenuItem(window); ++ return preview; ++ } ++ ++ _redisplay () { ++ 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 with a static order ++ let newWin = this._source.getInterestingWindows().sort(function(a, b) { ++ return a.get_stable_sequence() > b.get_stable_sequence(); ++ }); ++ ++ 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(); ++ } ++ ++ // 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() { ++ 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() { ++ return this.actor.get_children().reduce(function(result, actor) { ++ return result || actor.animatingOut; ++ }, false); ++ } ++}; ++ ++var WindowPreviewMenuItem = class DashToDock_WindowPreviewMenuItem extends PopupMenu.PopupBaseMenuItem { ++ ++ constructor(window, params) { ++ super(params); ++ ++ this._window = window; ++ this._destroyId = 0; ++ this._windowAddedId = 0; ++ ++ // 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.add_actor(new St.Icon({ icon_name: 'window-close-symbolic' })); ++ this.closeButton.set_x_align(Clutter.ActorAlign.END); ++ this.closeButton.set_y_align(Clutter.ActorAlign.START); ++ ++ ++ this.closeButton.opacity = 0; ++ this.closeButton.connect('clicked', this._closeWindow.bind(this)); ++ ++ 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', () => { ++ 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', ++ this._onEnter.bind(this)); ++ this.actor.connect('leave-event', ++ this._onLeave.bind(this)); ++ this.actor.connect('key-focus-in', ++ this._onEnter.bind(this)); ++ this.actor.connect('key-focus-out', ++ this._onLeave.bind(this)); ++ ++ this._cloneTexture(window); ++ ++ } ++ ++ _cloneTexture(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(() => { ++ // 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', () => { ++ 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() { ++ return this._window.can_close() && ++ !this._hasAttachedDialogs(); ++ } ++ ++ _closeWindow(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', ++ this._onWindowAdded.bind(this)); ++ ++ this.deleteAllWindows(); ++ } ++ ++ deleteAllWindows() { ++ // 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(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(() => { ++ this.emit('activate'); ++ return GLib.SOURCE_REMOVE; ++ }); ++ GLib.Source.set_name_by_id(id, '[dash-to-dock] this.emit'); ++ } ++ } ++ ++ _hasAttachedDialogs() { ++ // count trasient windows ++ let n=0; ++ this._window.foreach_transient(function(){n++;}); ++ return n>0; ++ } ++ ++ _onEnter() { ++ this._showCloseButton(); ++ return Clutter.EVENT_PROPAGATE; ++ } ++ ++ _onLeave() { ++ if (!this._cloneBin.has_pointer && ++ !this.closeButton.has_pointer) ++ this._hideCloseButton(); ++ ++ return Clutter.EVENT_PROPAGATE; ++ } ++ ++ _idleToggleCloseButton() { ++ this._idleToggleCloseId = 0; ++ ++ if (!this._cloneBin.has_pointer && ++ !this.closeButton.has_pointer) ++ this._hideCloseButton(); ++ ++ return GLib.SOURCE_REMOVE; ++ } ++ ++ _showCloseButton() { ++ ++ if (this._windowCanClose()) { ++ this.closeButton.show(); ++ Tweener.addTween(this.closeButton, ++ { opacity: 255, ++ time: Workspace.CLOSE_BUTTON_FADE_TIME, ++ transition: 'easeOutQuad' }); ++ } ++ } ++ ++ _hideCloseButton() { ++ Tweener.addTween(this.closeButton, ++ { opacity: 0, ++ time: Workspace.CLOSE_BUTTON_FADE_TIME, ++ transition: 'easeInQuad' }); ++ } ++ ++ show(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() { ++ 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() { ++ this.actor.destroy(); ++ } ++ }); ++ } ++ ++ activate() { ++ this._getTopMenu().close(); ++ Main.activateWindow(this._window); ++ } ++ ++ _onDestroy() { ++ super._onDestroy(); ++ ++ 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 6050c32..2909135 100644 +--- a/meson.build ++++ b/meson.build +@@ -49,6 +49,7 @@ default_extensions += [ + all_extensions = default_extensions + all_extensions += [ + 'auto-move-windows', ++ 'dash-to-dock', + 'native-window-placement', + 'top-icons', + 'user-theme' +-- +2.21.0 + + +From 0baadb623f45f5dfa9449e3bd0aa1ee3880852c3 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/8] 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 2909135..e8e00dc 100644 +--- a/meson.build ++++ b/meson.build +@@ -51,6 +51,7 @@ all_extensions += [ + 'auto-move-windows', + 'dash-to-dock', + 'native-window-placement', ++ 'panel-favorites', + 'top-icons', + 'user-theme' + ] +-- +2.21.0 + + +From ce4282836905117970b842a5504a92d8b6ea7dfe 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/8] Add updates-dialog extension + +--- + extensions/updates-dialog/extension.js | 503 ++++++++++++++++++ + 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, 554 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..59f6dcf +--- /dev/null ++++ b/extensions/updates-dialog/extension.js +@@ -0,0 +1,503 @@ ++/* ++ * 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 . ++ */ ++ ++/* exported enable disable */ ++ ++const { Clutter, Gio, GLib, PackageKitGlib: PkgKit, Pango, Polkit, St } = imports.gi; ++const Signals = imports.signals; ++ ++const EndSessionDialog = imports.ui.endSessionDialog; ++const ModalDialog = imports.ui.modalDialog; ++ ++const ExtensionUtils = imports.misc.extensionUtils; ++const Me = ExtensionUtils.getCurrentExtension(); ++ ++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 = class extends ModalDialog.ModalDialog { ++ constructor(settings) { ++ super({ ++ 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: this.close.bind(this), ++ label: _('Close'), ++ key: Clutter.Escape ++ }, { ++ action: this._done.bind(this), ++ label: _('Restart & Install') ++ }]; ++ ++ this.setButtons(buttons); ++ ++ this._openTimeoutId = 0; ++ this.connect('destroy', this._clearOpenTimeout.bind(this)); ++ ++ this._startTimer(); ++ } ++ ++ _clearOpenTimeout() { ++ if (this._openTimeoutId > 0) { ++ GLib.source_remove(this._openTimeoutId); ++ this._openTimeoutId = 0; ++ } ++ } ++ ++ tryOpen() { ++ if (this._openTimeoutId > 0 || this.open()) ++ return; ++ ++ this._openTimeoutId = GLib.timeout_add_seconds( ++ GLib.PRIORITY_DEFAULT, 1, () => { ++ if (!this.open()) ++ return GLib.SOURCE_CONTINUE; ++ ++ this._clearOpenTimeout(); ++ return GLib.SOURCE_REMOVE; ++ }); ++ } ++ ++ _startTimer() { ++ this._secondsLeft = this._gracePeriod * 60; ++ ++ this._timerId = GLib.timeout_add_seconds( ++ GLib.PRIORITY_DEFAULT, 1, () => { ++ 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', () => { ++ if (this._timerId > 0) { ++ GLib.source_remove(this._timerId); ++ this._timerId = 0; ++ } ++ }); ++ } ++ ++ _done() { ++ this.emit('done'); ++ this.destroy(); ++ } ++ ++ getState() { ++ return [this._gracePeriod, this._lastWarningPeriod, this._lastWarnings, this._secondsLeft]; ++ } ++ ++ setState(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', () => updatesDialog = null); ++ updatesDialog.connect('done', () => { ++ if (pkOfflineProxy.TriggerAction == 'power-off' || ++ pkOfflineProxy.TriggerAction == 'reboot') { ++ loginManagerProxy.RebootRemote(false); ++ } else { ++ pkOfflineProxy.TriggerRemote('reboot', (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((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), ++ (proxy, error) => { ++ if (!error) { ++ proxy.SetHintsRemote( ++ ['background=true', 'interactive=false'], ++ (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', (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', (p, e, params) => { ++ let [info, packageId, summary_] = params; ++ ++ if (info == PkgKit.InfoEnum.SECURITY) ++ securityUpdates.push(packageId); ++ }); ++ proxy.connectSignal('Finished', (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', ++ (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', ++ (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', ++ (proxy, error) => { ++ if (!error) { ++ proxy.CanRebootRemote(cancellable, (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 enable() { ++ cancellable = new Gio.Cancellable(); ++ extensionSettings = ExtensionUtils.getSettings(); ++ Polkit.Permission.new('org.freedesktop.packagekit.trigger-offline-update', ++ null, ++ cancellable, ++ (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 e8e00dc..d129e6c 100644 +--- a/meson.build ++++ b/meson.build +@@ -53,6 +53,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 9c1438a..55f0e9a 100644 +--- a/po/POTFILES.in ++++ b/po/POTFILES.in +@@ -10,6 +10,8 @@ extensions/native-window-placement/org.gnome.shell.extensions.native-window-plac + extensions/places-menu/extension.js + extensions/places-menu/placeDisplay.js + extensions/screenshot-window-sizer/org.gnome.shell.extensions.screenshot-window-sizer.gschema.xml ++extensions/updates-dialog/extension.js ++extensions/updates-dialog/org.gnome.shell.extensions.updates-dialog.gschema.xml + extensions/user-theme/extension.js + extensions/user-theme/org.gnome.shell.extensions.user-theme.gschema.xml + extensions/window-list/extension.js +-- +2.21.0 + + +From 75c1b568c0bc93c1218fdd1d9d1caa4b9c1d723e 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/8] 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 d129e6c..6f27f46 100644 +--- a/meson.build ++++ b/meson.build +@@ -51,6 +51,7 @@ all_extensions += [ + 'auto-move-windows', + 'dash-to-dock', + 'native-window-placement', ++ 'no-hot-corner', + 'panel-favorites', + 'top-icons', + 'updates-dialog', +-- +2.21.0 + + +From c165b79227f0702ecd5cb9f6b25ba5cede47a334 Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Florian=20M=C3=BCllner?= +Date: Tue, 26 Mar 2019 19:44:43 +0100 +Subject: [PATCH 6/8] Add window-grouper extension + +--- + extensions/window-grouper/extension.js | 109 ++++++++++ + extensions/window-grouper/meson.build | 8 + + extensions/window-grouper/metadata.json.in | 11 + + ...hell.extensions.window-grouper.gschema.xml | 9 + + extensions/window-grouper/prefs.js | 191 ++++++++++++++++++ + extensions/window-grouper/stylesheet.css | 1 + + meson.build | 3 +- + 7 files changed, 331 insertions(+), 1 deletion(-) + create mode 100644 extensions/window-grouper/extension.js + create mode 100644 extensions/window-grouper/meson.build + create mode 100644 extensions/window-grouper/metadata.json.in + create mode 100644 extensions/window-grouper/org.gnome.shell.extensions.window-grouper.gschema.xml + create mode 100644 extensions/window-grouper/prefs.js + create mode 100644 extensions/window-grouper/stylesheet.css + +diff --git a/extensions/window-grouper/extension.js b/extensions/window-grouper/extension.js +new file mode 100644 +index 0000000..f66a764 +--- /dev/null ++++ b/extensions/window-grouper/extension.js +@@ -0,0 +1,109 @@ ++// -*- mode: js2; indent-tabs-mode: nil; js2-basic-offset: 4 -*- ++/* exported init */ ++ ++const { Shell } = imports.gi; ++ ++const ExtensionUtils = imports.misc.extensionUtils; ++ ++class WindowMover { ++ constructor() { ++ this._settings = ExtensionUtils.getSettings(); ++ this._appSystem = Shell.AppSystem.get_default(); ++ this._appConfigs = new Set(); ++ this._appData = new Map(); ++ ++ this._appsChangedId = this._appSystem.connect( ++ 'installed-changed', this._updateAppData.bind(this)); ++ ++ this._settings.connect('changed', this._updateAppConfigs.bind(this)); ++ this._updateAppConfigs(); ++ } ++ ++ _updateAppConfigs() { ++ this._appConfigs.clear(); ++ ++ this._settings.get_strv('application-list').forEach(appId => { ++ this._appConfigs.add(appId); ++ }); ++ ++ this._updateAppData(); ++ } ++ ++ _updateAppData() { ++ let ids = [...this._appConfigs.values()]; ++ let removedApps = [...this._appData.keys()].filter( ++ a => !ids.includes(a.id) ++ ); ++ removedApps.forEach(app => { ++ app.disconnect(this._appData.get(app).windowsChangedId); ++ this._appData.delete(app); ++ }); ++ ++ let addedApps = ids.map(id => this._appSystem.lookup_app(id)).filter( ++ app => app != null && !this._appData.has(app) ++ ); ++ addedApps.forEach(app => { ++ let data = { ++ windows: app.get_windows(), ++ windowsChangedId: app.connect( ++ 'windows-changed', this._appWindowsChanged.bind(this)) ++ }; ++ this._appData.set(app, data); ++ }); ++ } ++ ++ destroy() { ++ if (this._appsChangedId) { ++ this._appSystem.disconnect(this._appsChangedId); ++ this._appsChangedId = 0; ++ } ++ ++ if (this._settings) { ++ this._settings.run_dispose(); ++ this._settings = null; ++ } ++ ++ this._appConfigs.clear(); ++ this._updateAppData(); ++ } ++ ++ _appWindowsChanged(app) { ++ let data = this._appData.get(app); ++ let windows = app.get_windows(); ++ ++ // If get_compositor_private() returns non-NULL on a removed windows, ++ // the window still exists and is just moved to a different workspace ++ // or something; assume it'll be added back immediately, so keep it ++ // to avoid moving it again ++ windows.push(...data.windows.filter( ++ w => !windows.includes(w) && w.get_compositor_private() != null ++ )); ++ ++ windows.filter(w => !data.windows.includes(w)).forEach(window => { ++ let leader = data.windows.find(w => w.get_pid() == window.get_pid()); ++ if (leader) ++ window.change_workspace(leader.get_workspace()); ++ }); ++ data.windows = windows; ++ } ++} ++ ++class Extension { ++ constructor() { ++ this._winMover = null; ++ } ++ ++ enable() { ++ this._winMover = new WindowMover(); ++ } ++ ++ disable() { ++ this._winMover.destroy(); ++ this._winMover = null; ++ } ++} ++ ++function init() { ++ ExtensionUtils.initTranslations(); ++ return new Extension(); ++} +diff --git a/extensions/window-grouper/meson.build b/extensions/window-grouper/meson.build +new file mode 100644 +index 0000000..c55a783 +--- /dev/null ++++ b/extensions/window-grouper/meson.build +@@ -0,0 +1,8 @@ ++extension_data += configure_file( ++ input: metadata_name + '.in', ++ output: metadata_name, ++ configuration: metadata_conf ++) ++ ++extension_sources += files('prefs.js') ++extension_schemas += files(metadata_conf.get('gschemaname') + '.gschema.xml') +diff --git a/extensions/window-grouper/metadata.json.in b/extensions/window-grouper/metadata.json.in +new file mode 100644 +index 0000000..aa202c8 +--- /dev/null ++++ b/extensions/window-grouper/metadata.json.in +@@ -0,0 +1,11 @@ ++{ ++ "extension-id": "@extension_id@", ++ "uuid": "@uuid@", ++ "settings-schema": "@gschemaname@", ++ "gettext-domain": "@gettext_domain@", ++ "name": "Window grouper", ++ "description": "Keep windows that belong to the same process on the same workspace.", ++ "shell-version": [ "@shell_current@" ], ++ "original-authors": [ "fmuellner@redhat.com" ], ++ "url": "@url@" ++} +diff --git a/extensions/window-grouper/org.gnome.shell.extensions.window-grouper.gschema.xml b/extensions/window-grouper/org.gnome.shell.extensions.window-grouper.gschema.xml +new file mode 100644 +index 0000000..ee052a6 +--- /dev/null ++++ b/extensions/window-grouper/org.gnome.shell.extensions.window-grouper.gschema.xml +@@ -0,0 +1,9 @@ ++ ++ ++ ++ [ ] ++ Application that should be grouped ++ A list of application ids ++ ++ ++ +diff --git a/extensions/window-grouper/prefs.js b/extensions/window-grouper/prefs.js +new file mode 100644 +index 0000000..d7b748e +--- /dev/null ++++ b/extensions/window-grouper/prefs.js +@@ -0,0 +1,191 @@ ++// -*- mode: js2; indent-tabs-mode: nil; js2-basic-offset: 4 -*- ++/* exported init buildPrefsWidget */ ++ ++const { Gio, GObject, Gtk } = imports.gi; ++ ++const ExtensionUtils = imports.misc.extensionUtils; ++const Gettext = imports.gettext.domain('gnome-shell-extensions'); ++const _ = Gettext.gettext; ++const N_ = e => e; ++ ++const SETTINGS_KEY = 'application-list'; ++ ++const Columns = { ++ APPINFO: 0, ++ DISPLAY_NAME: 1, ++ ICON: 2 ++}; ++ ++const Widget = GObject.registerClass({ ++ GTypeName: 'WindowGrouperPrefsWidget', ++}, class Widget extends Gtk.Grid { ++ _init(params) { ++ super._init(params); ++ this.set_orientation(Gtk.Orientation.VERTICAL); ++ ++ this._settings = ExtensionUtils.getSettings(); ++ this._settings.connect('changed', this._refresh.bind(this)); ++ this._changedPermitted = false; ++ ++ this._store = new Gtk.ListStore(); ++ this._store.set_column_types([Gio.AppInfo, GObject.TYPE_STRING, Gio.Icon]); ++ ++ let scrolled = new Gtk.ScrolledWindow({ shadow_type: Gtk.ShadowType.IN }); ++ scrolled.set_policy(Gtk.PolicyType.NEVER, Gtk.PolicyType.AUTOMATIC); ++ this.add(scrolled); ++ ++ ++ this._treeView = new Gtk.TreeView({ ++ model: this._store, ++ headers_visible: false, ++ hexpand: true, ++ vexpand: true ++ }); ++ this._treeView.get_selection().set_mode(Gtk.SelectionMode.SINGLE); ++ ++ let appColumn = new Gtk.TreeViewColumn({ ++ sort_column_id: Columns.DISPLAY_NAME, ++ spacing: 12 ++ }); ++ let iconRenderer = new Gtk.CellRendererPixbuf({ ++ stock_size: Gtk.IconSize.DIALOG, ++ xpad: 12, ++ ypad: 12 ++ }); ++ appColumn.pack_start(iconRenderer, false); ++ appColumn.add_attribute(iconRenderer, 'gicon', Columns.ICON); ++ let nameRenderer = new Gtk.CellRendererText(); ++ appColumn.pack_start(nameRenderer, true); ++ appColumn.add_attribute(nameRenderer, 'text', Columns.DISPLAY_NAME); ++ this._treeView.append_column(appColumn); ++ ++ scrolled.add(this._treeView); ++ ++ let toolbar = new Gtk.Toolbar({ icon_size: Gtk.IconSize.SMALL_TOOLBAR }); ++ toolbar.get_style_context().add_class(Gtk.STYLE_CLASS_INLINE_TOOLBAR); ++ this.add(toolbar); ++ ++ let newButton = new Gtk.ToolButton({ ++ icon_name: 'list-add-symbolic' ++ }); ++ newButton.connect('clicked', this._createNew.bind(this)); ++ toolbar.add(newButton); ++ ++ let delButton = new Gtk.ToolButton({ ++ icon_name: 'list-remove-symbolic' ++ }); ++ delButton.connect('clicked', this._deleteSelected.bind(this)); ++ toolbar.add(delButton); ++ ++ let selection = this._treeView.get_selection(); ++ selection.connect('changed', () => { ++ delButton.sensitive = selection.count_selected_rows() > 0; ++ }); ++ delButton.sensitive = selection.count_selected_rows() > 0; ++ ++ this._changedPermitted = true; ++ this._refresh(); ++ } ++ ++ _createNew() { ++ let dialog = new Gtk.AppChooserDialog({ ++ heading: _('Select an application for which grouping should apply'), ++ transient_for: this.get_toplevel(), ++ modal: true ++ }); ++ ++ dialog.get_widget().show_all = true; ++ ++ dialog.connect('response', (dialog, id) => { ++ if (id != Gtk.ResponseType.OK) { ++ dialog.destroy(); ++ return; ++ } ++ ++ let appInfo = dialog.get_app_info(); ++ if (!appInfo) { ++ dialog.destroy(); ++ return; ++ } ++ ++ this._changedPermitted = false; ++ this._appendItem(appInfo.get_id()); ++ this._changedPermitted = true; ++ ++ let iter = this._store.append(); ++ this._store.set(iter, ++ [Columns.APPINFO, Columns.ICON, Columns.DISPLAY_NAME], ++ [appInfo, appInfo.get_icon(), appInfo.get_display_name()]); ++ ++ dialog.destroy(); ++ }); ++ dialog.show_all(); ++ } ++ ++ _deleteSelected() { ++ let [any, model_, iter] = this._treeView.get_selection().get_selected(); ++ ++ if (any) { ++ let appInfo = this._store.get_value(iter, Columns.APPINFO); ++ ++ this._changedPermitted = false; ++ this._removeItem(appInfo.get_id()); ++ this._changedPermitted = true; ++ this._store.remove(iter); ++ } ++ } ++ ++ _refresh() { ++ if (!this._changedPermitted) ++ // Ignore this notification, model is being modified outside ++ return; ++ ++ this._store.clear(); ++ ++ let currentItems = this._settings.get_strv(SETTINGS_KEY); ++ let validItems = []; ++ for (let i = 0; i < currentItems.length; i++) { ++ let id = currentItems[i]; ++ let appInfo = Gio.DesktopAppInfo.new(id); ++ if (!appInfo) ++ continue; ++ validItems.push(currentItems[i]); ++ ++ let iter = this._store.append(); ++ this._store.set(iter, ++ [Columns.APPINFO, Columns.ICON, Columns.DISPLAY_NAME], ++ [appInfo, appInfo.get_icon(), appInfo.get_display_name()]); ++ } ++ ++ if (validItems.length != currentItems.length) // some items were filtered out ++ this._settings.set_strv(SETTINGS_KEY, validItems); ++ } ++ ++ _appendItem(id) { ++ let currentItems = this._settings.get_strv(SETTINGS_KEY); ++ currentItems.push(id); ++ this._settings.set_strv(SETTINGS_KEY, currentItems); ++ } ++ ++ _removeItem(id) { ++ let currentItems = this._settings.get_strv(SETTINGS_KEY); ++ let index = currentItems.indexOf(id); ++ ++ if (index < 0) ++ return; ++ currentItems.splice(index, 1); ++ this._settings.set_strv(SETTINGS_KEY, currentItems); ++ } ++}); ++ ++ ++function init() { ++ ExtensionUtils.initTranslations(); ++} ++ ++function buildPrefsWidget() { ++ let widget = new Widget({ margin: 12 }); ++ widget.show_all(); ++ ++ return widget; ++} +diff --git a/extensions/window-grouper/stylesheet.css b/extensions/window-grouper/stylesheet.css +new file mode 100644 +index 0000000..25134b6 +--- /dev/null ++++ b/extensions/window-grouper/stylesheet.css +@@ -0,0 +1 @@ ++/* This extensions requires no special styling */ +diff --git a/meson.build b/meson.build +index 6f27f46..4b9d138 100644 +--- a/meson.build ++++ b/meson.build +@@ -55,7 +55,8 @@ all_extensions += [ + 'panel-favorites', + 'top-icons', + 'updates-dialog', +- 'user-theme' ++ 'user-theme', ++ 'window-grouper' + ] + + enabled_extensions = get_option('enable_extensions') +-- +2.21.0 + + +From be2ec13b3a876b4c4bcf91ee9e537b89c06e4e3f Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Florian=20M=C3=BCllner?= +Date: Tue, 26 Mar 2019 21:32:09 +0100 +Subject: [PATCH 7/8] Add disable-screenshield extension + +--- + extensions/disable-screenshield/extension.js | 27 +++++++++++++++++++ + extensions/disable-screenshield/meson.build | 5 ++++ + .../disable-screenshield/metadata.json.in | 9 +++++++ + .../disable-screenshield/stylesheet.css | 1 + + meson.build | 1 + + 5 files changed, 43 insertions(+) + create mode 100644 extensions/disable-screenshield/extension.js + create mode 100644 extensions/disable-screenshield/meson.build + create mode 100644 extensions/disable-screenshield/metadata.json.in + create mode 100644 extensions/disable-screenshield/stylesheet.css + +diff --git a/extensions/disable-screenshield/extension.js b/extensions/disable-screenshield/extension.js +new file mode 100644 +index 0000000..91204c0 +--- /dev/null ++++ b/extensions/disable-screenshield/extension.js +@@ -0,0 +1,27 @@ ++/* exported enable disable */ ++ ++const ScreenShield = imports.ui.screenShield; ++ ++let _onUserBecameActiveOrig; ++ ++function _onUserBecameActiveInjected() { ++ this.idleMonitor.remove_watch(this._becameActiveId); ++ this._becameActiveId = 0; ++ ++ this._longLightbox.hide(); ++ this._shortLightbox.hide(); ++ ++ this.deactivate(false); ++} ++ ++function enable() { ++ _onUserBecameActiveOrig = ++ ScreenShield.ScreenShield.prototype._onUserBecameActive; ++ ScreenShield.ScreenShield.prototype._onUserBecameActive = ++ _onUserBecameActiveInjected; ++} ++ ++function disable() { ++ ScreenShield.ScreenShield.prototype._onUserBecameActive = ++ _onUserBecameActiveOrig; ++} +diff --git a/extensions/disable-screenshield/meson.build b/extensions/disable-screenshield/meson.build +new file mode 100644 +index 0000000..48504f6 +--- /dev/null ++++ b/extensions/disable-screenshield/meson.build +@@ -0,0 +1,5 @@ ++extension_data += configure_file( ++ input: metadata_name + '.in', ++ output: metadata_name, ++ configuration: metadata_conf ++) +diff --git a/extensions/disable-screenshield/metadata.json.in b/extensions/disable-screenshield/metadata.json.in +new file mode 100644 +index 0000000..074429f +--- /dev/null ++++ b/extensions/disable-screenshield/metadata.json.in +@@ -0,0 +1,9 @@ ++{ ++ "extension-id": "@extension_id@", ++ "uuid": "@uuid@", ++ "name": "Disable Screen Shield", ++ "description": "Disable screen shield when screen lock is disabled", ++ "shell-version": [ "@shell_current@" ], ++ "original-authors": [ "lgpasquale@gmail.com" ], ++ "url": "@url@" ++} +diff --git a/extensions/disable-screenshield/stylesheet.css b/extensions/disable-screenshield/stylesheet.css +new file mode 100644 +index 0000000..25134b6 +--- /dev/null ++++ b/extensions/disable-screenshield/stylesheet.css +@@ -0,0 +1 @@ ++/* This extensions requires no special styling */ +diff --git a/meson.build b/meson.build +index 4b9d138..cf855a0 100644 +--- a/meson.build ++++ b/meson.build +@@ -50,6 +50,7 @@ all_extensions = default_extensions + all_extensions += [ + 'auto-move-windows', + 'dash-to-dock', ++ 'disable-screenshield', + 'native-window-placement', + 'no-hot-corner', + 'panel-favorites', +-- +2.21.0 + + +From 0b1da0ee4e8669bb3ba5d7fef5cf436c7bbcea88 Mon Sep 17 00:00:00 2001 +From: Carlos Soriano +Date: Mon, 13 Aug 2018 17:28:41 +0200 +Subject: [PATCH 8/8] Add desktop icons extension + +--- + .../desktop-icons/createFolderDialog.js | 164 ++++ + extensions/desktop-icons/createThumbnail.js | 35 + + extensions/desktop-icons/dbusUtils.js | 103 +++ + extensions/desktop-icons/desktopGrid.js | 692 +++++++++++++++ + extensions/desktop-icons/desktopIconsUtil.js | 123 +++ + extensions/desktop-icons/desktopManager.js | 752 ++++++++++++++++ + extensions/desktop-icons/extension.js | 71 ++ + extensions/desktop-icons/fileItem.js | 800 ++++++++++++++++++ + extensions/desktop-icons/meson.build | 19 + + extensions/desktop-icons/metadata.json.in | 11 + + extensions/desktop-icons/po/LINGUAS | 19 + + extensions/desktop-icons/po/POTFILES.in | 4 + + extensions/desktop-icons/po/cs.po | 195 +++++ + extensions/desktop-icons/po/da.po | 159 ++++ + extensions/desktop-icons/po/de.po | 192 +++++ + extensions/desktop-icons/po/es.po | 218 +++++ + extensions/desktop-icons/po/fi.po | 191 +++++ + extensions/desktop-icons/po/fr.po | 164 ++++ + extensions/desktop-icons/po/fur.po | 187 ++++ + extensions/desktop-icons/po/hr.po | 186 ++++ + extensions/desktop-icons/po/hu.po | 190 +++++ + extensions/desktop-icons/po/id.po | 190 +++++ + extensions/desktop-icons/po/it.po | 189 +++++ + extensions/desktop-icons/po/ja.po | 187 ++++ + extensions/desktop-icons/po/meson.build | 1 + + extensions/desktop-icons/po/nl.po | 188 ++++ + extensions/desktop-icons/po/pl.po | 193 +++++ + extensions/desktop-icons/po/pt_BR.po | 199 +++++ + extensions/desktop-icons/po/ru.po | 153 ++++ + extensions/desktop-icons/po/sv.po | 197 +++++ + extensions/desktop-icons/po/tr.po | 191 +++++ + 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 | 38 + + 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 | 188 ++++ + 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 +++- + 49 files changed, 8617 insertions(+), 30 deletions(-) + create mode 100644 extensions/desktop-icons/createFolderDialog.js + 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/fur.po + create mode 100644 extensions/desktop-icons/po/hr.po + create mode 100644 extensions/desktop-icons/po/hu.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/ja.po + create mode 100644 extensions/desktop-icons/po/meson.build + create mode 100644 extensions/desktop-icons/po/nl.po + 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/sv.po + create mode 100644 extensions/desktop-icons/po/tr.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/createFolderDialog.js b/extensions/desktop-icons/createFolderDialog.js +new file mode 100644 +index 0000000..f3e40e9 +--- /dev/null ++++ b/extensions/desktop-icons/createFolderDialog.js +@@ -0,0 +1,164 @@ ++/* Desktop Icons GNOME Shell extension ++ * ++ * Copyright (C) 2019 Andrea Azzaronea ++ * ++ * 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, GObject, GLib, Gio, St } = imports.gi; ++ ++const Signals = imports.signals; ++ ++const Dialog = imports.ui.dialog; ++const Gettext = imports.gettext.domain('desktop-icons'); ++const ModalDialog = imports.ui.modalDialog; ++const ShellEntry = imports.ui.shellEntry; ++const Tweener = imports.ui.tweener; ++ ++const ExtensionUtils = imports.misc.extensionUtils; ++const Me = ExtensionUtils.getCurrentExtension(); ++const DesktopIconsUtil = Me.imports.desktopIconsUtil; ++const Extension = Me.imports.extension; ++ ++const _ = Gettext.gettext; ++ ++const DIALOG_GROW_TIME = 0.1; ++ ++var CreateFolderDialog = class extends ModalDialog.ModalDialog { ++ ++ constructor() { ++ super({ styleClass: 'create-folder-dialog' }); ++ ++ this._buildLayout(); ++ } ++ ++ _buildLayout() { ++ let label = new St.Label({ style_class: 'create-folder-dialog-label', ++ text: _('New folder name') }); ++ this.contentLayout.add(label, { x_align: St.Align.START }); ++ ++ this._entry = new St.Entry({ style_class: 'create-folder-dialog-entry', ++ can_focus: true }); ++ this._entry.clutter_text.connect('activate', this._onEntryActivate.bind(this)); ++ this._entry.clutter_text.connect('text-changed', this._onTextChanged.bind(this)); ++ ShellEntry.addContextMenu(this._entry); ++ this.contentLayout.add(this._entry); ++ this.setInitialKeyFocus(this._entry); ++ ++ this._errorBox = new St.BoxLayout({ style_class: 'create-folder-dialog-error-box', ++ visible: false }); ++ this.contentLayout.add(this._errorBox, { expand: true }); ++ ++ this._errorMessage = new St.Label({ style_class: 'create-folder-dialog-error-label' }); ++ this._errorMessage.clutter_text.line_wrap = true; ++ this._errorBox.add(this._errorMessage, { expand: true, ++ x_align: St.Align.START, ++ x_fill: false, ++ y_align: St.Align.MIDDLE, ++ y_fill: false }); ++ ++ this._createButton = this.addButton({ action: this._onCreateButton.bind(this), ++ label: _('Create') }); ++ this.addButton({ action: this.close.bind(this), ++ label: _('Cancel'), ++ key: Clutter.Escape }); ++ this._onTextChanged(); ++ } ++ ++ _showError(message) { ++ this._errorMessage.set_text(message); ++ ++ if (!this._errorBox.visible) { ++ let [errorBoxMinHeight, errorBoxNaturalHeight] = this._errorBox.get_preferred_height(-1); ++ let parentActor = this._errorBox.get_parent(); ++ ++ Tweener.addTween(parentActor, ++ { height: parentActor.height + errorBoxNaturalHeight, ++ time: DIALOG_GROW_TIME, ++ transition: 'easeOutQuad', ++ onComplete: () => { ++ parentActor.set_height(-1); ++ this._errorBox.show(); ++ } ++ }); ++ } ++ } ++ ++ _hideError() { ++ if (this._errorBox.visible) { ++ let [errorBoxMinHeight, errorBoxNaturalHeight] = this._errorBox.get_preferred_height(-1); ++ let parentActor = this._errorBox.get_parent(); ++ ++ Tweener.addTween(parentActor, ++ { height: parentActor.height - errorBoxNaturalHeight, ++ time: DIALOG_GROW_TIME, ++ transition: 'easeOutQuad', ++ onComplete: () => { ++ parentActor.set_height(-1); ++ this._errorBox.hide(); ++ this._errorMessage.set_text(''); ++ } ++ }); ++ } ++ } ++ ++ _onCreateButton() { ++ this._onEntryActivate(); ++ } ++ ++ _onEntryActivate() { ++ if (!this._createButton.reactive) ++ return; ++ ++ this.emit('response', this._entry.get_text()); ++ this.close(); ++ } ++ ++ _onTextChanged() { ++ let text = this._entry.get_text(); ++ let is_valid = true; ++ ++ let found_name = false; ++ for(let name of Extension.desktopManager.getDesktopFileNames()) { ++ if (name === text) { ++ found_name = true; ++ break; ++ } ++ } ++ ++ if (text.trim().length == 0) { ++ is_valid = false; ++ this._hideError(); ++ } else if (text.includes('/')) { ++ is_valid = false; ++ this._showError(_('Folder names cannot contain “/”.')); ++ } else if (text === '.') { ++ is_valid = false; ++ this._showError(_('A folder cannot be called “.”.')); ++ } else if (text === '..') { ++ is_valid = false; ++ this._showError(_('A folder cannot be called “..”.')); ++ } else if (text.startsWith('.')) { ++ this._showError(_('Folders with “.” at the beginning of their name are hidden.')); ++ } else if (found_name) { ++ this._showError(_('There is already a file or folder with that name.')); ++ is_valid = false; ++ } else { ++ this._hideError(); ++ } ++ ++ this._createButton.reactive = is_valid; ++ } ++}; ++Signals.addSignalMethods(CreateFolderDialog.prototype); +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..19fe987 +--- /dev/null ++++ b/extensions/desktop-icons/dbusUtils.js +@@ -0,0 +1,103 @@ ++const Gio = imports.gi.Gio; ++const GLib = imports.gi.GLib; ++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'); ++ } ++ } ++ ); ++} ++ ++function openFileWithOtherApplication(filePath) { ++ let fdList = new Gio.UnixFDList(); ++ let channel = GLib.IOChannel.new_file(filePath, "r"); ++ fdList.append(channel.unix_get_fd()); ++ channel.set_close_on_unref(true); ++ let builder = GLib.VariantBuilder.new(GLib.VariantType.new("a{sv}")); ++ let options = builder.end(); ++ let parameters = GLib.Variant.new_tuple([GLib.Variant.new_string("0"), ++ GLib.Variant.new_handle(0), ++ options]); ++ Gio.bus_get(Gio.BusType.SESSION, null, ++ (source, result) => { ++ let dbus_connection = Gio.bus_get_finish(result); ++ dbus_connection.call_with_unix_fd_list("org.freedesktop.portal.Desktop", ++ "/org/freedesktop/portal/desktop", ++ "org.freedesktop.portal.OpenURI", ++ "OpenFile", ++ parameters, ++ GLib.VariantType.new("o"), ++ Gio.DBusCallFlags.NONE, ++ -1, ++ fdList, ++ null, ++ null); ++ } ++ ); ++} +diff --git a/extensions/desktop-icons/desktopGrid.js b/extensions/desktop-icons/desktopGrid.js +new file mode 100644 +index 0000000..a2d1f12 +--- /dev/null ++++ b/extensions/desktop-icons/desktopGrid.js +@@ -0,0 +1,692 @@ ++/* 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 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 Config = imports.misc.config; ++ ++const ExtensionUtils = imports.misc.extensionUtils; ++const Me = ExtensionUtils.getCurrentExtension(); ++const CreateFolderDialog = Me.imports.createFolderDialog; ++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, ++ ASSIGN:2, ++}; ++ ++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._actorLayout = new Clutter.BinLayout({ ++ x_align: Clutter.BinAlignment.FIXED, ++ y_align: Clutter.BinAlignment.FIXED ++ }); ++ ++ this.actor = new St.Widget({ ++ layout_manager: this._actorLayout ++ }); ++ 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.actor.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; ++ } ++ ++ _onNewFolderClicked() { ++ ++ let dialog = new CreateFolderDialog.CreateFolderDialog(); ++ ++ dialog.connect('response', (dialog, name) => { ++ let dir = DesktopIconsUtil.getDesktopDir().get_child(name); ++ DBusUtils.NautilusFileOperationsProxy.CreateFolderRemote(dir.get_uri(), ++ (result, error) => { ++ if (error) ++ throw new Error('Error creating new folder: ' + error.message); ++ } ++ ); ++ }); ++ ++ dialog.open(); ++ } ++ ++ _parseClipboardText(text) { ++ if (text === null) ++ return [false, false, null]; ++ ++ 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, result) => { ++ try { ++ Gio.AppInfo.launch_default_for_uri_finish(result); ++ } catch (e) { ++ log('Error opening Desktop in Files: ' + e.message); ++ } ++ } ++ ); ++ } ++ ++ _onOpenTerminalClicked() { ++ let desktopPath = DesktopIconsUtil.getDesktopDir().get_path(); ++ DesktopIconsUtil.launchTerminal(desktopPath); ++ } ++ ++ _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._onNewFolderClicked()); ++ 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.setSensitive(valid); ++ } ++ ); ++ } ++ } ++ ); ++ this._pasteMenuItem.setSensitive(false); ++ ++ 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, coordinatesAction) { ++ 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) || (coordinatesAction == StoredCoordinates.OVERWRITE)) { ++ let [fileX, fileY] = placeholder.get_transformed_position(); ++ fileItem.savedCoordinates = [Math.round(fileX), Math.round(fileY)]; ++ } ++ } ++ ++ addFileItemCloseTo(fileItem, x, y, coordinatesAction) { ++ let [column, row] = this._getEmptyPlaceClosestTo(x, y, coordinatesAction); ++ this._addFileItemTo(fileItem, column, row, coordinatesAction); ++ } ++ ++ _getEmptyPlaceClosestTo(x, y, coordinatesAction) { ++ 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(); ++ if (coordinatesAction == StoredCoordinates.ASSIGN) ++ return [column, row]; ++ 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.actor.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: false }); ++ 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); ++ if (ExtensionUtils.versionCheck(['3.28', '3.30'], Config.PACKAGE_VERSION)) ++ this._boxPointer.show(BoxPointer.PopupAnimation.FADE | ++ BoxPointer.PopupAnimation.SLIDE); ++ else ++ this._boxPointer.open(BoxPointer.PopupAnimation.FADE | ++ BoxPointer.PopupAnimation.SLIDE); ++ ++ this.emit('open-state-changed', true); ++ } ++ ++ _popdown() { ++ if (!this._isOpen) ++ return; ++ ++ this._grabHelper.ungrab({ actor: this.actor }); ++ ++ if (ExtensionUtils.versionCheck(['3.28', '3.30'], Config.PACKAGE_VERSION)) ++ this._boxPointer.hide(BoxPointer.PopupAnimation.FADE | ++ BoxPointer.PopupAnimation.SLIDE); ++ else ++ this._boxPointer.close(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 extensionOffset = DesktopIconsUtil.getFileExtensionOffset(fileItem.displayName, fileItem.isDirectory); ++ this._renameEntry.clutter_text.set_selection(0, extensionOffset); ++ } ++ ++ _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..0aea654 +--- /dev/null ++++ b/extensions/desktop-icons/desktopIconsUtil.js +@@ -0,0 +1,123 @@ ++/* 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 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 launchTerminal(workdir) { ++ let terminalSettings = new Gio.Settings({ schema_id: TERMINAL_SCHEMA }); ++ let exec = terminalSettings.get_string(EXEC_KEY); ++ let argv = [exec, `--working-directory=${workdir}`]; ++ ++ /* The following code has been extracted from GNOME Shell's ++ * source code in Misc.Util.trySpawn function and modified to ++ * set the working directory. ++ * ++ * https://gitlab.gnome.org/GNOME/gnome-shell/blob/gnome-3-30/js/misc/util.js ++ */ ++ ++ var success, pid; ++ try { ++ [success, pid] = GLib.spawn_async(workdir, argv, null, ++ GLib.SpawnFlags.SEARCH_PATH | GLib.SpawnFlags.DO_NOT_REAP_CHILD, ++ null); ++ } catch (err) { ++ /* Rewrite the error in case of ENOENT */ ++ if (err.matches(GLib.SpawnError, GLib.SpawnError.NOENT)) { ++ throw new GLib.SpawnError({ code: GLib.SpawnError.NOENT, ++ message: _("Command not found") }); ++ } else if (err instanceof GLib.Error) { ++ // The exception from gjs contains an error string like: ++ // Error invoking GLib.spawn_command_line_async: Failed to ++ // execute child process "foo" (No such file or directory) ++ // We are only interested in the part in the parentheses. (And ++ // we can't pattern match the text, since it gets localized.) ++ let message = err.message.replace(/.*\((.+)\)/, '$1'); ++ throw new (err.constructor)({ code: err.code, ++ message: message }); ++ } else { ++ throw err; ++ } ++ } ++ // Dummy child watch; we don't want to double-fork internally ++ // because then we lose the parent-child relationship, which ++ // can break polkit. See https://bugzilla.redhat.com//show_bug.cgi?id=819275 ++ GLib.child_watch_add(GLib.PRIORITY_DEFAULT, pid, () => {}); ++} ++ ++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; ++} ++ ++function getFileExtensionOffset(filename, isDirectory) { ++ let offset = filename.length; ++ ++ if (!isDirectory) { ++ let doubleExtensions = ['.gz', '.bz2', '.sit', '.Z', '.bz', '.xz']; ++ for (let extension of doubleExtensions) { ++ if (filename.endsWith(extension)) { ++ offset -= extension.length; ++ filename = filename.substring(0, offset); ++ break; ++ } ++ } ++ let lastDot = filename.lastIndexOf('.'); ++ if (lastDot > 0) ++ offset = lastDot; ++ } ++ return offset; ++} ++ ++function getGtkClassBackgroundColor(classname, state) { ++ let widget = new Gtk.WidgetPath(); ++ widget.append_type(Gtk.Widget); ++ ++ let context = new Gtk.StyleContext(); ++ context.set_path(widget); ++ context.add_class(classname); ++ return context.get_background_color(state); ++} +diff --git a/extensions/desktop-icons/desktopManager.js b/extensions/desktop-icons/desktopManager.js +new file mode 100644 +index 0000000..d8f548f +--- /dev/null ++++ b/extensions/desktop-icons/desktopManager.js +@@ -0,0 +1,752 @@ ++/* 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 GObject = imports.gi.GObject; ++const Gio = imports.gi.Gio; ++const GLib = imports.gi.GLib; ++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._backgroundGroup.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._currentSelection = 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._initRubberBandColor(); ++ 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) => { ++ /* In some cases, when the user starts a rubberband selection and ends it ++ * (by releasing the left button) over a window instead of doing it over ++ * the desktop, the stage doesn't receive the "button-release" event. ++ * This happens currently with, at least, Dash to Dock extension, but ++ * it probably also happens with other applications or extensions. ++ * To fix this, we also end the rubberband selection if we detect mouse ++ * motion in the stage without the left button pressed during a ++ * rubberband selection. ++ * */ ++ let button = event.get_state(); ++ if (!(button & Clutter.ModifierType.BUTTON1_MASK)) { ++ this.endRubberBand(); ++ return; ++ } ++ [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, 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; ++ ++ this._selection = new Set([...this._selection, ...this._currentSelection]); ++ this._currentSelection.clear(); ++ } ++ ++ _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 = {}; ++ } ++ ++ /** ++ * Initialize rubberband color from the GTK rubberband class ++ * */ ++ _initRubberBandColor() { ++ let rgba = DesktopIconsUtil.getGtkClassBackgroundColor('rubberband', Gtk.StateFlags.NORMAL); ++ let background_color = ++ 'rgba(' + rgba.red * 255 + ', ' + rgba.green * 255 + ', ' + rgba.blue * 255 + ', 0.4)'; ++ this._rubberBand.set_style('background-color: ' + background_color); ++ } ++ ++ async _scanFiles() { ++ for (let [fileItem, id] of this._fileItemHandlers) ++ fileItem.disconnect(id); ++ this._fileItemHandlers = 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 { ++ let tmpFileItems = new Map(); ++ for (let [file, info, extra] of await this._enumerateDesktop()) { ++ let fileItem = new FileItem.FileItem(file, info, extra); ++ tmpFileItems.set(fileItem.file.get_uri(), fileItem); ++ let id = fileItem.connect('selected', ++ this._onFileItemSelected.bind(this)); ++ ++ this._fileItemHandlers.set(fileItem, id); ++ } ++ this._fileItems = tmpFileItems; ++ } catch (e) { ++ if (!e.matches(Gio.IOErrorEnum, Gio.IOErrorEnum.CANCELLED)) ++ log(`Error loading desktop files ${e.message}`); ++ return; ++ } ++ ++ this.scheduleReLayoutChildren(); ++ } ++ ++ getDesktopFileNames () { ++ let fileList = []; ++ for (let [uri, item] of this._fileItems) { ++ fileList.push(item.fileName); ++ } ++ return fileList; ++ } ++ ++ _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, ++ (source, result) => { ++ try { ++ let fileEnum = source.enumerate_children_finish(result); ++ let resultGenerator = function *() { ++ let info; ++ for (let [newFolder, extras] of DesktopIconsUtil.getExtraFolders()) { ++ yield [newFolder, newFolder.query_info(DesktopIconsUtil.DEFAULT_ATTRIBUTES, Gio.FileQueryInfoFlags.NONE, this._desktopEnumerateCancellable), extras]; ++ } ++ while ((info = fileEnum.next_file(null))) ++ yield [fileEnum.get_child(info), info, Prefs.FileType.NONE]; ++ }.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; ++ } ++ ++ getNumberOfSelectedItems() { ++ return this._selection.size; ++ } ++ ++ 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, result) => { ++ try { ++ let info = source.query_info_finish(result); ++ 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 coordinates; ++ let x = 0; ++ let y = 0; ++ let coordinatesAction = DesktopGrid.StoredCoordinates.ASSIGN; ++ if (item.savedCoordinates != null) { ++ [x, y] = item.savedCoordinates; ++ coordinatesAction = DesktopGrid.StoredCoordinates.PRESERVE; ++ } ++ let monitorIndex = findMonitorIndexForPos(x, y); ++ let desktopGrid = this._desktopGrids[monitorIndex]; ++ try { ++ desktopGrid.addFileItemCloseTo(item, x, y, coordinatesAction); ++ } 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, rubberBandSelection, addToSelection) { ++ ++ if (!keepCurrentSelection && !this._inDrag) ++ this.clearSelection(); ++ ++ let selection = keepCurrentSelection && rubberBandSelection ? this._currentSelection : this._selection; ++ if (addToSelection) ++ selection.add(fileItem); ++ else ++ selection.delete(fileItem); ++ ++ for (let [fileUri, fileItem] of this._fileItems) ++ fileItem.isSelected = this._currentSelection.has(fileItem) || this._selection.has(fileItem); ++ } ++ ++ clearSelection() { ++ for (let [fileUri, fileItem] of this._fileItems) ++ fileItem.isSelected = false; ++ this._selection = new Set(); ++ this._currentSelection = 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..4b960ab +--- /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; ++} +diff --git a/extensions/desktop-icons/fileItem.js b/extensions/desktop-icons/fileItem.js +new file mode 100644 +index 0000000..e6f4f2c +--- /dev/null ++++ b/extensions/desktop-icons/fileItem.js +@@ -0,0 +1,800 @@ ++/* 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 Gio = imports.gi.Gio; ++const GLib = imports.gi.GLib; ++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); ++ this._monitorDesktopFile.cancel(); ++ } ++ ++ /* Trash */ ++ if (this._monitorTrashDir) { ++ this._monitorTrashDir.disconnect(this._monitorTrashId); ++ this._monitorTrashDir.cancel(); ++ } ++ 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, result) => { ++ try { ++ let newFileInfo = source.query_info_finish(result); ++ 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._isHidden = fileInfo.get_is_hidden() | fileInfo.get_is_backup(); ++ 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, ++ (source, result) => { ++ try { ++ let [thumbnailData, etag_out] = source.load_bytes_finish(result); ++ 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, result) => { ++ try { ++ this._fileInfo = source.query_info_finish(result); ++ 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._isHidden; ++ } ++ ++ _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, result) => { ++ try { ++ Gio.AppInfo.launch_default_for_uri_finish(result); ++ } 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, result) => { ++ try { ++ source.set_attributes_finish(result); ++ 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, result) => { ++ try { ++ source.set_attributes_finish (result); ++ } catch(e) { ++ log(`Failed to set unix mode: ${e.message}`); ++ } ++ }); ++ } ++ } ++ ++ canRename() { ++ return !this.trustedDesktopFile && this._fileExtra == Prefs.FileType.NONE; ++ } ++ ++ _doOpenWith() { ++ DBusUtils.openFileWithOtherApplication(this.file.get_path()); ++ } ++ ++ _getSelectionStyle() { ++ let rgba = DesktopIconsUtil.getGtkClassBackgroundColor('view', Gtk.StateFlags.SELECTED); ++ let background_color = ++ 'rgba(' + rgba.red * 255 + ', ' + rgba.green * 255 + ', ' + rgba.blue * 255 + ', 0.6)'; ++ let border_color = ++ 'rgba(' + rgba.red * 255 + ', ' + rgba.green * 255 + ', ' + rgba.blue * 255 + ', 0.8)'; ++ ++ return 'background-color: ' + background_color + ';' + ++ 'border-color: ' + border_color + ';' ++ } ++ ++ _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: ++ if (!this._isDirectory) ++ this._actionOpenWith = this._menu.addAction(_('Open With Other Application'), () => this._doOpenWith()); ++ else ++ this._actionOpenWith = null; ++ this._menu.addMenuItem(new PopupMenu.PopupSeparatorMenuItem()); ++ 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 () { ++ DesktopIconsUtil.launchTerminal(this.file.get_path()); ++ } ++ ++ _onPressButton(actor, event) { ++ let button = event.get_button(); ++ if (button == 3) { ++ if (!this.isSelected) ++ this.emit('selected', false, false, true); ++ this._menu.toggle(); ++ if (this._actionOpenWith) { ++ let allowOpenWith = (Extension.desktopManager.getNumberOfSelectedItems() == 1); ++ this._actionOpenWith.setSensitive(allowOpenWith); ++ } ++ 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, false, 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, false, 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.set_style(this._getSelectionStyle()); ++ } else { ++ this._container.set_style('background-color: transparent'); ++ this._container.set_style('border-color: transparent'); ++ } ++ ++ 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 fileName() { ++ return this._fileInfo.get_name(); ++ } ++ ++ 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..8431af6 +--- /dev/null ++++ b/extensions/desktop-icons/meson.build +@@ -0,0 +1,19 @@ ++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( ++ 'createFolderDialog.js', ++ '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..65b7521 +--- /dev/null ++++ b/extensions/desktop-icons/po/LINGUAS +@@ -0,0 +1,19 @@ ++cs ++da ++de ++es ++fi ++fr ++fur ++hr ++hu ++id ++it ++ja ++nl ++pl ++pt_BR ++ru ++sv ++tr ++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..f5f9db4 +--- /dev/null ++++ b/extensions/desktop-icons/po/cs.po +@@ -0,0 +1,195 @@ ++# 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. ++# Milan Zink , 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: 2019-03-01 12:49+0000\n" ++"PO-Revision-Date: 2019-03-02 18:02+0100\n" ++"Last-Translator: Daniel Rusek \n" ++"Language-Team: Czech \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: Poedit 2.2.1\n" ++ ++#: createFolderDialog.js:48 ++msgid "New folder name" ++msgstr "Název nové složky" ++ ++#: createFolderDialog.js:72 ++msgid "Create" ++msgstr "Vytvořit" ++ ++#: createFolderDialog.js:74 desktopGrid.js:586 ++msgid "Cancel" ++msgstr "Zrušit" ++ ++#: createFolderDialog.js:145 ++msgid "Folder names cannot contain “/”." ++msgstr "Názvy složek nesmí obsahovat „/“." ++ ++#: createFolderDialog.js:148 ++msgid "A folder cannot be called “.”." ++msgstr "Složka se nemůže jmenovat „.“." ++ ++#: createFolderDialog.js:151 ++msgid "A folder cannot be called “..”." ++msgstr "Složka se nemůže jmenovat „..“." ++ ++#: createFolderDialog.js:153 ++msgid "Folders with “.” at the beginning of their name are hidden." ++msgstr "Složky s „.“ na začátku jejich názvu jsou skryty." ++ ++#: createFolderDialog.js:155 ++msgid "There is already a file or folder with that name." ++msgstr "Soubor nebo složka s tímto názvem již existuje." ++ ++#: prefs.js:102 ++msgid "Size for the desktop icons" ++msgstr "Velikost ikon na pracovní ploše" ++ ++#: prefs.js:102 ++msgid "Small" ++msgstr "malé" ++ ++#: prefs.js:102 ++msgid "Standard" ++msgstr "standardní" ++ ++#: prefs.js:102 ++msgid "Large" ++msgstr "velké" ++ ++#: prefs.js:103 ++msgid "Show the personal folder in the desktop" ++msgstr "Zobrazovat osobní složku na pracovní ploše" ++ ++#: prefs.js:104 ++msgid "Show the trash icon in the desktop" ++msgstr "Zobrazovat ikonu koše na pracovní ploše" ++ ++#: desktopGrid.js:320 ++msgid "New Folder" ++msgstr "Nová složka" ++ ++#: desktopGrid.js:322 ++msgid "Paste" ++msgstr "Vložit" ++ ++#: desktopGrid.js:323 ++msgid "Undo" ++msgstr "Zpět" ++ ++#: desktopGrid.js:324 ++msgid "Redo" ++msgstr "Znovu" ++ ++#: desktopGrid.js:326 ++msgid "Show Desktop in Files" ++msgstr "Zobrazit plochu v Souborech" ++ ++#: desktopGrid.js:327 fileItem.js:606 ++msgid "Open in Terminal" ++msgstr "Otevřít v terminálu" ++ ++#: desktopGrid.js:329 ++msgid "Change Background…" ++msgstr "Změnit pozadí…" ++ ++#: desktopGrid.js:331 ++msgid "Display Settings" ++msgstr "Zobrazit nastavení" ++ ++#: desktopGrid.js:332 ++msgid "Settings" ++msgstr "Nastavení" ++ ++#: desktopGrid.js:576 ++msgid "Enter file name…" ++msgstr "Zadejte název souboru…" ++ ++#: desktopGrid.js:580 ++msgid "OK" ++msgstr "Budiž" ++ ++#: fileItem.js:490 ++msgid "Don’t Allow Launching" ++msgstr "Nepovolit spouštění" ++ ++#: fileItem.js:492 ++msgid "Allow Launching" ++msgstr "Povolit spouštění" ++ ++#: fileItem.js:574 ++msgid "Open" ++msgstr "Otevřít" ++ ++#: fileItem.js:578 ++msgid "Open With Other Application" ++msgstr "Otevřít pomocí jiné aplikace" ++ ++#: fileItem.js:582 ++msgid "Cut" ++msgstr "Vyjmout" ++ ++#: fileItem.js:583 ++msgid "Copy" ++msgstr "Kopírovat" ++ ++#: fileItem.js:585 ++msgid "Rename…" ++msgstr "Přejmenovat…" ++ ++#: fileItem.js:586 ++msgid "Move to Trash" ++msgstr "Přesunout do koše" ++ ++#: fileItem.js:596 ++msgid "Empty Trash" ++msgstr "Vyprázdnit koš" ++ ++#: fileItem.js:602 ++msgid "Properties" ++msgstr "Vlastnosti" ++ ++#: fileItem.js:604 ++msgid "Show in Files" ++msgstr "Zobrazit v Souborech" ++ ++#: schemas/org.gnome.shell.extensions.desktop-icons.gschema.xml:11 ++msgid "Icon size" ++msgstr "Velikost ikon" ++ ++#: schemas/org.gnome.shell.extensions.desktop-icons.gschema.xml:12 ++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:16 ++msgid "Show personal folder" ++msgstr "Zobrazovat osobní složku" ++ ++#: schemas/org.gnome.shell.extensions.desktop-icons.gschema.xml:17 ++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:21 ++msgid "Show trash icon" ++msgstr "Zobrazovat koš" ++ ++#: schemas/org.gnome.shell.extensions.desktop-icons.gschema.xml:22 ++msgid "Show the trash icon in the desktop." ++msgstr "Zobrazovat ikonu koše na pracovní ploše." ++ ++#~ msgid "Huge" ++#~ msgstr "obrovské" ++ ++#~ msgid "Ok" ++#~ msgstr "Ok" +diff --git a/extensions/desktop-icons/po/da.po b/extensions/desktop-icons/po/da.po +new file mode 100644 +index 0000000..d3f51f0 +--- /dev/null ++++ b/extensions/desktop-icons/po/da.po +@@ -0,0 +1,159 @@ ++# 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: 2019-01-15 10:59+0000\n" ++"PO-Revision-Date: 2019-02-09 11:35+0100\n" ++"Last-Translator: Alan Mortensen \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" ++"X-Generator: Poedit 2.0.6\n" ++ ++#: prefs.js:102 ++msgid "Size for the desktop icons" ++msgstr "Størrelsen på skrivebordsikoner" ++ ++#: prefs.js:102 ++msgid "Small" ++msgstr "Små" ++ ++#: prefs.js:102 ++msgid "Standard" ++msgstr "Standard" ++ ++#: prefs.js:102 ++msgid "Large" ++msgstr "Store" ++ ++#: prefs.js:103 ++msgid "Show the personal folder in the desktop" ++msgstr "Vis den personlige mappe på skrivebordet" ++ ++#: prefs.js:104 ++msgid "Show the trash icon in the desktop" ++msgstr "Vis papirkurvsikonet på skrivebordet" ++ ++#: desktopGrid.js:187 desktopGrid.js:306 ++msgid "New Folder" ++msgstr "Ny mappe" ++ ++#: desktopGrid.js:308 ++msgid "Paste" ++msgstr "Indsæt" ++ ++#: desktopGrid.js:309 ++msgid "Undo" ++msgstr "Fortryd" ++ ++#: desktopGrid.js:310 ++msgid "Redo" ++msgstr "Omgør" ++ ++#: desktopGrid.js:312 ++msgid "Show Desktop in Files" ++msgstr "Vis skrivebordet i Filer" ++ ++#: desktopGrid.js:313 fileItem.js:586 ++msgid "Open in Terminal" ++msgstr "Åbn i terminal" ++ ++#: desktopGrid.js:315 ++msgid "Change Background…" ++msgstr "Skift baggrund …" ++ ++#: desktopGrid.js:317 ++msgid "Display Settings" ++msgstr "Skærmindstillinger" ++ ++#: desktopGrid.js:318 ++msgid "Settings" ++msgstr "Indstillinger" ++ ++#: desktopGrid.js:559 ++msgid "Enter file name…" ++msgstr "Indtast filnavn …" ++ ++#: desktopGrid.js:563 ++msgid "OK" ++msgstr "OK" ++ ++#: desktopGrid.js:569 ++msgid "Cancel" ++msgstr "Annullér" ++ ++#: fileItem.js:490 ++msgid "Don’t Allow Launching" ++msgstr "Tillad ikke opstart" ++ ++#: fileItem.js:492 ++msgid "Allow Launching" ++msgstr "Tillad opstart" ++ ++#: fileItem.js:559 ++msgid "Open" ++msgstr "Åbn" ++ ++#: fileItem.js:562 ++msgid "Cut" ++msgstr "Klip" ++ ++#: fileItem.js:563 ++msgid "Copy" ++msgstr "Kopiér" ++ ++#: fileItem.js:565 ++msgid "Rename…" ++msgstr "Omdøb …" ++ ++#: fileItem.js:566 ++msgid "Move to Trash" ++msgstr "Flyt til papirkurven" ++ ++#: fileItem.js:576 ++msgid "Empty Trash" ++msgstr "Tøm papirkurven" ++ ++#: fileItem.js:582 ++msgid "Properties" ++msgstr "Egenskaber" ++ ++#: fileItem.js:584 ++msgid "Show in Files" ++msgstr "Vis i Filer" ++ ++#: schemas/org.gnome.shell.extensions.desktop-icons.gschema.xml:11 ++msgid "Icon size" ++msgstr "Ikonstørrelse" ++ ++#: schemas/org.gnome.shell.extensions.desktop-icons.gschema.xml:12 ++msgid "Set the size for the desktop icons." ++msgstr "Angiv størrelsen på skrivebordsikoner." ++ ++#: schemas/org.gnome.shell.extensions.desktop-icons.gschema.xml:16 ++msgid "Show personal folder" ++msgstr "Vis personlig mappe" ++ ++#: schemas/org.gnome.shell.extensions.desktop-icons.gschema.xml:17 ++msgid "Show the personal folder in the desktop." ++msgstr "Vis den personlige mappe på skrivebordet." ++ ++#: schemas/org.gnome.shell.extensions.desktop-icons.gschema.xml:21 ++msgid "Show trash icon" ++msgstr "Vis papirkurvsikon" ++ ++#: schemas/org.gnome.shell.extensions.desktop-icons.gschema.xml:22 ++msgid "Show the trash icon in the desktop." ++msgstr "Vis papirkurvsikonet på skrivebordet." ++ ++#~ msgid "Huge" ++#~ msgstr "Enorme" +diff --git a/extensions/desktop-icons/po/de.po b/extensions/desktop-icons/po/de.po +new file mode 100644 +index 0000000..ed5a257 +--- /dev/null ++++ b/extensions/desktop-icons/po/de.po +@@ -0,0 +1,192 @@ ++# 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. ++# rugk , 2019. ++# ++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: 2019-03-07 22:46+0000\n" ++"PO-Revision-Date: 2019-03-09 15:42+0100\n" ++"Last-Translator: Tim Sabsch \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.2.1\n" ++ ++#: createFolderDialog.js:48 ++msgid "New folder name" ++msgstr "Neuer Ordner" ++ ++#: createFolderDialog.js:72 ++msgid "Create" ++msgstr "Erstellen" ++ ++#: createFolderDialog.js:74 desktopGrid.js:586 ++msgid "Cancel" ++msgstr "Abbrechen" ++ ++#: createFolderDialog.js:145 ++msgid "Folder names cannot contain “/”." ++msgstr "Ordnernamen dürfen kein »/« enthalten." ++ ++#: createFolderDialog.js:148 ++msgid "A folder cannot be called “.”." ++msgstr "Ein Ordner darf nicht ».« genannt werden." ++ ++#: createFolderDialog.js:151 ++msgid "A folder cannot be called “..”." ++msgstr "Ein Ordner darf nicht »..« genannt werden." ++ ++#: createFolderDialog.js:153 ++msgid "Folders with “.” at the beginning of their name are hidden." ++msgstr "Ordner mit ».« am Anfang sind verborgen." ++ ++#: createFolderDialog.js:155 ++msgid "There is already a file or folder with that name." ++msgstr "Es gibt bereits eine Datei oder einen Ordner mit diesem Namen." ++ ++#: prefs.js:102 ++msgid "Size for the desktop icons" ++msgstr "Größe der Arbeitsflächensymbole" ++ ++#: prefs.js:102 ++msgid "Small" ++msgstr "Klein" ++ ++#: prefs.js:102 ++msgid "Standard" ++msgstr "Standard" ++ ++#: prefs.js:102 ++msgid "Large" ++msgstr "Groß" ++ ++#: prefs.js:103 ++msgid "Show the personal folder in the desktop" ++msgstr "Den persönlichen Ordner auf der Arbeitsfläche anzeigen" ++ ++#: prefs.js:104 ++msgid "Show the trash icon in the desktop" ++msgstr "Papierkorb-Symbol auf der Arbeitsfläche anzeigen" ++ ++#: desktopGrid.js:320 ++msgid "New Folder" ++msgstr "Neuer Ordner" ++ ++#: desktopGrid.js:322 ++msgid "Paste" ++msgstr "Einfügen" ++ ++#: desktopGrid.js:323 ++msgid "Undo" ++msgstr "Rückgängig" ++ ++#: desktopGrid.js:324 ++msgid "Redo" ++msgstr "Wiederholen" ++ ++#: desktopGrid.js:326 ++msgid "Show Desktop in Files" ++msgstr "Schreibtisch in Dateien anzeigen" ++ ++#: desktopGrid.js:327 fileItem.js:606 ++msgid "Open in Terminal" ++msgstr "Im Terminal öffnen" ++ ++#: desktopGrid.js:329 ++msgid "Change Background…" ++msgstr "Hintergrund ändern …" ++ ++#: desktopGrid.js:331 ++msgid "Display Settings" ++msgstr "Anzeigeeinstellungen" ++ ++#: desktopGrid.js:332 ++msgid "Settings" ++msgstr "Einstellungen" ++ ++#: desktopGrid.js:576 ++msgid "Enter file name…" ++msgstr "Dateinamen eingeben …" ++ ++#: desktopGrid.js:580 ++msgid "OK" ++msgstr "OK" ++ ++#: fileItem.js:490 ++msgid "Don’t Allow Launching" ++msgstr "Start nicht erlauben" ++ ++#: fileItem.js:492 ++msgid "Allow Launching" ++msgstr "Start erlauben" ++ ++#: fileItem.js:574 ++msgid "Open" ++msgstr "Öffnen" ++ ++#: fileItem.js:578 ++msgid "Open With Other Application" ++msgstr "Mit anderer Anwendung öffnen" ++ ++#: fileItem.js:582 ++msgid "Cut" ++msgstr "Ausschneiden" ++ ++#: fileItem.js:583 ++msgid "Copy" ++msgstr "Kopieren" ++ ++#: fileItem.js:585 ++msgid "Rename…" ++msgstr "Umbenennen …" ++ ++#: fileItem.js:586 ++msgid "Move to Trash" ++msgstr "In den Papierkorb verschieben" ++ ++#: fileItem.js:596 ++msgid "Empty Trash" ++msgstr "Papierkorb leeren" ++ ++#: fileItem.js:602 ++msgid "Properties" ++msgstr "Eigenschaften" ++ ++#: fileItem.js:604 ++msgid "Show in Files" ++msgstr "In Dateiverwaltung anzeigen" ++ ++#: schemas/org.gnome.shell.extensions.desktop-icons.gschema.xml:11 ++msgid "Icon size" ++msgstr "Symbolgröße" ++ ++#: schemas/org.gnome.shell.extensions.desktop-icons.gschema.xml:12 ++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:16 ++msgid "Show personal folder" ++msgstr "Persönlichen Ordner anzeigen" ++ ++#: schemas/org.gnome.shell.extensions.desktop-icons.gschema.xml:17 ++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:21 ++msgid "Show trash icon" ++msgstr "Papierkorb-Symbol anzeigen" ++ ++#: schemas/org.gnome.shell.extensions.desktop-icons.gschema.xml:22 ++msgid "Show the trash icon in the desktop." ++msgstr "Das Papierkorb-Symbol auf der Arbeitsfläche anzeigen." ++ ++#~ msgid "Huge" ++#~ msgstr "Riesig" +diff --git a/extensions/desktop-icons/po/es.po b/extensions/desktop-icons/po/es.po +new file mode 100644 +index 0000000..8cc87da +--- /dev/null ++++ b/extensions/desktop-icons/po/es.po +@@ -0,0 +1,218 @@ ++# 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: 1.0\n" ++"Report-Msgid-Bugs-To: https://gitlab.gnome.org/World/ShellExtensions/desktop-" ++"icons/issues\n" ++"POT-Creation-Date: 2019-03-01 12:11+0000\n" ++"PO-Revision-Date: 2019-03-04 10:37+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 3.31.90\n" ++ ++#: createFolderDialog.js:48 ++#| msgid "New Folder" ++msgid "New folder name" ++msgstr "Nombre de la nueva carpeta" ++ ++#: createFolderDialog.js:72 ++msgid "Create" ++msgstr "Crear" ++ ++#: createFolderDialog.js:74 desktopGrid.js:586 ++msgid "Cancel" ++msgstr "Cancelar" ++ ++#: createFolderDialog.js:145 ++msgid "Folder names cannot contain “/”." ++msgstr "Los nombres de carpetas no pueden contener «/»." ++ ++#: createFolderDialog.js:148 ++msgid "A folder cannot be called “.”." ++msgstr "Una carpeta no se puede llamar «.»." ++ ++#: createFolderDialog.js:151 ++msgid "A folder cannot be called “..”." ++msgstr "Una carpeta no se puede llamar «..»." ++ ++#: createFolderDialog.js:153 ++msgid "Folders with “.” at the beginning of their name are hidden." ++msgstr "Las carpetas cuyo nombre empieza por «.» están ocultas." ++ ++#: createFolderDialog.js:155 ++msgid "There is already a file or folder with that name." ++msgstr "Ya hay un archivo o carpeta con ese nombre." ++ ++#: 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: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:320 ++msgid "New Folder" ++msgstr "Nueva carpeta" ++ ++#: desktopGrid.js:322 ++msgid "Paste" ++msgstr "Pegar" ++ ++#: desktopGrid.js:323 ++msgid "Undo" ++msgstr "Deshacer" ++ ++#: desktopGrid.js:324 ++msgid "Redo" ++msgstr "Rehacer" ++ ++#: desktopGrid.js:326 ++msgid "Show Desktop in Files" ++msgstr "Mostrar el escritorio en Archivos" ++ ++#: desktopGrid.js:327 fileItem.js:606 ++msgid "Open in Terminal" ++msgstr "Abrir en una terminal" ++ ++#: desktopGrid.js:329 ++msgid "Change Background…" ++msgstr "Cambiar el fondo..." ++ ++#: desktopGrid.js:331 ++msgid "Display Settings" ++msgstr "Configuración de pantalla" ++ ++#: desktopGrid.js:332 ++msgid "Settings" ++msgstr "Configuración" ++ ++#: desktopGrid.js:576 ++msgid "Enter file name…" ++msgstr "Introduzca el nombre del archivo…" ++ ++#: desktopGrid.js:580 ++msgid "OK" ++msgstr "Aceptar" ++ ++#: fileItem.js:490 ++msgid "Don’t Allow Launching" ++msgstr "No permitir lanzar" ++ ++#: fileItem.js:492 ++msgid "Allow Launching" ++msgstr "Permitir lanzar" ++ ++#: fileItem.js:574 ++msgid "Open" ++msgstr "Abrir" ++ ++#: fileItem.js:578 ++msgid "Open With Other Application" ++msgstr "Abrir con otra aplicación" ++ ++#: fileItem.js:582 ++msgid "Cut" ++msgstr "Cortar" ++ ++#: fileItem.js:583 ++msgid "Copy" ++msgstr "Copiar" ++ ++#: fileItem.js:585 ++msgid "Rename…" ++msgstr "Renombrar…" ++ ++#: fileItem.js:586 ++msgid "Move to Trash" ++msgstr "Mover a la papelera" ++ ++#: fileItem.js:596 ++msgid "Empty Trash" ++msgstr "Vaciar la papelera" ++ ++#: fileItem.js:602 ++msgid "Properties" ++msgstr "Propiedades" ++ ++#: fileItem.js:604 ++msgid "Show in Files" ++msgstr "Mostrar en Files" ++ ++#: schemas/org.gnome.shell.extensions.desktop-icons.gschema.xml:11 ++msgid "Icon size" ++msgstr "Tamaño de los iconos" ++ ++#: schemas/org.gnome.shell.extensions.desktop-icons.gschema.xml:12 ++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:16 ++msgid "Show personal folder" ++msgstr "Mostrar la carpeta personal" ++ ++#: schemas/org.gnome.shell.extensions.desktop-icons.gschema.xml:17 ++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:21 ++msgid "Show trash icon" ++msgstr "Mostrar la papelera" ++ ++#: schemas/org.gnome.shell.extensions.desktop-icons.gschema.xml:22 ++msgid "Show the trash icon in the desktop." ++msgstr "Mostrar la papelera en el escritorio." ++ ++#~ msgid "Huge" ++#~ msgstr "Inmenso" ++ ++#~ 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..71ac40d +--- /dev/null ++++ b/extensions/desktop-icons/po/fi.po +@@ -0,0 +1,191 @@ ++# 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: 2019-03-01 12:11+0000\n" ++"PO-Revision-Date: 2019-03-02 11:29+0200\n" ++"Last-Translator: Jiri Grönroos \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" ++"X-Generator: Poedit 2.0.6\n" ++ ++#: createFolderDialog.js:48 ++msgid "New folder name" ++msgstr "Uusi kansion nimi" ++ ++#: createFolderDialog.js:72 ++msgid "Create" ++msgstr "Luo" ++ ++#: createFolderDialog.js:74 desktopGrid.js:586 ++msgid "Cancel" ++msgstr "Peru" ++ ++#: createFolderDialog.js:145 ++msgid "Folder names cannot contain “/”." ++msgstr "Kansion nimi ei voi sisältää merkkiä “/”." ++ ++#: createFolderDialog.js:148 ++msgid "A folder cannot be called “.”." ++msgstr "Kansion nimi ei voi olla “.”." ++ ++#: createFolderDialog.js:151 ++msgid "A folder cannot be called “..”." ++msgstr "Kansion nimi ei voi olla “..”." ++ ++#: createFolderDialog.js:153 ++msgid "Folders with “.” at the beginning of their name are hidden." ++msgstr "Kansiot, joiden nimi alkaa merkillä “.”, ovat piilotettuja." ++ ++#: createFolderDialog.js:155 ++msgid "There is already a file or folder with that name." ++msgstr "Nimi on jo toisen tiedoston tai kansion käytössä." ++ ++#: prefs.js:102 ++msgid "Size for the desktop icons" ++msgstr "Työpöytäkuvakkeiden koko" ++ ++#: prefs.js:102 ++msgid "Small" ++msgstr "Pieni" ++ ++#: prefs.js:102 ++msgid "Standard" ++msgstr "Normaali" ++ ++#: prefs.js:102 ++msgid "Large" ++msgstr "Suuri" ++ ++#: prefs.js:103 ++msgid "Show the personal folder in the desktop" ++msgstr "Näytä kotikansio työpöydällä" ++ ++#: prefs.js:104 ++msgid "Show the trash icon in the desktop" ++msgstr "Näytä roskakorin kuvake työpöydällä" ++ ++#: desktopGrid.js:320 ++msgid "New Folder" ++msgstr "Uusi kansio" ++ ++#: desktopGrid.js:322 ++msgid "Paste" ++msgstr "Liitä" ++ ++#: desktopGrid.js:323 ++msgid "Undo" ++msgstr "Kumoa" ++ ++#: desktopGrid.js:324 ++msgid "Redo" ++msgstr "Tee uudeleen" ++ ++#: desktopGrid.js:326 ++msgid "Show Desktop in Files" ++msgstr "Näytä työpöytä tiedostonhallinnassa" ++ ++#: desktopGrid.js:327 fileItem.js:606 ++msgid "Open in Terminal" ++msgstr "Avaa päätteessä" ++ ++#: desktopGrid.js:329 ++msgid "Change Background…" ++msgstr "Vaihda taustakuvaa…" ++ ++#: desktopGrid.js:331 ++msgid "Display Settings" ++msgstr "Näytön asetukset" ++ ++#: desktopGrid.js:332 ++msgid "Settings" ++msgstr "Asetukset" ++ ++#: desktopGrid.js:576 ++msgid "Enter file name…" ++msgstr "Anna tiedostonimi…" ++ ++#: desktopGrid.js:580 ++msgid "OK" ++msgstr "OK" ++ ++#: fileItem.js:490 ++msgid "Don’t Allow Launching" ++msgstr "Älä salli käynnistämistä" ++ ++#: fileItem.js:492 ++msgid "Allow Launching" ++msgstr "Salli käynnistäminen" ++ ++#: fileItem.js:574 ++msgid "Open" ++msgstr "Avaa" ++ ++#: fileItem.js:578 ++msgid "Open With Other Application" ++msgstr "Avaa toisella sovelluksella" ++ ++#: fileItem.js:582 ++msgid "Cut" ++msgstr "Leikkaa" ++ ++#: fileItem.js:583 ++msgid "Copy" ++msgstr "Kopioi" ++ ++#: fileItem.js:585 ++msgid "Rename…" ++msgstr "Nimeä uudelleen…" ++ ++#: fileItem.js:586 ++msgid "Move to Trash" ++msgstr "Siirrä roskakoriin" ++ ++#: fileItem.js:596 ++msgid "Empty Trash" ++msgstr "Tyhjennä roskakori" ++ ++#: fileItem.js:602 ++msgid "Properties" ++msgstr "Ominaisuudet" ++ ++#: fileItem.js:604 ++msgid "Show in Files" ++msgstr "Näytä tiedostonhallinnassa" ++ ++#: schemas/org.gnome.shell.extensions.desktop-icons.gschema.xml:11 ++msgid "Icon size" ++msgstr "Kuvakekoko" ++ ++#: schemas/org.gnome.shell.extensions.desktop-icons.gschema.xml:12 ++msgid "Set the size for the desktop icons." ++msgstr "Aseta työpöytäkuvakkeiden koko." ++ ++#: schemas/org.gnome.shell.extensions.desktop-icons.gschema.xml:16 ++msgid "Show personal folder" ++msgstr "Näytä kotikansio" ++ ++#: schemas/org.gnome.shell.extensions.desktop-icons.gschema.xml:17 ++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:21 ++msgid "Show trash icon" ++msgstr "Näytä roskakorin kuvake" ++ ++#: schemas/org.gnome.shell.extensions.desktop-icons.gschema.xml:22 ++msgid "Show the trash icon in the desktop." ++msgstr "Näytä roskakorin kuvake työpöydällä." ++ ++#~ msgid "Huge" ++#~ msgstr "Valtava" +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/fur.po b/extensions/desktop-icons/po/fur.po +new file mode 100644 +index 0000000..3ab6129 +--- /dev/null ++++ b/extensions/desktop-icons/po/fur.po +@@ -0,0 +1,187 @@ ++# Friulian translation for desktop-icons. ++# Copyright (C) 2019 desktop-icons's COPYRIGHT HOLDER ++# This file is distributed under the same license as the desktop-icons package. ++# Fabio Tomat , 2019. ++# ++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: 2019-03-01 12:11+0000\n" ++"PO-Revision-Date: 2019-03-05 22:20+0100\n" ++"Last-Translator: Fabio Tomat \n" ++"Language-Team: Friulian \n" ++"Language: fur\n" ++"MIME-Version: 1.0\n" ++"Content-Type: text/plain; charset=UTF-8\n" ++"Content-Transfer-Encoding: 8bit\n" ++"X-Generator: Poedit 2.2.1\n" ++ ++#: createFolderDialog.js:48 ++msgid "New folder name" ++msgstr "Gnûf non de cartele" ++ ++#: createFolderDialog.js:72 ++msgid "Create" ++msgstr "Cree" ++ ++#: createFolderDialog.js:74 desktopGrid.js:586 ++msgid "Cancel" ++msgstr "Anule" ++ ++#: createFolderDialog.js:145 ++msgid "Folder names cannot contain “/”." ++msgstr "I nons des cartelis no puedin contignî il caratar “/”" ++ ++#: createFolderDialog.js:148 ++msgid "A folder cannot be called “.”." ++msgstr "Une cartele no pues jessi clamade “.”." ++ ++#: createFolderDialog.js:151 ++msgid "A folder cannot be called “..”." ++msgstr "Une cartele no pues jessi clamade “..”." ++ ++#: createFolderDialog.js:153 ++msgid "Folders with “.” at the beginning of their name are hidden." ++msgstr "Lis cartelis cul “.” al inizi dal lôr non a son platadis." ++ ++#: createFolderDialog.js:155 ++msgid "There is already a file or folder with that name." ++msgstr "Un file o une cartele cul stes non e esist za." ++ ++#: prefs.js:102 ++msgid "Size for the desktop icons" ++msgstr "Dimension pes iconis dal scritori" ++ ++#: prefs.js:102 ++msgid "Small" ++msgstr "Piçule" ++ ++#: prefs.js:102 ++msgid "Standard" ++msgstr "Standard" ++ ++#: prefs.js:102 ++msgid "Large" ++msgstr "Largje" ++ ++#: prefs.js:103 ++msgid "Show the personal folder in the desktop" ++msgstr "Mostre la cartele personâl intal scritori" ++ ++#: prefs.js:104 ++msgid "Show the trash icon in the desktop" ++msgstr "Mostre la icone de scovacere intal scritori" ++ ++#: desktopGrid.js:320 ++msgid "New Folder" ++msgstr "Gnove cartele" ++ ++#: desktopGrid.js:322 ++msgid "Paste" ++msgstr "Tache" ++ ++#: desktopGrid.js:323 ++msgid "Undo" ++msgstr "Anule" ++ ++#: desktopGrid.js:324 ++msgid "Redo" ++msgstr "Torne fâ" ++ ++#: desktopGrid.js:326 ++msgid "Show Desktop in Files" ++msgstr "Mostre Scritori in File" ++ ++#: desktopGrid.js:327 fileItem.js:606 ++msgid "Open in Terminal" ++msgstr "Vierç in Terminâl" ++ ++#: desktopGrid.js:329 ++msgid "Change Background…" ++msgstr "Cambie sfont…" ++ ++#: desktopGrid.js:331 ++msgid "Display Settings" ++msgstr "Impostazions visôr" ++ ++#: desktopGrid.js:332 ++msgid "Settings" ++msgstr "Impostazions" ++ ++#: desktopGrid.js:576 ++msgid "Enter file name…" ++msgstr "Inserìs il non dal file…" ++ ++#: desktopGrid.js:580 ++msgid "OK" ++msgstr "Va ben" ++ ++#: fileItem.js:490 ++msgid "Don’t Allow Launching" ++msgstr "No sta permeti inviament" ++ ++#: fileItem.js:492 ++msgid "Allow Launching" ++msgstr "Permet inviament" ++ ++#: fileItem.js:574 ++msgid "Open" ++msgstr "Vierç" ++ ++#: fileItem.js:578 ++msgid "Open With Other Application" ++msgstr "Vierç cuntune altre aplicazion" ++ ++#: fileItem.js:582 ++msgid "Cut" ++msgstr "Taie" ++ ++#: fileItem.js:583 ++msgid "Copy" ++msgstr "Copie" ++ ++#: fileItem.js:585 ++msgid "Rename…" ++msgstr "Cambie non..." ++ ++#: fileItem.js:586 ++msgid "Move to Trash" ++msgstr "Sposte te scovacere" ++ ++#: fileItem.js:596 ++msgid "Empty Trash" ++msgstr "Disvuede scovacere" ++ ++#: fileItem.js:602 ++msgid "Properties" ++msgstr "Propietâts" ++ ++#: fileItem.js:604 ++msgid "Show in Files" ++msgstr "Mostre in File" ++ ++#: schemas/org.gnome.shell.extensions.desktop-icons.gschema.xml:11 ++msgid "Icon size" ++msgstr "Dimension icone" ++ ++#: schemas/org.gnome.shell.extensions.desktop-icons.gschema.xml:12 ++msgid "Set the size for the desktop icons." ++msgstr "Stabilìs la dimension pes iconis dal scritori." ++ ++#: schemas/org.gnome.shell.extensions.desktop-icons.gschema.xml:16 ++msgid "Show personal folder" ++msgstr "Mostre cartele personâl" ++ ++#: schemas/org.gnome.shell.extensions.desktop-icons.gschema.xml:17 ++msgid "Show the personal folder in the desktop." ++msgstr "Mostre la cartele personâl intal scritori." ++ ++#: schemas/org.gnome.shell.extensions.desktop-icons.gschema.xml:21 ++msgid "Show trash icon" ++msgstr "Mostre la icone de scovacere" ++ ++#: schemas/org.gnome.shell.extensions.desktop-icons.gschema.xml:22 ++msgid "Show the trash icon in the desktop." ++msgstr "Mostre la icone de scovacere intal scritori." +diff --git a/extensions/desktop-icons/po/hr.po b/extensions/desktop-icons/po/hr.po +new file mode 100644 +index 0000000..0d26696 +--- /dev/null ++++ b/extensions/desktop-icons/po/hr.po +@@ -0,0 +1,186 @@ ++# Croatian translation for gnome-shell-extension-desktop-icons ++# Copyright (c) 2019 Rosetta Contributors and Canonical Ltd 2019 ++# This file is distributed under the same license as the gnome-shell-extension-desktop-icons package. ++# FIRST AUTHOR , 2019. ++# ++msgid "" ++msgstr "" ++"Project-Id-Version: gnome-shell-extension-desktop-icons\n" ++"Report-Msgid-Bugs-To: FULL NAME \n" ++"POT-Creation-Date: 2019-03-05 11:27+0000\n" ++"PO-Revision-Date: 2019-03-23 16:03+0000\n" ++"Last-Translator: gogo \n" ++"Language-Team: Croatian \n" ++"MIME-Version: 1.0\n" ++"Content-Type: text/plain; charset=UTF-8\n" ++"Content-Transfer-Encoding: 8bit\n" ++"X-Launchpad-Export-Date: 2019-03-27 09:36+0000\n" ++"X-Generator: Launchpad (build 18910)\n" ++ ++#: createFolderDialog.js:48 ++msgid "New folder name" ++msgstr "Novi naziv mape" ++ ++#: createFolderDialog.js:72 ++msgid "Create" ++msgstr "Stvori" ++ ++#: createFolderDialog.js:74 desktopGrid.js:586 ++msgid "Cancel" ++msgstr "Odustani" ++ ++#: createFolderDialog.js:145 ++msgid "Folder names cannot contain “/”." ++msgstr "Naziv mape ne može sadržavati “/”." ++ ++#: createFolderDialog.js:148 ++msgid "A folder cannot be called “.”." ++msgstr "Mapa se ne može nazvati “.”." ++ ++#: createFolderDialog.js:151 ++msgid "A folder cannot be called “..”." ++msgstr "Mapa se ne može nazvati “..”." ++ ++#: createFolderDialog.js:153 ++msgid "Folders with “.” at the beginning of their name are hidden." ++msgstr "Mape sa “.” na početku njihovih naziva su skrivene." ++ ++#: createFolderDialog.js:155 ++msgid "There is already a file or folder with that name." ++msgstr "Već postoji datoteka ili mapa s tim nazivom." ++ ++#: prefs.js:102 ++msgid "Size for the desktop icons" ++msgstr "Veličina ikona radne površine" ++ ++#: prefs.js:102 ++msgid "Small" ++msgstr "Male" ++ ++#: prefs.js:102 ++msgid "Standard" ++msgstr "Standardne" ++ ++#: prefs.js:102 ++msgid "Large" ++msgstr "Velike" ++ ++#: prefs.js:103 ++msgid "Show the personal folder in the desktop" ++msgstr "Prikaži osobnu mapu na radnoj površini" ++ ++#: prefs.js:104 ++msgid "Show the trash icon in the desktop" ++msgstr "Prikaži mapu smeća na radnoj površini" ++ ++#: desktopGrid.js:320 ++msgid "New Folder" ++msgstr "Nova mapa" ++ ++#: desktopGrid.js:322 ++msgid "Paste" ++msgstr "Zalijepi" ++ ++#: desktopGrid.js:323 ++msgid "Undo" ++msgstr "Poništi" ++ ++#: desktopGrid.js:324 ++msgid "Redo" ++msgstr "Ponovi" ++ ++#: desktopGrid.js:326 ++msgid "Show Desktop in Files" ++msgstr "Prikaži radnu površinu u Datotekama" ++ ++#: desktopGrid.js:327 fileItem.js:606 ++msgid "Open in Terminal" ++msgstr "Otvori u Terminalu" ++ ++#: desktopGrid.js:329 ++msgid "Change Background…" ++msgstr "Promijeni pozadinu…" ++ ++#: desktopGrid.js:331 ++msgid "Display Settings" ++msgstr "Postavke zaslona" ++ ++#: desktopGrid.js:332 ++msgid "Settings" ++msgstr "Postavke" ++ ++#: desktopGrid.js:576 ++msgid "Enter file name…" ++msgstr "Upiši naziv datoteke…" ++ ++#: desktopGrid.js:580 ++msgid "OK" ++msgstr "U redu" ++ ++#: fileItem.js:490 ++msgid "Don’t Allow Launching" ++msgstr "Ne dopuštaj pokretanje" ++ ++#: fileItem.js:492 ++msgid "Allow Launching" ++msgstr "Dopusti pokretanje" ++ ++#: fileItem.js:574 ++msgid "Open" ++msgstr "Otvori" ++ ++#: fileItem.js:578 ++msgid "Open With Other Application" ++msgstr "Otvori s drugom aplikacijom" ++ ++#: fileItem.js:582 ++msgid "Cut" ++msgstr "Izreži" ++ ++#: fileItem.js:583 ++msgid "Copy" ++msgstr "Kopiraj" ++ ++#: fileItem.js:585 ++msgid "Rename…" ++msgstr "Preimenuj…" ++ ++#: fileItem.js:586 ++msgid "Move to Trash" ++msgstr "Premjesti u smeće" ++ ++#: fileItem.js:596 ++msgid "Empty Trash" ++msgstr "Isprazni smeće" ++ ++#: fileItem.js:602 ++msgid "Properties" ++msgstr "Svojstva" ++ ++#: fileItem.js:604 ++msgid "Show in Files" ++msgstr "Prikaži u Datotekama" ++ ++#: schemas/org.gnome.shell.extensions.desktop-icons.gschema.xml:11 ++msgid "Icon size" ++msgstr "Veličina ikona" ++ ++#: schemas/org.gnome.shell.extensions.desktop-icons.gschema.xml:12 ++msgid "Set the size for the desktop icons." ++msgstr "Postavi veličinu ikona radne površine." ++ ++#: schemas/org.gnome.shell.extensions.desktop-icons.gschema.xml:16 ++msgid "Show personal folder" ++msgstr "Prikaži osobnu mapu" ++ ++#: schemas/org.gnome.shell.extensions.desktop-icons.gschema.xml:17 ++msgid "Show the personal folder in the desktop." ++msgstr "Prikaži osobnu mapu na radnoj površini." ++ ++#: schemas/org.gnome.shell.extensions.desktop-icons.gschema.xml:21 ++msgid "Show trash icon" ++msgstr "Prikaži ikonu smeća" ++ ++#: schemas/org.gnome.shell.extensions.desktop-icons.gschema.xml:22 ++msgid "Show the trash icon in the desktop." ++msgstr "Prikaži ikonu smeća na radnoj površini." +diff --git a/extensions/desktop-icons/po/hu.po b/extensions/desktop-icons/po/hu.po +new file mode 100644 +index 0000000..a350dd1 +--- /dev/null ++++ b/extensions/desktop-icons/po/hu.po +@@ -0,0 +1,190 @@ ++# Hungarian translation for desktop-icons. ++# Copyright (C) 2019 The Free Software Foundation, inc. ++# This file is distributed under the same license as the desktop-icons package. ++# ++# Balázs Úr , 2019. ++msgid "" ++msgstr "" ++"Project-Id-Version: desktop-icons master\n" ++"Report-Msgid-Bugs-To: https://gitlab.gnome.org/World/ShellExtensions/desktop-i" ++"cons/issues\n" ++"POT-Creation-Date: 2019-03-01 12:11+0000\n" ++"PO-Revision-Date: 2019-03-07 23:45+0100\n" ++"Last-Translator: Balázs Úr \n" ++"Language-Team: Hungarian \n" ++"Language: hu\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: Lokalize 2.0\n" ++ ++#: createFolderDialog.js:48 ++#| msgid "New Folder" ++msgid "New folder name" ++msgstr "Új mappa neve" ++ ++#: createFolderDialog.js:72 ++msgid "Create" ++msgstr "Létrehozás" ++ ++#: createFolderDialog.js:74 desktopGrid.js:586 ++msgid "Cancel" ++msgstr "Mégse" ++ ++#: createFolderDialog.js:145 ++msgid "Folder names cannot contain “/”." ++msgstr "A mappanevek nem tartalmazhatnak „/” karaktert." ++ ++#: createFolderDialog.js:148 ++msgid "A folder cannot be called “.”." ++msgstr "Egy mappának nem lehet „.” a neve." ++ ++#: createFolderDialog.js:151 ++msgid "A folder cannot be called “..”." ++msgstr "Egy mappának nem lehet „..” a neve." ++ ++#: createFolderDialog.js:153 ++msgid "Folders with “.” at the beginning of their name are hidden." ++msgstr "A „.” karakterrel kezdődő nevű mappák rejtettek." ++ ++#: createFolderDialog.js:155 ++msgid "There is already a file or folder with that name." ++msgstr "Már van egy fájl vagy mappa azzal a névvel." ++ ++#: prefs.js:102 ++msgid "Size for the desktop icons" ++msgstr "Az asztali ikonok mérete" ++ ++#: prefs.js:102 ++msgid "Small" ++msgstr "Kicsi" ++ ++#: prefs.js:102 ++msgid "Standard" ++msgstr "Szabványos" ++ ++#: prefs.js:102 ++msgid "Large" ++msgstr "Nagy" ++ ++#: prefs.js:103 ++msgid "Show the personal folder in the desktop" ++msgstr "A személyes mappa megjelenítése az asztalon" ++ ++#: prefs.js:104 ++msgid "Show the trash icon in the desktop" ++msgstr "A kuka ikon megjelenítése az asztalon" ++ ++#: desktopGrid.js:320 ++msgid "New Folder" ++msgstr "Új mappa" ++ ++#: desktopGrid.js:322 ++msgid "Paste" ++msgstr "Beillesztés" ++ ++#: desktopGrid.js:323 ++msgid "Undo" ++msgstr "Visszavonás" ++ ++#: desktopGrid.js:324 ++msgid "Redo" ++msgstr "Újra" ++ ++#: desktopGrid.js:326 ++msgid "Show Desktop in Files" ++msgstr "Asztal megjelenítése a Fájlokban" ++ ++#: desktopGrid.js:327 fileItem.js:606 ++msgid "Open in Terminal" ++msgstr "Megnyitás terminálban" ++ ++#: desktopGrid.js:329 ++msgid "Change Background…" ++msgstr "Háttér megváltoztatása…" ++ ++#: desktopGrid.js:331 ++msgid "Display Settings" ++msgstr "Megjelenítés beállításai" ++ ++#: desktopGrid.js:332 ++msgid "Settings" ++msgstr "Beállítások" ++ ++#: desktopGrid.js:576 ++msgid "Enter file name…" ++msgstr "Adjon meg egy fájlnevet…" ++ ++#: desktopGrid.js:580 ++msgid "OK" ++msgstr "Rendben" ++ ++#: fileItem.js:490 ++msgid "Don’t Allow Launching" ++msgstr "Ne engedélyezzen indítást" ++ ++#: fileItem.js:492 ++msgid "Allow Launching" ++msgstr "Indítás engedélyezése" ++ ++#: fileItem.js:574 ++msgid "Open" ++msgstr "Megnyitás" ++ ++#: fileItem.js:578 ++msgid "Open With Other Application" ++msgstr "Megnyitás egyéb alkalmazással" ++ ++#: fileItem.js:582 ++msgid "Cut" ++msgstr "Kivágás" ++ ++#: fileItem.js:583 ++msgid "Copy" ++msgstr "Másolás" ++ ++#: fileItem.js:585 ++msgid "Rename…" ++msgstr "Átnevezés…" ++ ++#: fileItem.js:586 ++msgid "Move to Trash" ++msgstr "Áthelyezés a Kukába" ++ ++#: fileItem.js:596 ++msgid "Empty Trash" ++msgstr "Kuka ürítése" ++ ++#: fileItem.js:602 ++msgid "Properties" ++msgstr "Tulajdonságok" ++ ++#: fileItem.js:604 ++msgid "Show in Files" ++msgstr "Megjelenítés a Fájlokban" ++ ++#: schemas/org.gnome.shell.extensions.desktop-icons.gschema.xml:11 ++msgid "Icon size" ++msgstr "Ikonméret" ++ ++#: schemas/org.gnome.shell.extensions.desktop-icons.gschema.xml:12 ++msgid "Set the size for the desktop icons." ++msgstr "Az asztali ikonok méretének beállítása." ++ ++#: schemas/org.gnome.shell.extensions.desktop-icons.gschema.xml:16 ++msgid "Show personal folder" ++msgstr "Személyes mappa megjelenítése" ++ ++#: schemas/org.gnome.shell.extensions.desktop-icons.gschema.xml:17 ++msgid "Show the personal folder in the desktop." ++msgstr "A személyes mappa megjelenítése az asztalon." ++ ++#: schemas/org.gnome.shell.extensions.desktop-icons.gschema.xml:21 ++msgid "Show trash icon" ++msgstr "Kuka ikon megjelenítése" ++ ++#: schemas/org.gnome.shell.extensions.desktop-icons.gschema.xml:22 ++msgid "Show the trash icon in the desktop." ++msgstr "A kuka ikon megjelenítése az asztalon." ++ +diff --git a/extensions/desktop-icons/po/id.po b/extensions/desktop-icons/po/id.po +new file mode 100644 +index 0000000..b809c3d +--- /dev/null ++++ b/extensions/desktop-icons/po/id.po +@@ -0,0 +1,190 @@ ++# 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. ++# Kukuh Syafaat , 2018, 2019. ++# ++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: 2019-03-01 12:11+0000\n" ++"PO-Revision-Date: 2019-03-02 19:13+0700\n" ++"Last-Translator: Kukuh Syafaat \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" ++"X-Generator: Poedit 2.2.1\n" ++ ++#: createFolderDialog.js:48 ++msgid "New folder name" ++msgstr "Nama folder baru" ++ ++#: createFolderDialog.js:72 ++msgid "Create" ++msgstr "Buat" ++ ++#: createFolderDialog.js:74 desktopGrid.js:586 ++msgid "Cancel" ++msgstr "Batal" ++ ++#: createFolderDialog.js:145 ++msgid "Folder names cannot contain “/”." ++msgstr "Nama folder tak boleh memuat \"/\"." ++ ++#: createFolderDialog.js:148 ++msgid "A folder cannot be called “.”." ++msgstr "Sebuah folder tak bisa dinamai \".\"." ++ ++#: createFolderDialog.js:151 ++msgid "A folder cannot be called “..”." ++msgstr "Sebuah folder tak bisa dinamai \"..\"." ++ ++#: createFolderDialog.js:153 ++msgid "Folders with “.” at the beginning of their name are hidden." ++msgstr "Folder dengan \".\" di awal nama mereka disembunyikan." ++ ++#: createFolderDialog.js:155 ++msgid "There is already a file or folder with that name." ++msgstr "Folder dengan nama itu sudah ada." ++ ++#: prefs.js:102 ++msgid "Size for the desktop icons" ++msgstr "Ukuran untuk ikon destop" ++ ++#: prefs.js:102 ++msgid "Small" ++msgstr "Kecil" ++ ++#: prefs.js:102 ++msgid "Standard" ++msgstr "Standar" ++ ++#: prefs.js:102 ++msgid "Large" ++msgstr "Besar" ++ ++#: prefs.js:103 ++msgid "Show the personal folder in the desktop" ++msgstr "Tampilkan folder pribadi di destop" ++ ++#: prefs.js:104 ++msgid "Show the trash icon in the desktop" ++msgstr "Tampilkan ikon tong sampah di destop" ++ ++#: desktopGrid.js:320 ++msgid "New Folder" ++msgstr "Folder Baru" ++ ++#: desktopGrid.js:322 ++msgid "Paste" ++msgstr "Tempel" ++ ++#: desktopGrid.js:323 ++msgid "Undo" ++msgstr "Tak Jadi" ++ ++#: desktopGrid.js:324 ++msgid "Redo" ++msgstr "Jadi Lagi" ++ ++#: desktopGrid.js:326 ++msgid "Show Desktop in Files" ++msgstr "Tampilkan Destop pada Berkas" ++ ++#: desktopGrid.js:327 fileItem.js:606 ++msgid "Open in Terminal" ++msgstr "Buka dalam Terminal" ++ ++#: desktopGrid.js:329 ++msgid "Change Background…" ++msgstr "Ubah Latar Belakang…" ++ ++#: desktopGrid.js:331 ++msgid "Display Settings" ++msgstr "Pengaturan Tampilan" ++ ++#: desktopGrid.js:332 ++msgid "Settings" ++msgstr "Pengaturan" ++ ++#: desktopGrid.js:576 ++msgid "Enter file name…" ++msgstr "Masukkan nama berkas…" ++ ++#: desktopGrid.js:580 ++msgid "OK" ++msgstr "OK" ++ ++#: fileItem.js:490 ++msgid "Don’t Allow Launching" ++msgstr "Jangan Izinkan Peluncuran" ++ ++#: fileItem.js:492 ++msgid "Allow Launching" ++msgstr "Izinkan Peluncuran" ++ ++#: fileItem.js:574 ++msgid "Open" ++msgstr "Buka" ++ ++#: fileItem.js:578 ++msgid "Open With Other Application" ++msgstr "Buka Dengan Aplikasi Lain" ++ ++#: fileItem.js:582 ++msgid "Cut" ++msgstr "Potong" ++ ++#: fileItem.js:583 ++msgid "Copy" ++msgstr "Salin" ++ ++#: fileItem.js:585 ++msgid "Rename…" ++msgstr "Ganti Nama…" ++ ++#: fileItem.js:586 ++msgid "Move to Trash" ++msgstr "Pindahkan ke Tong Sampah" ++ ++#: fileItem.js:596 ++msgid "Empty Trash" ++msgstr "Kosongkan Tong Sampah" ++ ++#: fileItem.js:602 ++msgid "Properties" ++msgstr "Properti" ++ ++#: fileItem.js:604 ++msgid "Show in Files" ++msgstr "Tampilkan pada Berkas" ++ ++#: schemas/org.gnome.shell.extensions.desktop-icons.gschema.xml:11 ++msgid "Icon size" ++msgstr "Ukuran ikon" ++ ++#: schemas/org.gnome.shell.extensions.desktop-icons.gschema.xml:12 ++msgid "Set the size for the desktop icons." ++msgstr "Set ukuran untuk ikon destop." ++ ++#: schemas/org.gnome.shell.extensions.desktop-icons.gschema.xml:16 ++msgid "Show personal folder" ++msgstr "Tampilkan folder pribadi" ++ ++#: schemas/org.gnome.shell.extensions.desktop-icons.gschema.xml:17 ++msgid "Show the personal folder in the desktop." ++msgstr "Tampilkan folder pribadi di destop." ++ ++#: schemas/org.gnome.shell.extensions.desktop-icons.gschema.xml:21 ++msgid "Show trash icon" ++msgstr "Tampilkan ikon tong sampah" ++ ++#: schemas/org.gnome.shell.extensions.desktop-icons.gschema.xml:22 ++msgid "Show the trash icon in the desktop." ++msgstr "Tampilkan ikon tong sampah di destop." ++ ++#~ msgid "Huge" ++#~ msgstr "Sangat besar" +diff --git a/extensions/desktop-icons/po/it.po b/extensions/desktop-icons/po/it.po +new file mode 100644 +index 0000000..5001da4 +--- /dev/null ++++ b/extensions/desktop-icons/po/it.po +@@ -0,0 +1,189 @@ ++# Italian translation for desktop-icons. ++# Copyright (C) 2019 desktop-icons's COPYRIGHT HOLDER ++# This file is distributed under the same license as the desktop-icons package. ++# Massimo Branchini , 2019. ++# Milo Casagrande , 2019. ++# ++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: 2019-03-01 12:11+0000\n" ++"PO-Revision-Date: 2019-03-12 09:51+0100\n" ++"Last-Translator: Milo Casagrande \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.1\n" ++ ++#: createFolderDialog.js:48 ++msgid "New folder name" ++msgstr "Nuova cartella" ++ ++#: createFolderDialog.js:72 ++msgid "Create" ++msgstr "Crea" ++ ++#: createFolderDialog.js:74 desktopGrid.js:586 ++msgid "Cancel" ++msgstr "Annulla" ++ ++#: createFolderDialog.js:145 ++msgid "Folder names cannot contain “/”." ++msgstr "I nomi di cartelle non possono contenere il carattere «/»" ++ ++#: createFolderDialog.js:148 ++msgid "A folder cannot be called “.”." ++msgstr "Una cartella non può essere chiamata «.»." ++ ++#: createFolderDialog.js:151 ++msgid "A folder cannot be called “..”." ++msgstr "Una cartella non può essere chiamata «..»." ++ ++#: createFolderDialog.js:153 ++msgid "Folders with “.” at the beginning of their name are hidden." ++msgstr "Cartelle il cui nome inizia con «.» sono nascoste." ++ ++#: createFolderDialog.js:155 ++msgid "There is already a file or folder with that name." ++msgstr "Esiste già un file o una cartella con quel nome." ++ ++#: prefs.js:102 ++msgid "Size for the desktop icons" ++msgstr "Dimensione delle icone della scrivania" ++ ++#: prefs.js:102 ++msgid "Small" ++msgstr "Piccola" ++ ++#: prefs.js:102 ++msgid "Standard" ++msgstr "Normale" ++ ++#: prefs.js:102 ++msgid "Large" ++msgstr "Grande" ++ ++#: prefs.js:103 ++msgid "Show the personal folder in the desktop" ++msgstr "Mostra la cartella personale sulla scrivania" ++ ++#: prefs.js:104 ++msgid "Show the trash icon in the desktop" ++msgstr "Mostra il cestino sulla scrivania" ++ ++#: desktopGrid.js:320 ++msgid "New Folder" ++msgstr "Nuova cartella" ++ ++#: desktopGrid.js:322 ++msgid "Paste" ++msgstr "Incolla" ++ ++#: desktopGrid.js:323 ++msgid "Undo" ++msgstr "Annulla" ++ ++#: desktopGrid.js:324 ++msgid "Redo" ++msgstr "Ripeti" ++ ++#: desktopGrid.js:326 ++msgid "Show Desktop in Files" ++msgstr "Mostra la scrivania in File" ++ ++#: desktopGrid.js:327 fileItem.js:606 ++msgid "Open in Terminal" ++msgstr "Apri in Terminale" ++ ++#: desktopGrid.js:329 ++msgid "Change Background…" ++msgstr "Cambia lo sfondo…" ++ ++#: desktopGrid.js:331 ++msgid "Display Settings" ++msgstr "Impostazioni dello schermo" ++ ++#: desktopGrid.js:332 ++msgid "Settings" ++msgstr "Impostazioni" ++ ++#: desktopGrid.js:576 ++msgid "Enter file name…" ++msgstr "Indicare un nome per il file…" ++ ++#: desktopGrid.js:580 ++msgid "OK" ++msgstr "Ok" ++ ++#: fileItem.js:490 ++msgid "Don’t Allow Launching" ++msgstr "Non permettere l'esecuzione" ++ ++#: fileItem.js:492 ++msgid "Allow Launching" ++msgstr "Permetti l'esecuzione" ++ ++#: fileItem.js:574 ++msgid "Open" ++msgstr "Apri" ++ ++#: fileItem.js:578 ++msgid "Open With Other Application" ++msgstr "Apri con altra applicazione" ++ ++#: fileItem.js:582 ++msgid "Cut" ++msgstr "Taglia" ++ ++#: fileItem.js:583 ++msgid "Copy" ++msgstr "Copia" ++ ++#: fileItem.js:585 ++msgid "Rename…" ++msgstr "Rinomina…" ++ ++#: fileItem.js:586 ++msgid "Move to Trash" ++msgstr "Sposta nel cestino" ++ ++#: fileItem.js:596 ++msgid "Empty Trash" ++msgstr "Svuota il cestino" ++ ++#: fileItem.js:602 ++msgid "Properties" ++msgstr "Proprietà" ++ ++#: fileItem.js:604 ++msgid "Show in Files" ++msgstr "Mostra in File" ++ ++#: schemas/org.gnome.shell.extensions.desktop-icons.gschema.xml:11 ++msgid "Icon size" ++msgstr "Dimensione dell'icona" ++ ++#: schemas/org.gnome.shell.extensions.desktop-icons.gschema.xml:12 ++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:16 ++msgid "Show personal folder" ++msgstr "Mostra la cartella personale" ++ ++#: schemas/org.gnome.shell.extensions.desktop-icons.gschema.xml:17 ++msgid "Show the personal folder in the desktop." ++msgstr "Mostra la cartella personale sulla scrivania." ++ ++#: schemas/org.gnome.shell.extensions.desktop-icons.gschema.xml:21 ++msgid "Show trash icon" ++msgstr "Mostra il cestino" ++ ++#: schemas/org.gnome.shell.extensions.desktop-icons.gschema.xml:22 ++msgid "Show the trash icon in the desktop." ++msgstr "Mostra il cestino sulla scrivania." +diff --git a/extensions/desktop-icons/po/ja.po b/extensions/desktop-icons/po/ja.po +new file mode 100644 +index 0000000..3b103e0 +--- /dev/null ++++ b/extensions/desktop-icons/po/ja.po +@@ -0,0 +1,187 @@ ++# Japanese translation for desktop-icons. ++# Copyright (C) 2019 desktop-icons's COPYRIGHT HOLDER ++# This file is distributed under the same license as the desktop-icons package. ++# sicklylife , 2019. ++# ++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: 2019-03-14 12:56+0000\n" ++"PO-Revision-Date: 2019-03-15 06:30+0000\n" ++"Last-Translator: sicklylife \n" ++"Language-Team: Japanese \n" ++"Language: ja\n" ++"MIME-Version: 1.0\n" ++"Content-Type: text/plain; charset=UTF-8\n" ++"Content-Transfer-Encoding: 8bit\n" ++"Plural-Forms: nplurals=1; plural=0;\n" ++ ++#: createFolderDialog.js:48 ++msgid "New folder name" ++msgstr "新しいフォルダー名" ++ ++#: createFolderDialog.js:72 ++msgid "Create" ++msgstr "作成" ++ ++#: createFolderDialog.js:74 desktopGrid.js:586 ++msgid "Cancel" ++msgstr "キャンセル" ++ ++#: createFolderDialog.js:145 ++msgid "Folder names cannot contain “/”." ++msgstr "“/”は、フォルダー名に含められません。" ++ ++#: createFolderDialog.js:148 ++msgid "A folder cannot be called “.”." ++msgstr "“.”という名前をフォルダーに付けられません。" ++ ++#: createFolderDialog.js:151 ++msgid "A folder cannot be called “..”." ++msgstr "“..”という名前をフォルダーに付けられません。" ++ ++#: createFolderDialog.js:153 ++msgid "Folders with “.” at the beginning of their name are hidden." ++msgstr "名前が“.”で始まるフォルダーは、隠しフォルダーになります。" ++ ++#: createFolderDialog.js:155 ++msgid "There is already a file or folder with that name." ++msgstr "その名前のファイルかフォルダーがすでに存在します。" ++ ++#: prefs.js:102 ++msgid "Size for the desktop icons" ++msgstr "デスクトップアイコンのサイズ" ++ ++#: prefs.js:102 ++msgid "Small" ++msgstr "小さい" ++ ++#: prefs.js:102 ++msgid "Standard" ++msgstr "標準" ++ ++#: prefs.js:102 ++msgid "Large" ++msgstr "大きい" ++ ++#: prefs.js:103 ++msgid "Show the personal folder in the desktop" ++msgstr "デスクトップにホームフォルダーを表示する" ++ ++#: prefs.js:104 ++msgid "Show the trash icon in the desktop" ++msgstr "デスクトップにゴミ箱を表示する" ++ ++#: desktopGrid.js:320 ++msgid "New Folder" ++msgstr "新しいフォルダー" ++ ++#: desktopGrid.js:322 ++msgid "Paste" ++msgstr "貼り付け" ++ ++#: desktopGrid.js:323 ++msgid "Undo" ++msgstr "元に戻す" ++ ++#: desktopGrid.js:324 ++msgid "Redo" ++msgstr "やり直す" ++ ++#: desktopGrid.js:326 ++msgid "Show Desktop in Files" ++msgstr "“ファイル”でデスクトップを表示する" ++ ++#: desktopGrid.js:327 fileItem.js:606 ++msgid "Open in Terminal" ++msgstr "端末を開く" ++ ++#: desktopGrid.js:329 ++msgid "Change Background…" ++msgstr "背景を変更する…" ++ ++#: desktopGrid.js:331 ++msgid "Display Settings" ++msgstr "ディスプレイの設定" ++ ++#: desktopGrid.js:332 ++msgid "Settings" ++msgstr "設定" ++ ++#: desktopGrid.js:576 ++msgid "Enter file name…" ++msgstr "ファイル名を入力してください…" ++ ++#: desktopGrid.js:580 ++msgid "OK" ++msgstr "OK" ++ ++#: fileItem.js:490 ++msgid "Don’t Allow Launching" ++msgstr "起動を許可しない" ++ ++#: fileItem.js:492 ++msgid "Allow Launching" ++msgstr "起動を許可する" ++ ++#: fileItem.js:574 ++msgid "Open" ++msgstr "開く" ++ ++#: fileItem.js:578 ++msgid "Open With Other Application" ++msgstr "別のアプリケーションで開く" ++ ++#: fileItem.js:582 ++msgid "Cut" ++msgstr "切り取り" ++ ++#: fileItem.js:583 ++msgid "Copy" ++msgstr "コピー" ++ ++#: fileItem.js:585 ++msgid "Rename…" ++msgstr "名前の変更…" ++ ++#: fileItem.js:586 ++msgid "Move to Trash" ++msgstr "ゴミ箱へ移動する" ++ ++#: fileItem.js:596 ++msgid "Empty Trash" ++msgstr "ゴミ箱を空にする" ++ ++#: fileItem.js:602 ++msgid "Properties" ++msgstr "プロパティ" ++ ++#: fileItem.js:604 ++msgid "Show in Files" ++msgstr "“ファイル”で表示する" ++ ++#: schemas/org.gnome.shell.extensions.desktop-icons.gschema.xml:11 ++msgid "Icon size" ++msgstr "アイコンサイズ" ++ ++#: schemas/org.gnome.shell.extensions.desktop-icons.gschema.xml:12 ++msgid "Set the size for the desktop icons." ++msgstr "デスクトップのアイコンサイズを設定します。" ++ ++#: schemas/org.gnome.shell.extensions.desktop-icons.gschema.xml:16 ++msgid "Show personal folder" ++msgstr "ホームフォルダーを表示する" ++ ++#: schemas/org.gnome.shell.extensions.desktop-icons.gschema.xml:17 ++msgid "Show the personal folder in the desktop." ++msgstr "デスクトップにホームフォルダーを表示します。" ++ ++#: schemas/org.gnome.shell.extensions.desktop-icons.gschema.xml:21 ++msgid "Show trash icon" ++msgstr "ゴミ箱アイコンを表示する" ++ ++#: schemas/org.gnome.shell.extensions.desktop-icons.gschema.xml:22 ++msgid "Show the trash icon in the desktop." ++msgstr "デスクトップにゴミ箱のアイコンを表示します。" +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/nl.po b/extensions/desktop-icons/po/nl.po +new file mode 100644 +index 0000000..b2f7dab +--- /dev/null ++++ b/extensions/desktop-icons/po/nl.po +@@ -0,0 +1,188 @@ ++# Dutch translation for desktop-icons. ++# Copyright (C) 2019 desktop-icons's COPYRIGHT HOLDER ++# This file is distributed under the same license as the desktop-icons package. ++# Nathan Follens , 2019. ++# ++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: 2019-03-01 12:11+0000\n" ++"PO-Revision-Date: 2019-03-04 19:46+0100\n" ++"Last-Translator: Nathan Follens \n" ++"Language-Team: Dutch \n" ++"Language: nl\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.1\n" ++ ++#: createFolderDialog.js:48 ++msgid "New folder name" ++msgstr "Nieuwe mapnaam" ++ ++#: createFolderDialog.js:72 ++msgid "Create" ++msgstr "Aanmaken" ++ ++#: createFolderDialog.js:74 desktopGrid.js:586 ++msgid "Cancel" ++msgstr "Annuleren" ++ ++#: createFolderDialog.js:145 ++msgid "Folder names cannot contain “/”." ++msgstr "Mapnamen kunnen geen ‘/’ bevatten." ++ ++#: createFolderDialog.js:148 ++msgid "A folder cannot be called “.”." ++msgstr "Een map kan niet ‘.’ worden genoemd." ++ ++#: createFolderDialog.js:151 ++msgid "A folder cannot be called “..”." ++msgstr "Een map kan niet ‘..’ worden genoemd." ++ ++#: createFolderDialog.js:153 ++msgid "Folders with “.” at the beginning of their name are hidden." ++msgstr "Mappen waarvan de naam begint met ‘.’ zijn verborgen." ++ ++#: createFolderDialog.js:155 ++msgid "There is already a file or folder with that name." ++msgstr "Er bestaat al een bestand of map met die naam." ++ ++#: prefs.js:102 ++msgid "Size for the desktop icons" ++msgstr "Grootte van bureaubladpictogrammen" ++ ++#: prefs.js:102 ++msgid "Small" ++msgstr "Klein" ++ ++#: prefs.js:102 ++msgid "Standard" ++msgstr "Standaard" ++ ++#: prefs.js:102 ++msgid "Large" ++msgstr "Groot" ++ ++#: prefs.js:103 ++msgid "Show the personal folder in the desktop" ++msgstr "Toon de persoonlijke map op het bureaublad" ++ ++#: prefs.js:104 ++msgid "Show the trash icon in the desktop" ++msgstr "Toon het prullenbakpictogram op het bureaublad" ++ ++#: desktopGrid.js:320 ++msgid "New Folder" ++msgstr "Nieuwe map" ++ ++#: desktopGrid.js:322 ++msgid "Paste" ++msgstr "Plakken" ++ ++#: desktopGrid.js:323 ++msgid "Undo" ++msgstr "Ongedaan maken" ++ ++#: desktopGrid.js:324 ++msgid "Redo" ++msgstr "Opnieuw" ++ ++#: desktopGrid.js:326 ++msgid "Show Desktop in Files" ++msgstr "Bureaublad tonen in Bestanden" ++ ++#: desktopGrid.js:327 fileItem.js:606 ++msgid "Open in Terminal" ++msgstr "Openen in terminalvenster" ++ ++#: desktopGrid.js:329 ++msgid "Change Background…" ++msgstr "Achtergrond aanpassen…" ++ ++#: desktopGrid.js:331 ++msgid "Display Settings" ++msgstr "Scherminstellingen" ++ ++#: desktopGrid.js:332 ++msgid "Settings" ++msgstr "Instellingen" ++ ++#: desktopGrid.js:576 ++msgid "Enter file name…" ++msgstr "Voer bestandsnaam in…" ++ ++#: desktopGrid.js:580 ++msgid "OK" ++msgstr "Oké" ++ ++#: fileItem.js:490 ++msgid "Don’t Allow Launching" ++msgstr "Toepassingen starten niet toestaan" ++ ++#: fileItem.js:492 ++msgid "Allow Launching" ++msgstr "Toepassingen starten toestaan" ++ ++#: fileItem.js:574 ++msgid "Open" ++msgstr "Openen" ++ ++#: fileItem.js:578 ++msgid "Open With Other Application" ++msgstr "Met andere toepassing openen" ++ ++#: fileItem.js:582 ++msgid "Cut" ++msgstr "Knippen" ++ ++#: fileItem.js:583 ++msgid "Copy" ++msgstr "Kopiëren" ++ ++#: fileItem.js:585 ++msgid "Rename…" ++msgstr "Hernoemen…" ++ ++#: fileItem.js:586 ++msgid "Move to Trash" ++msgstr "Verplaatsen naar prullenbak" ++ ++#: fileItem.js:596 ++msgid "Empty Trash" ++msgstr "Prullenbak legen" ++ ++#: fileItem.js:602 ++msgid "Properties" ++msgstr "Eigenschappen" ++ ++#: fileItem.js:604 ++msgid "Show in Files" ++msgstr "Tonen in Bestanden" ++ ++#: schemas/org.gnome.shell.extensions.desktop-icons.gschema.xml:11 ++msgid "Icon size" ++msgstr "Pictogramgrootte" ++ ++#: schemas/org.gnome.shell.extensions.desktop-icons.gschema.xml:12 ++msgid "Set the size for the desktop icons." ++msgstr "Stel de grootte van de bureaubladpictogrammen in." ++ ++#: schemas/org.gnome.shell.extensions.desktop-icons.gschema.xml:16 ++msgid "Show personal folder" ++msgstr "Persoonlijke map tonen" ++ ++#: schemas/org.gnome.shell.extensions.desktop-icons.gschema.xml:17 ++msgid "Show the personal folder in the desktop." ++msgstr "Toon de persoonlijke map op het bureaublad." ++ ++#: schemas/org.gnome.shell.extensions.desktop-icons.gschema.xml:21 ++msgid "Show trash icon" ++msgstr "Prullenbakpictogram tonen" ++ ++#: schemas/org.gnome.shell.extensions.desktop-icons.gschema.xml:22 ++msgid "Show the trash icon in the desktop." ++msgstr "Toon het prullenbakpictogram op het bureaublad." +diff --git a/extensions/desktop-icons/po/pl.po b/extensions/desktop-icons/po/pl.po +new file mode 100644 +index 0000000..f57b3be +--- /dev/null ++++ b/extensions/desktop-icons/po/pl.po +@@ -0,0 +1,193 @@ ++# 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-04-29 14:11+0000\n" ++"PO-Revision-Date: 2019-05-01 13:03+0200\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" ++ ++#: createFolderDialog.js:48 ++msgid "New folder name" ++msgstr "Nazwa nowego katalogu" ++ ++#: createFolderDialog.js:72 ++msgid "Create" ++msgstr "Utwórz" ++ ++#: createFolderDialog.js:74 desktopGrid.js:592 ++msgid "Cancel" ++msgstr "Anuluj" ++ ++#: createFolderDialog.js:145 ++msgid "Folder names cannot contain “/”." ++msgstr "Nazwy katalogów nie mogą zawierać znaku „/”." ++ ++#: createFolderDialog.js:148 ++msgid "A folder cannot be called “.”." ++msgstr "Katalog nie może mieć nazwy „.”." ++ ++#: createFolderDialog.js:151 ++msgid "A folder cannot be called “..”." ++msgstr "Katalog nie może mieć nazwy „..”." ++ ++#: createFolderDialog.js:153 ++msgid "Folders with “.” at the beginning of their name are hidden." ++msgstr "Katalogi z „.” na początku nazwy są ukryte." ++ ++#: createFolderDialog.js:155 ++msgid "There is already a file or folder with that name." ++msgstr "Plik lub katalog o tej nazwie już istnieje." ++ ++#: 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:323 ++msgid "New Folder" ++msgstr "Nowy katalog" ++ ++#: desktopGrid.js:325 ++msgid "Paste" ++msgstr "Wklej" ++ ++#: desktopGrid.js:326 ++msgid "Undo" ++msgstr "Cofnij" ++ ++#: desktopGrid.js:327 ++msgid "Redo" ++msgstr "Ponów" ++ ++#: desktopGrid.js:329 ++msgid "Show Desktop in Files" ++msgstr "Wyświetl pulpit w menedżerze plików" ++ ++#: desktopGrid.js:330 fileItem.js:610 ++msgid "Open in Terminal" ++msgstr "Otwórz w terminalu" ++ ++#: desktopGrid.js:332 ++msgid "Change Background…" ++msgstr "Zmień tło…" ++ ++#: desktopGrid.js:334 ++msgid "Display Settings" ++msgstr "Ustawienia ekranu" ++ ++#: desktopGrid.js:335 ++msgid "Settings" ++msgstr "Ustawienia" ++ ++#: desktopGrid.js:582 ++msgid "Enter file name…" ++msgstr "Nazwa pliku…" ++ ++#: desktopGrid.js:586 ++msgid "OK" ++msgstr "OK" ++ ++#: desktopIconsUtil.js:61 ++msgid "Command not found" ++msgstr "Nie odnaleziono polecenia" ++ ++#: fileItem.js:494 ++msgid "Don’t Allow Launching" ++msgstr "Nie zezwalaj na uruchamianie" ++ ++#: fileItem.js:496 ++msgid "Allow Launching" ++msgstr "Zezwól na uruchamianie" ++ ++#: fileItem.js:578 ++msgid "Open" ++msgstr "Otwórz" ++ ++#: fileItem.js:582 ++msgid "Open With Other Application" ++msgstr "Otwórz za pomocą innego programu" ++ ++#: fileItem.js:586 ++msgid "Cut" ++msgstr "Wytnij" ++ ++#: fileItem.js:587 ++msgid "Copy" ++msgstr "Skopiuj" ++ ++#: fileItem.js:589 ++msgid "Rename…" ++msgstr "Zmień nazwę…" ++ ++#: fileItem.js:590 ++msgid "Move to Trash" ++msgstr "Przenieś do kosza" ++ ++#: fileItem.js:600 ++msgid "Empty Trash" ++msgstr "Opróżnij kosz" ++ ++#: fileItem.js:606 ++msgid "Properties" ++msgstr "Właściwości" ++ ++#: fileItem.js:608 ++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..8d61c72 +--- /dev/null ++++ b/extensions/desktop-icons/po/pt_BR.po +@@ -0,0 +1,199 @@ ++# Brazilian Portuguese translation for desktop-icons. ++# Copyright (C) 2019 desktop-icons's COPYRIGHT HOLDER ++# This file is distributed under the same license as the desktop-icons package. ++# Enrico Nicoletto , 2018. ++# Rafael Fontenelle , 2018-2019. ++# ++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: 2019-04-29 14:11+0000\n" ++"PO-Revision-Date: 2019-04-29 17:35-0300\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: Gtranslator 3.32.0\n" ++ ++#: createFolderDialog.js:48 ++msgid "New folder name" ++msgstr "Nome da nova pasta" ++ ++#: createFolderDialog.js:72 ++msgid "Create" ++msgstr "Criar" ++ ++#: createFolderDialog.js:74 desktopGrid.js:592 ++msgid "Cancel" ++msgstr "Cancelar" ++ ++#: createFolderDialog.js:145 ++msgid "Folder names cannot contain “/”." ++msgstr "Nomes de pastas não podem conter “/”." ++ ++#: createFolderDialog.js:148 ++msgid "A folder cannot be called “.”." ++msgstr "Uma pasta não pode ser chamada “.”." ++ ++#: createFolderDialog.js:151 ++msgid "A folder cannot be called “..”." ++msgstr "Uma pasta não pode ser chamada “..”." ++ ++#: createFolderDialog.js:153 ++msgid "Folders with “.” at the beginning of their name are hidden." ++msgstr "Pastas com “.” no começo de seus nomes são ocultas." ++ ++#: createFolderDialog.js:155 ++msgid "There is already a file or folder with that name." ++msgstr "Já existe um arquivo ou uma pasta com esse nome." ++ ++#: 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: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:323 ++msgid "New Folder" ++msgstr "Nova pasta" ++ ++#: desktopGrid.js:325 ++msgid "Paste" ++msgstr "Colar" ++ ++#: desktopGrid.js:326 ++msgid "Undo" ++msgstr "Desfazer" ++ ++#: desktopGrid.js:327 ++msgid "Redo" ++msgstr "Refazer" ++ ++#: desktopGrid.js:329 ++msgid "Show Desktop in Files" ++msgstr "Mostrar a área de trabalho no Arquivos" ++ ++#: desktopGrid.js:330 fileItem.js:610 ++msgid "Open in Terminal" ++msgstr "Abrir no terminal" ++ ++#: desktopGrid.js:332 ++msgid "Change Background…" ++msgstr "Alterar plano de fundo…" ++ ++#: desktopGrid.js:334 ++msgid "Display Settings" ++msgstr "Configurações de exibição" ++ ++#: desktopGrid.js:335 ++msgid "Settings" ++msgstr "Configurações" ++ ++#: desktopGrid.js:582 ++msgid "Enter file name…" ++msgstr "Insira um nome de arquivo…" ++ ++#: desktopGrid.js:586 ++msgid "OK" ++msgstr "OK" ++ ++#: desktopIconsUtil.js:61 ++msgid "Command not found" ++msgstr "Comando não encontrado" ++ ++#: fileItem.js:494 ++msgid "Don’t Allow Launching" ++msgstr "Não permitir iniciar" ++ ++#: fileItem.js:496 ++msgid "Allow Launching" ++msgstr "Permitir iniciar" ++ ++#: fileItem.js:578 ++msgid "Open" ++msgstr "Abrir" ++ ++#: fileItem.js:582 ++msgid "Open With Other Application" ++msgstr "Abrir com outro aplicativo" ++ ++#: fileItem.js:586 ++msgid "Cut" ++msgstr "Recortar" ++ ++#: fileItem.js:587 ++msgid "Copy" ++msgstr "Copiar" ++ ++#: fileItem.js:589 ++msgid "Rename…" ++msgstr "Renomear…" ++ ++#: fileItem.js:590 ++msgid "Move to Trash" ++msgstr "Mover para a lixeira" ++ ++#: fileItem.js:600 ++msgid "Empty Trash" ++msgstr "Esvaziar lixeira" ++ ++#: fileItem.js:606 ++msgid "Properties" ++msgstr "Propriedades" ++ ++#: fileItem.js:608 ++msgid "Show in Files" ++msgstr "Mostrar no Arquivos" ++ ++#: schemas/org.gnome.shell.extensions.desktop-icons.gschema.xml:11 ++msgid "Icon size" ++msgstr "Tamanho do ícone" ++ ++#: schemas/org.gnome.shell.extensions.desktop-icons.gschema.xml:12 ++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:16 ++msgid "Show personal folder" ++msgstr "Mostrar pasta pessoal" ++ ++#: schemas/org.gnome.shell.extensions.desktop-icons.gschema.xml:17 ++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:21 ++msgid "Show trash icon" ++msgstr "Mostrar ícone da lixeira" ++ ++#: schemas/org.gnome.shell.extensions.desktop-icons.gschema.xml:22 ++msgid "Show the trash icon in the desktop." ++msgstr "Mostra o ícone da lixeira na área de trabalho." ++ ++#~ msgid "Huge" ++#~ msgstr "Enorme" ++ ++#~ 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/sv.po b/extensions/desktop-icons/po/sv.po +new file mode 100644 +index 0000000..928cbe9 +--- /dev/null ++++ b/extensions/desktop-icons/po/sv.po +@@ -0,0 +1,197 @@ ++# Swedish translation for desktop-icons. ++# Copyright © 2018, 2019 desktop-icons's COPYRIGHT HOLDER ++# This file is distributed under the same license as the desktop-icons package. ++# Anders Jonsson , 2018, 2019. ++# Josef Andersson , 2019. ++# ++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: 2019-03-01 12:11+0000\n" ++"PO-Revision-Date: 2019-03-11 21:50+0100\n" ++"Last-Translator: Anders Jonsson \n" ++"Language-Team: Swedish \n" ++"Language: sv\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.1\n" ++ ++#: createFolderDialog.js:48 ++msgid "New folder name" ++msgstr "Nytt mappnamn" ++ ++#: createFolderDialog.js:72 ++msgid "Create" ++msgstr "Skapa" ++ ++#: createFolderDialog.js:74 desktopGrid.js:586 ++msgid "Cancel" ++msgstr "Avbryt" ++ ++#: createFolderDialog.js:145 ++msgid "Folder names cannot contain “/”." ++msgstr "Mappnamn kan inte innehålla ”/”." ++ ++#: createFolderDialog.js:148 ++msgid "A folder cannot be called “.”." ++msgstr "En mapp kan inte kallas ”.”." ++ ++#: createFolderDialog.js:151 ++msgid "A folder cannot be called “..”." ++msgstr "En mapp kan inte kallas ”..”." ++ ++#: createFolderDialog.js:153 ++msgid "Folders with “.” at the beginning of their name are hidden." ++msgstr "Mappar med ”.” i början på sitt namn är dolda." ++ ++#: createFolderDialog.js:155 ++msgid "There is already a file or folder with that name." ++msgstr "Det finns redan en fil eller mapp med det namnet" ++ ++#: prefs.js:102 ++msgid "Size for the desktop icons" ++msgstr "Storlek för skrivbordsikonerna" ++ ++#: prefs.js:102 ++msgid "Small" ++msgstr "Liten" ++ ++#: prefs.js:102 ++msgid "Standard" ++msgstr "Standard" ++ ++#: prefs.js:102 ++msgid "Large" ++msgstr "Stor" ++ ++# TODO: *ON* the desktop? ++#: prefs.js:103 ++msgid "Show the personal folder in the desktop" ++msgstr "Visa den personliga mappen på skrivbordet" ++ ++# TODO: *ON* the desktop? ++#: prefs.js:104 ++msgid "Show the trash icon in the desktop" ++msgstr "Visa papperskorgsikonen på skrivbordet" ++ ++#: desktopGrid.js:320 ++msgid "New Folder" ++msgstr "Ny mapp" ++ ++#: desktopGrid.js:322 ++msgid "Paste" ++msgstr "Klistra in" ++ ++#: desktopGrid.js:323 ++msgid "Undo" ++msgstr "Ångra" ++ ++#: desktopGrid.js:324 ++msgid "Redo" ++msgstr "Gör om" ++ ++#: desktopGrid.js:326 ++msgid "Show Desktop in Files" ++msgstr "Visa skrivbord i Filer" ++ ++#: desktopGrid.js:327 fileItem.js:606 ++msgid "Open in Terminal" ++msgstr "Öppna i terminal" ++ ++#: desktopGrid.js:329 ++msgid "Change Background…" ++msgstr "Ändra bakgrund…" ++ ++#: desktopGrid.js:331 ++msgid "Display Settings" ++msgstr "Visningsinställningar" ++ ++#: desktopGrid.js:332 ++msgid "Settings" ++msgstr "Inställningar" ++ ++#: desktopGrid.js:576 ++msgid "Enter file name…" ++msgstr "Ange filnamn…" ++ ++#: desktopGrid.js:580 ++msgid "OK" ++msgstr "OK" ++ ++#: fileItem.js:490 ++msgid "Don’t Allow Launching" ++msgstr "Tillåt ej programstart" ++ ++#: fileItem.js:492 ++msgid "Allow Launching" ++msgstr "Tillåt programstart" ++ ++#: fileItem.js:574 ++msgid "Open" ++msgstr "Öppna" ++ ++#: fileItem.js:578 ++msgid "Open With Other Application" ++msgstr "Öppna med annat program" ++ ++#: fileItem.js:582 ++msgid "Cut" ++msgstr "Klipp ut" ++ ++#: fileItem.js:583 ++msgid "Copy" ++msgstr "Kopiera" ++ ++#: fileItem.js:585 ++msgid "Rename…" ++msgstr "Byt namn…" ++ ++#: fileItem.js:586 ++msgid "Move to Trash" ++msgstr "Flytta till papperskorgen" ++ ++#: fileItem.js:596 ++msgid "Empty Trash" ++msgstr "Töm papperskorgen" ++ ++#: fileItem.js:602 ++msgid "Properties" ++msgstr "Egenskaper" ++ ++#: fileItem.js:604 ++msgid "Show in Files" ++msgstr "Visa i Filer" ++ ++#: schemas/org.gnome.shell.extensions.desktop-icons.gschema.xml:11 ++msgid "Icon size" ++msgstr "Ikonstorlek" ++ ++# TODO: *ON* the desktop? ++#: schemas/org.gnome.shell.extensions.desktop-icons.gschema.xml:12 ++msgid "Set the size for the desktop icons." ++msgstr "Ställ in storleken för skrivbordsikonerna." ++ ++#: schemas/org.gnome.shell.extensions.desktop-icons.gschema.xml:16 ++msgid "Show personal folder" ++msgstr "Visa personlig mapp" ++ ++# TODO: *ON* the desktop? ++#: schemas/org.gnome.shell.extensions.desktop-icons.gschema.xml:17 ++msgid "Show the personal folder in the desktop." ++msgstr "Visa den personliga mappen på skrivbordet." ++ ++#: schemas/org.gnome.shell.extensions.desktop-icons.gschema.xml:21 ++msgid "Show trash icon" ++msgstr "Visa papperskorgsikon" ++ ++# TODO: *ON* the desktop? ++#: schemas/org.gnome.shell.extensions.desktop-icons.gschema.xml:22 ++msgid "Show the trash icon in the desktop." ++msgstr "Visa papperskorgsikonen på skrivbordet." ++ ++#~ msgid "Huge" ++#~ msgstr "Enorm" +diff --git a/extensions/desktop-icons/po/tr.po b/extensions/desktop-icons/po/tr.po +new file mode 100644 +index 0000000..2e5f5a7 +--- /dev/null ++++ b/extensions/desktop-icons/po/tr.po +@@ -0,0 +1,191 @@ ++# Turkish translation for desktop-icons. ++# Copyright (C) 2000-2019 desktop-icons's COPYRIGHT HOLDER ++# This file is distributed under the same license as the desktop-icons package. ++# ++# Sabri Ünal , 2019. ++# Serdar Sağlam , 2019 ++# Emin Tufan Çetin , 2019. ++# ++msgid "" ++msgstr "" ++"Project-Id-Version: \n" ++"Report-Msgid-Bugs-To: https://gitlab.gnome.org/World/ShellExtensions/desktop-" ++"icons/issues\n" ++"POT-Creation-Date: 2019-03-09 14:47+0000\n" ++"PO-Revision-Date: 2019-03-13 13:43+0300\n" ++"Last-Translator: Emin Tufan Çetin \n" ++"Language-Team: Türkçe \n" ++"Language: tr\n" ++"MIME-Version: 1.0\n" ++"Content-Type: text/plain; charset=UTF-8\n" ++"Content-Transfer-Encoding: 8bit\n" ++"X-Generator: Gtranslator 3.30.1\n" ++"Plural-Forms: nplurals=1; plural=0;\n" ++ ++#: createFolderDialog.js:48 ++msgid "New folder name" ++msgstr "Yeni klasör adı" ++ ++#: createFolderDialog.js:72 ++msgid "Create" ++msgstr "Oluştur" ++ ++#: createFolderDialog.js:74 desktopGrid.js:586 ++msgid "Cancel" ++msgstr "İptal" ++ ++#: createFolderDialog.js:145 ++msgid "Folder names cannot contain “/”." ++msgstr "Klasör adları “/” içeremez." ++ ++#: createFolderDialog.js:148 ++msgid "A folder cannot be called “.”." ++msgstr "Bir klasör “.” olarak adlandırılamaz." ++ ++#: createFolderDialog.js:151 ++msgid "A folder cannot be called “..”." ++msgstr "Bir klasör “..” olarak adlandırılamaz." ++ ++#: createFolderDialog.js:153 ++msgid "Folders with “.” at the beginning of their name are hidden." ++msgstr "Adlarının başında “.” bulunan klasörler gizlenir." ++ ++#: createFolderDialog.js:155 ++msgid "There is already a file or folder with that name." ++msgstr "Zaten bu adda bir dosya veya klasör var." ++ ++#: prefs.js:102 ++msgid "Size for the desktop icons" ++msgstr "Masaüstü simgeleri boyutu" ++ ++#: prefs.js:102 ++msgid "Small" ++msgstr "Küçük" ++ ++#: prefs.js:102 ++msgid "Standard" ++msgstr "Standart" ++ ++#: prefs.js:102 ++msgid "Large" ++msgstr "Büyük" ++ ++#: prefs.js:103 ++msgid "Show the personal folder in the desktop" ++msgstr "Kişisel klasörü masaüstünde göster" ++ ++#: prefs.js:104 ++msgid "Show the trash icon in the desktop" ++msgstr "Çöp kutusunu masaüstünde göster" ++ ++#: desktopGrid.js:320 ++msgid "New Folder" ++msgstr "Yeni Klasör" ++ ++#: desktopGrid.js:322 ++msgid "Paste" ++msgstr "Yapıştır" ++ ++#: desktopGrid.js:323 ++msgid "Undo" ++msgstr "Geri Al" ++ ++#: desktopGrid.js:324 ++msgid "Redo" ++msgstr "Yinele" ++ ++#: desktopGrid.js:326 ++msgid "Show Desktop in Files" ++msgstr "Masaüstünü Dosyalarʼda Göster" ++ ++#: desktopGrid.js:327 fileItem.js:606 ++msgid "Open in Terminal" ++msgstr "Uçbirimde Aç" ++ ++#: desktopGrid.js:329 ++msgid "Change Background…" ++msgstr "Arka Planı Değiştir…" ++ ++#: desktopGrid.js:331 ++msgid "Display Settings" ++msgstr "Görüntü Ayarları" ++ ++#: desktopGrid.js:332 ++msgid "Settings" ++msgstr "Ayarlar" ++ ++#: desktopGrid.js:576 ++msgid "Enter file name…" ++msgstr "Dosya adını gir…" ++ ++#: desktopGrid.js:580 ++msgid "OK" ++msgstr "Tamam" ++ ++#: fileItem.js:490 ++msgid "Don’t Allow Launching" ++msgstr "Başlatmaya İzin Verme" ++ ++#: fileItem.js:492 ++msgid "Allow Launching" ++msgstr "Başlatmaya İzin Ver" ++ ++#: fileItem.js:574 ++msgid "Open" ++msgstr "Aç" ++ ++#: fileItem.js:578 ++msgid "Open With Other Application" ++msgstr "Başka Uygulamayla Aç" ++ ++#: fileItem.js:582 ++msgid "Cut" ++msgstr "Kes" ++ ++#: fileItem.js:583 ++msgid "Copy" ++msgstr "Kopyala" ++ ++#: fileItem.js:585 ++msgid "Rename…" ++msgstr "Yeniden Adlandır…" ++ ++#: fileItem.js:586 ++msgid "Move to Trash" ++msgstr "Çöpe Taşı" ++ ++#: fileItem.js:596 ++msgid "Empty Trash" ++msgstr "Çöpü Boşalt" ++ ++#: fileItem.js:602 ++msgid "Properties" ++msgstr "Özellikler" ++ ++#: fileItem.js:604 ++msgid "Show in Files" ++msgstr "Dosyalarʼda Göster" ++ ++#: schemas/org.gnome.shell.extensions.desktop-icons.gschema.xml:11 ++msgid "Icon size" ++msgstr "Simge boyutu" ++ ++#: schemas/org.gnome.shell.extensions.desktop-icons.gschema.xml:12 ++msgid "Set the size for the desktop icons." ++msgstr "Masaüstü simgelerinin boyutunu ayarla." ++ ++#: schemas/org.gnome.shell.extensions.desktop-icons.gschema.xml:16 ++msgid "Show personal folder" ++msgstr "Kişisel klasörü göster" ++ ++#: schemas/org.gnome.shell.extensions.desktop-icons.gschema.xml:17 ++msgid "Show the personal folder in the desktop." ++msgstr "Kişisel klasörü masaüstünde göster." ++ ++#: schemas/org.gnome.shell.extensions.desktop-icons.gschema.xml:21 ++msgid "Show trash icon" ++msgstr "Çöp kutusunu göster" ++ ++#: schemas/org.gnome.shell.extensions.desktop-icons.gschema.xml:22 ++msgid "Show the trash icon in the desktop." ++msgstr "Çöp kutusu simgesini masaüstünde göster." +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..4b8d986 +--- /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() { ++ initTranslations(); ++ 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..17d4b32 +--- /dev/null ++++ b/extensions/desktop-icons/stylesheet.css +@@ -0,0 +1,38 @@ ++.file-item { ++ padding: 4px; ++ border: 1px; ++ margin: 1px; ++} ++ ++.file-item:hover { ++ background-color: rgba(238, 238, 238, 0.2); ++} ++ ++.name-label { ++ text-shadow: 1px 1px black; ++ color: white; ++ text-align: center; ++} ++ ++.draggable { ++ background-color: red; ++} ++ ++.rename-popup { ++ min-width: 300px; ++ margin: 6px; ++} ++ ++.create-folder-dialog-entry { ++ width: 20em; ++ margin-bottom: 6px; ++} ++ ++.create-folder-dialog-label { ++ padding-bottom: .4em; ++} ++ ++.create-folder-dialog-error-box { ++ padding-top: 16px; ++ spacing: 6px; ++} +diff --git a/meson.build b/meson.build +index cf855a0..6e8c41f 100644 +--- a/meson.build ++++ b/meson.build +@@ -33,6 +33,7 @@ uuid_suffix = '@gnome-shell-extensions.gcampax.github.com' + + classic_extensions = [ + '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 6825d0d..a61cc76 100644 +--- a/po/fr.po ++++ b/po/fr.po +@@ -1,3 +1,5 @@ ++# #-#-#-#-# 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. +@@ -5,8 +7,25 @@ + # Alain Lojewski , 2012-2013. + # 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. ++# ++# #-#-#-#-# 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://gitlab.gnome.org/GNOME/gnome-shell-extensions/" + "issues\n" +@@ -19,6 +38,33 @@ 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 +@@ -354,9 +400,151 @@ msgstr "Espace de travail %d" + #~ msgstr "" + #~ "Retarder les changements de focus en mode souris jusqu’à ce que le " + #~ "pointeur arrête de bouger" ++#: 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.21.0 + 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/more-classic-classic-mode.patch b/SOURCES/more-classic-classic-mode.patch new file mode 100644 index 0000000..dde41ef --- /dev/null +++ b/SOURCES/more-classic-classic-mode.patch @@ -0,0 +1,2317 @@ +From a19128ef885d8a933af364651b21e519f583f82a Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Florian=20M=C3=BCllner?= +Date: Wed, 29 May 2019 10:17:20 +0000 +Subject: [PATCH 01/22] places-menu: Don't hardcode position + +The extension currently assumes that we have the "Activities" button +at the left of the top bar. This is currently true, not only in the +regular session, but also in GNOME classic where the button is hidden +(but still present). + +However this is about to change: We will stop taking over the button +from the apps-menu extension, and instead disable "Activities" from +the session mode definition. + +Prepare for this by adding the places menu before the application menu +instead of assuming a hardcoded position. + +https://gitlab.gnome.org/GNOME/gnome-shell-extensions/merge_requests/69 +--- + extensions/places-menu/extension.js | 4 ++-- + 1 file changed, 2 insertions(+), 2 deletions(-) + +diff --git a/extensions/places-menu/extension.js b/extensions/places-menu/extension.js +index c477a4a..5c038ae 100644 +--- a/extensions/places-menu/extension.js ++++ b/extensions/places-menu/extension.js +@@ -135,9 +135,9 @@ let _indicator; + function enable() { + _indicator = new PlacesMenu; + +- let pos = 1; ++ let pos = Main.sessionMode.panel.left.indexOf('appMenu'); + if ('apps-menu' in Main.panel.statusArea) +- pos = 2; ++ pos++; + Main.panel.addToStatusArea('places-menu', _indicator, pos, 'left'); + } + +-- +2.21.0 + + +From 075f6d45b242061be774ba5d3d8c2236cacd64c5 Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Florian=20M=C3=BCllner?= +Date: Wed, 29 May 2019 08:32:03 +0000 +Subject: [PATCH 02/22] apps-menu: Stop taking over Activities button + +We don't want the "Activities" button in GNOME Classic, but the current +way of handling it is confusing: + + - the button is hidden, but the corresponding hot corner + sometimes works (when the application menu isn't open) + + - the button is effectively moved inside the menu, although + it's clearly not an app or category + + - the apps-menu can be used independent from classic mode, in + which case removing the "Activities" button may not be wanted + +Address those points by removing any handling of the activities button +from the apps-menu extension. We will remove it again from the classic +session via a session mode tweak. + +https://gitlab.gnome.org/GNOME/gnome-shell-extensions/merge_requests/69 +--- + extensions/apps-menu/extension.js | 67 +++---------------------------- + 1 file changed, 6 insertions(+), 61 deletions(-) + +diff --git a/extensions/apps-menu/extension.js b/extensions/apps-menu/extension.js +index cc399c6..d6c9c8b 100644 +--- a/extensions/apps-menu/extension.js ++++ b/extensions/apps-menu/extension.js +@@ -25,22 +25,6 @@ const NAVIGATION_REGION_OVERSHOOT = 50; + Gio._promisify(Gio._LocalFilePrototype, 'query_info_async', 'query_info_finish'); + Gio._promisify(Gio._LocalFilePrototype, 'set_attributes_async', 'set_attributes_finish'); + +-class ActivitiesMenuItem extends PopupMenu.PopupBaseMenuItem { +- constructor(button) { +- super(); +- this._button = button; +- let label = new St.Label({ text: _('Activities Overview') }); +- this.actor.add_child(label); +- this.actor.label_actor = label; +- } +- +- activate(event) { +- this._button.menu.toggle(); +- Main.overview.toggle(); +- super.activate(event); +- } +-} +- + class ApplicationMenuItem extends PopupMenu.PopupBaseMenuItem { + constructor(button, app) { + super(); +@@ -233,21 +217,6 @@ class ApplicationsMenu extends PopupMenu.PopupMenu { + return false; + } + +- open(animate) { +- this._button.hotCorner.setBarrierSize(0); +- if (this._button.hotCorner.actor) // fallback corner +- this._button.hotCorner.actor.hide(); +- super.open(animate); +- } +- +- close(animate) { +- let size = Main.layoutManager.panelBox.height; +- this._button.hotCorner.setBarrierSize(size); +- if (this._button.hotCorner.actor) // fallback corner +- this._button.hotCorner.actor.show(); +- super.close(animate); +- } +- + toggle() { + if (this.isOpen) { + this._button.selectCategory(null); +@@ -381,7 +350,7 @@ Signals.addSignalMethods(DesktopTarget.prototype); + + let ApplicationsButton = GObject.registerClass( + class ApplicationsButton extends PanelMenu.Button { +- _init() { ++ _init(includeIcon) { + super._init(1.0, null, false); + + this.setMenu(new ApplicationsMenu(this, 1.0, St.Side.TOP, this)); +@@ -398,7 +367,8 @@ class ApplicationsButton extends PanelMenu.Button { + '/usr/share/icons/hicolor/scalable/apps/start-here.svg'); + this._icon = new St.Icon({ + gicon: new Gio.FileIcon({ file: iconFile }), +- style_class: 'panel-logo-icon' ++ style_class: 'panel-logo-icon', ++ visible: includeIcon + }); + hbox.add_actor(this._icon); + +@@ -414,8 +384,6 @@ class ApplicationsButton extends PanelMenu.Button { + this.name = 'panelApplications'; + this.label_actor = this._label; + +- this.connect('captured-event', this._onCapturedEvent.bind(this)); +- + this._showingId = Main.overview.connect('showing', () => { + this.add_accessible_state (Atk.StateType.CHECKED); + }); +@@ -457,10 +425,6 @@ class ApplicationsButton extends PanelMenu.Button { + } + } + +- get hotCorner() { +- return Main.layoutManager.hotCorners[Main.layoutManager.primaryIndex]; +- } +- + _createVertSeparator() { + let separator = new St.DrawingArea({ + style_class: 'calendar-vertical-separator', +@@ -487,14 +451,6 @@ class ApplicationsButton extends PanelMenu.Button { + this._desktopTarget.destroy(); + } + +- _onCapturedEvent(actor, event) { +- if (event.type() == Clutter.EventType.BUTTON_PRESS) { +- if (!Main.overview.shouldToggleByCornerOrButton()) +- return true; +- } +- return false; +- } +- + _onMenuKeyPress(actor, event) { + let symbol = event.get_key_symbol(); + if (symbol == Clutter.KEY_Left || symbol == Clutter.KEY_Right) { +@@ -638,14 +594,6 @@ class ApplicationsButton extends PanelMenu.Button { + y_align: St.Align.START + }); + +- let activities = new ActivitiesMenuItem(this); +- this.leftBox.add(activities.actor, { +- expand: false, +- x_fill: true, +- y_fill: false, +- y_align: St.Align.START +- }); +- + this.applicationsBox = new St.BoxLayout({ vertical: true }); + this.applicationsScrollBox.add_actor(this.applicationsBox); + this.categoriesBox = new St.BoxLayout({ vertical: true }); +@@ -757,19 +705,16 @@ class ApplicationsButton extends PanelMenu.Button { + }); + + let appsMenuButton; +-let activitiesButton; + + function enable() { +- activitiesButton = Main.panel.statusArea['activities']; +- activitiesButton.container.hide(); +- appsMenuButton = new ApplicationsButton(); +- Main.panel.addToStatusArea('apps-menu', appsMenuButton, 1, 'left'); ++ let index = Main.sessionMode.panel.left.indexOf('activities') + 1; ++ appsMenuButton = new ApplicationsButton(index == 0); ++ Main.panel.addToStatusArea('apps-menu', appsMenuButton, index, 'left'); + } + + function disable() { + Main.panel.menuManager.removeMenu(appsMenuButton.menu); + appsMenuButton.destroy(); +- activitiesButton.container.show(); + } + + function init() { +-- +2.21.0 + + +From e8b2f5206b659398931cfc961954b5d744d52728 Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Florian=20M=C3=BCllner?= +Date: Fri, 7 Jun 2019 14:30:16 +0000 +Subject: [PATCH 03/22] apps-menu: Stop hiding the overview when toggled + +Now that the extension no longer doubles as the "Activities" button, +that behavior is confusing. + +https://gitlab.gnome.org/GNOME/gnome-shell-extensions/merge_requests/69 +--- + extensions/apps-menu/extension.js | 6 +----- + 1 file changed, 1 insertion(+), 5 deletions(-) + +diff --git a/extensions/apps-menu/extension.js b/extensions/apps-menu/extension.js +index d6c9c8b..76bcd1a 100644 +--- a/extensions/apps-menu/extension.js ++++ b/extensions/apps-menu/extension.js +@@ -218,12 +218,8 @@ class ApplicationsMenu extends PopupMenu.PopupMenu { + } + + toggle() { +- if (this.isOpen) { ++ if (this.isOpen) + this._button.selectCategory(null); +- } else { +- if (Main.overview.visible) +- Main.overview.hide(); +- } + super.toggle(); + } + } +-- +2.21.0 + + +From bcd2c97e89bc90675525dbd05207ae25a91d39df Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Florian=20M=C3=BCllner?= +Date: Fri, 7 Jun 2019 20:07:19 +0000 +Subject: [PATCH 04/22] apps-menu: Hide overview when launching app + +Now that we no longer hide the overview when the menu is opened, +it is possible to activate menu entries from the overview. Start +hiding the overview in that case, which is consistent with app +launching elsewhere. + +https://gitlab.gnome.org/GNOME/gnome-shell-extensions/merge_requests/69 +--- + extensions/apps-menu/extension.js | 2 ++ + 1 file changed, 2 insertions(+) + +diff --git a/extensions/apps-menu/extension.js b/extensions/apps-menu/extension.js +index 76bcd1a..b9e7111 100644 +--- a/extensions/apps-menu/extension.js ++++ b/extensions/apps-menu/extension.js +@@ -66,6 +66,8 @@ class ApplicationMenuItem extends PopupMenu.PopupBaseMenuItem { + this._button.selectCategory(null); + this._button.menu.toggle(); + super.activate(event); ++ ++ Main.overview.hide(); + } + + setActive(active, params) { +-- +2.21.0 + + +From c118895cff4cedfb43f0ae875d5cd355f66f0db7 Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Florian=20M=C3=BCllner?= +Date: Wed, 29 May 2019 09:44:30 +0000 +Subject: [PATCH 05/22] classic: Disable overview + +The overview is one of the defining features of GNOME 3, and thus +almost by definition at odds with the classic session, which +emulates a traditional GNOME 2 desktop. + +Even with the less prominent placement inside the application menu +it never quite fit in - it doesn't help that besides the different +UI paradigma, the overview keeps its "normal" styling which differs +greatly with classic's normal mode. + +So besides removing the "Activities" button via the session mode +definition, now that the apps-menu extension doesn't replace it anymore, +disable the overview completely in the classic session. + +https://gitlab.gnome.org/GNOME/gnome-shell-extensions/merge_requests/69 +--- + data/classic.json.in | 3 ++- + 1 file changed, 2 insertions(+), 1 deletion(-) + +diff --git a/data/classic.json.in b/data/classic.json.in +index fdb3762..c1c0544 100644 +--- a/data/classic.json.in ++++ b/data/classic.json.in +@@ -1,8 +1,9 @@ + { + "parentMode": "user", + "stylesheetName": "gnome-classic.css", ++ "hasOverview": false, + "enabledExtensions": [@CLASSIC_EXTENSIONS@], +- "panel": { "left": ["activities", "appMenu"], ++ "panel": { "left": ["appMenu"], + "center": [], + "right": ["a11y", "keyboard", "dateMenu", "aggregateMenu"] + } +-- +2.21.0 + + +From 004affc147579e2b1a11614cd0c8faf99e307999 Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Florian=20M=C3=BCllner?= +Date: Wed, 5 Jun 2019 00:23:13 +0000 +Subject: [PATCH 06/22] window-list: Split out workspaceIndicator + +The extension has grown unwieldily big, so before starting to improve +on the workspace indicator, move it to its own source file. + +https://gitlab.gnome.org/GNOME/gnome-shell-extensions/merge_requests/70 +--- + extensions/window-list/extension.js | 130 +----------------- + extensions/window-list/meson.build | 2 +- + extensions/window-list/workspaceIndicator.js | 135 +++++++++++++++++++ + 3 files changed, 138 insertions(+), 129 deletions(-) + create mode 100644 extensions/window-list/workspaceIndicator.js + +diff --git a/extensions/window-list/extension.js b/extensions/window-list/extension.js +index e1ea742..5275d9c 100644 +--- a/extensions/window-list/extension.js ++++ b/extensions/window-list/extension.js +@@ -1,13 +1,13 @@ + /* exported init */ +-const { Clutter, Gio, GLib, GObject, Gtk, Meta, Shell, St } = imports.gi; ++const { Clutter, Gio, GLib, Gtk, Meta, Shell, St } = imports.gi; + + const DND = imports.ui.dnd; + const Main = imports.ui.main; +-const PanelMenu = imports.ui.panelMenu; + const PopupMenu = imports.ui.popupMenu; + + const ExtensionUtils = imports.misc.extensionUtils; + const Me = ExtensionUtils.getCurrentExtension(); ++const { WorkspaceIndicator } = Me.imports.workspaceIndicator; + + const Gettext = imports.gettext.domain('gnome-shell-extensions'); + const _ = Gettext.gettext; +@@ -644,132 +644,6 @@ class AppButton extends BaseButton { + } + + +-let WorkspaceIndicator = GObject.registerClass( +-class WorkspaceIndicator extends PanelMenu.Button { +- _init() { +- super._init(0.0, _('Workspace Indicator'), true); +- this.setMenu(new PopupMenu.PopupMenu(this, 0.0, St.Side.BOTTOM)); +- this.add_style_class_name('window-list-workspace-indicator'); +- this.menu.actor.remove_style_class_name('panel-menu'); +- +- let container = new St.Widget({ +- layout_manager: new Clutter.BinLayout(), +- x_expand: true, +- y_expand: true +- }); +- this.add_actor(container); +- +- let workspaceManager = global.workspace_manager; +- +- this._currentWorkspace = workspaceManager.get_active_workspace().index(); +- this.statusLabel = new St.Label({ +- text: this._getStatusText(), +- x_align: Clutter.ActorAlign.CENTER, +- y_align: Clutter.ActorAlign.CENTER +- }); +- container.add_actor(this.statusLabel); +- +- this.workspacesItems = []; +- +- this._workspaceManagerSignals = []; +- this._workspaceManagerSignals.push(workspaceManager.connect('notify::n-workspaces', +- this._updateMenu.bind(this))); +- this._workspaceManagerSignals.push(workspaceManager.connect_after('workspace-switched', +- this._updateIndicator.bind(this))); +- +- this.connect('scroll-event', this._onScrollEvent.bind(this)); +- this._updateMenu(); +- +- this._settings = new Gio.Settings({ schema_id: 'org.gnome.desktop.wm.preferences' }); +- this._settingsChangedId = +- this._settings.connect('changed::workspace-names', +- this._updateMenu.bind(this)); +- } +- +- _onDestroy() { +- for (let i = 0; i < this._workspaceManagerSignals.length; i++) +- global.workspace_manager.disconnect(this._workspaceManagerSignals[i]); +- +- if (this._settingsChangedId) { +- this._settings.disconnect(this._settingsChangedId); +- this._settingsChangedId = 0; +- } +- +- super._onDestroy(); +- } +- +- _updateIndicator() { +- this.workspacesItems[this._currentWorkspace].setOrnament(PopupMenu.Ornament.NONE); +- this._currentWorkspace = global.workspace_manager.get_active_workspace().index(); +- this.workspacesItems[this._currentWorkspace].setOrnament(PopupMenu.Ornament.DOT); +- +- this.statusLabel.set_text(this._getStatusText()); +- } +- +- _getStatusText() { +- let workspaceManager = global.workspace_manager; +- let current = workspaceManager.get_active_workspace().index(); +- let total = workspaceManager.n_workspaces; +- +- return '%d / %d'.format(current + 1, total); +- } +- +- _updateMenu() { +- let workspaceManager = global.workspace_manager; +- +- this.menu.removeAll(); +- this.workspacesItems = []; +- this._currentWorkspace = workspaceManager.get_active_workspace().index(); +- +- for (let i = 0; i < workspaceManager.n_workspaces; i++) { +- let name = Meta.prefs_get_workspace_name(i); +- let item = new PopupMenu.PopupMenuItem(name); +- item.workspaceId = i; +- +- item.connect('activate', (item, _event) => { +- this._activate(item.workspaceId); +- }); +- +- if (i == this._currentWorkspace) +- item.setOrnament(PopupMenu.Ornament.DOT); +- +- this.menu.addMenuItem(item); +- this.workspacesItems[i] = item; +- } +- +- this.statusLabel.set_text(this._getStatusText()); +- } +- +- _activate(index) { +- let workspaceManager = global.workspace_manager; +- +- if (index >= 0 && index < workspaceManager.n_workspaces) { +- let metaWorkspace = workspaceManager.get_workspace_by_index(index); +- metaWorkspace.activate(global.get_current_time()); +- } +- } +- +- _onScrollEvent(actor, event) { +- let direction = event.get_scroll_direction(); +- let diff = 0; +- if (direction == Clutter.ScrollDirection.DOWN) { +- diff = 1; +- } else if (direction == Clutter.ScrollDirection.UP) { +- diff = -1; +- } else { +- return; +- } +- +- let newIndex = this._currentWorkspace + diff; +- this._activate(newIndex); +- } +- +- _allocate(actor, box, flags) { +- if (actor.get_n_children() > 0) +- actor.get_first_child().allocate(box, flags); +- } +-}); +- + class WindowList { + constructor(perMonitor, monitor) { + this._perMonitor = perMonitor; +diff --git a/extensions/window-list/meson.build b/extensions/window-list/meson.build +index b4aa4db..74025f9 100644 +--- a/extensions/window-list/meson.build ++++ b/extensions/window-list/meson.build +@@ -4,7 +4,7 @@ extension_data += configure_file( + configuration: metadata_conf + ) + +-extension_sources += files('prefs.js') ++extension_sources += files('prefs.js', 'workspaceIndicator.js') + extension_schemas += files(metadata_conf.get('gschemaname') + '.gschema.xml') + + if classic_mode_enabled +diff --git a/extensions/window-list/workspaceIndicator.js b/extensions/window-list/workspaceIndicator.js +new file mode 100644 +index 0000000..fb3ffe7 +--- /dev/null ++++ b/extensions/window-list/workspaceIndicator.js +@@ -0,0 +1,135 @@ ++/* exported WorkspaceIndicator */ ++const { Clutter, Gio, GObject, Meta, St } = imports.gi; ++ ++const PanelMenu = imports.ui.panelMenu; ++const PopupMenu = imports.ui.popupMenu; ++ ++const Gettext = imports.gettext.domain('gnome-shell-extensions'); ++const _ = Gettext.gettext; ++ ++var WorkspaceIndicator = GObject.registerClass( ++class WorkspaceIndicator extends PanelMenu.Button { ++ _init() { ++ super._init(0.0, _('Workspace Indicator'), true); ++ this.setMenu(new PopupMenu.PopupMenu(this, 0.0, St.Side.BOTTOM)); ++ this.add_style_class_name('window-list-workspace-indicator'); ++ this.menu.actor.remove_style_class_name('panel-menu'); ++ ++ let container = new St.Widget({ ++ layout_manager: new Clutter.BinLayout(), ++ x_expand: true, ++ y_expand: true ++ }); ++ this.add_actor(container); ++ ++ let workspaceManager = global.workspace_manager; ++ ++ this._currentWorkspace = workspaceManager.get_active_workspace().index(); ++ this.statusLabel = new St.Label({ ++ text: this._getStatusText(), ++ x_align: Clutter.ActorAlign.CENTER, ++ y_align: Clutter.ActorAlign.CENTER ++ }); ++ container.add_actor(this.statusLabel); ++ ++ this.workspacesItems = []; ++ ++ this._workspaceManagerSignals = [ ++ workspaceManager.connect('notify::n-workspaces', ++ this._updateMenu.bind(this)), ++ workspaceManager.connect_after('workspace-switched', ++ this._updateIndicator.bind(this)) ++ ]; ++ ++ this.connect('scroll-event', this._onScrollEvent.bind(this)); ++ this._updateMenu(); ++ ++ this._settings = new Gio.Settings({ schema_id: 'org.gnome.desktop.wm.preferences' }); ++ this._settingsChangedId = this._settings.connect( ++ 'changed::workspace-names', this._updateMenu.bind(this)); ++ } ++ ++ _onDestroy() { ++ for (let i = 0; i < this._workspaceManagerSignals.length; i++) ++ global.workspace_manager.disconnect(this._workspaceManagerSignals[i]); ++ ++ if (this._settingsChangedId) { ++ this._settings.disconnect(this._settingsChangedId); ++ this._settingsChangedId = 0; ++ } ++ ++ super._onDestroy(); ++ } ++ ++ _updateIndicator() { ++ this.workspacesItems[this._currentWorkspace].setOrnament(PopupMenu.Ornament.NONE); ++ this._currentWorkspace = global.workspace_manager.get_active_workspace().index(); ++ this.workspacesItems[this._currentWorkspace].setOrnament(PopupMenu.Ornament.DOT); ++ ++ this.statusLabel.set_text(this._getStatusText()); ++ } ++ ++ _getStatusText() { ++ let workspaceManager = global.workspace_manager; ++ let current = workspaceManager.get_active_workspace().index(); ++ let total = workspaceManager.n_workspaces; ++ ++ return '%d / %d'.format(current + 1, total); ++ } ++ ++ _updateMenu() { ++ let workspaceManager = global.workspace_manager; ++ ++ this.menu.removeAll(); ++ this.workspacesItems = []; ++ this._currentWorkspace = workspaceManager.get_active_workspace().index(); ++ ++ for (let i = 0; i < workspaceManager.n_workspaces; i++) { ++ let name = Meta.prefs_get_workspace_name(i); ++ let item = new PopupMenu.PopupMenuItem(name); ++ item.workspaceId = i; ++ ++ item.connect('activate', (item, _event) => { ++ this._activate(item.workspaceId); ++ }); ++ ++ if (i == this._currentWorkspace) ++ item.setOrnament(PopupMenu.Ornament.DOT); ++ ++ this.menu.addMenuItem(item); ++ this.workspacesItems[i] = item; ++ } ++ ++ this.statusLabel.set_text(this._getStatusText()); ++ } ++ ++ _activate(index) { ++ let workspaceManager = global.workspace_manager; ++ ++ if (index >= 0 && index < workspaceManager.n_workspaces) { ++ let metaWorkspace = workspaceManager.get_workspace_by_index(index); ++ metaWorkspace.activate(global.get_current_time()); ++ } ++ } ++ ++ _onScrollEvent(actor, event) { ++ let direction = event.get_scroll_direction(); ++ let diff = 0; ++ if (direction == Clutter.ScrollDirection.DOWN) { ++ diff = 1; ++ } else if (direction == Clutter.ScrollDirection.UP) { ++ diff = -1; ++ } else { ++ return; ++ } ++ ++ let newIndex = this._currentWorkspace + diff; ++ this._activate(newIndex); ++ } ++ ++ _allocate(actor, box, flags) { ++ if (actor.get_n_children() > 0) ++ actor.get_first_child().allocate(box, flags); ++ } ++}); ++ +-- +2.21.0 + + +From cceda34bfcd3de806a7b72bb532e1f8a248accee Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Florian=20M=C3=BCllner?= +Date: Wed, 5 Jun 2019 17:39:53 +0000 +Subject: [PATCH 07/22] window-list: Use a more specific GTypeName for + workspace indicator + +Now that the class inherits from GObject, the generic name easily +conflicts with other classes otherwise, for example with the one +from the workspace-indicator extension. + +https://gitlab.gnome.org/GNOME/gnome-shell-extensions/merge_requests/70 +--- + extensions/window-list/workspaceIndicator.js | 5 +++-- + 1 file changed, 3 insertions(+), 2 deletions(-) + +diff --git a/extensions/window-list/workspaceIndicator.js b/extensions/window-list/workspaceIndicator.js +index fb3ffe7..8ac43eb 100644 +--- a/extensions/window-list/workspaceIndicator.js ++++ b/extensions/window-list/workspaceIndicator.js +@@ -7,8 +7,9 @@ const PopupMenu = imports.ui.popupMenu; + const Gettext = imports.gettext.domain('gnome-shell-extensions'); + const _ = Gettext.gettext; + +-var WorkspaceIndicator = GObject.registerClass( +-class WorkspaceIndicator extends PanelMenu.Button { ++var WorkspaceIndicator = GObject.registerClass({ ++ GTypeName: 'WindowListWorkspaceIndicator' ++}, class WorkspaceIndicator extends PanelMenu.Button { + _init() { + super._init(0.0, _('Workspace Indicator'), true); + this.setMenu(new PopupMenu.PopupMenu(this, 0.0, St.Side.BOTTOM)); +-- +2.21.0 + + +From 57a97bef2e2cfb88324e9d1540254dca8f5195e3 Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Florian=20M=C3=BCllner?= +Date: Wed, 5 Jun 2019 04:54:50 +0200 +Subject: [PATCH 08/22] window-list: Make some properties private + +There's no reason why they should be public. + +https://gitlab.gnome.org/GNOME/gnome-shell-extensions/merge_requests/70 +--- + extensions/window-list/workspaceIndicator.js | 18 +++++++++--------- + 1 file changed, 9 insertions(+), 9 deletions(-) + +diff --git a/extensions/window-list/workspaceIndicator.js b/extensions/window-list/workspaceIndicator.js +index 8ac43eb..7c0360a 100644 +--- a/extensions/window-list/workspaceIndicator.js ++++ b/extensions/window-list/workspaceIndicator.js +@@ -26,14 +26,14 @@ var WorkspaceIndicator = GObject.registerClass({ + let workspaceManager = global.workspace_manager; + + this._currentWorkspace = workspaceManager.get_active_workspace().index(); +- this.statusLabel = new St.Label({ ++ this._statusLabel = new St.Label({ + text: this._getStatusText(), + x_align: Clutter.ActorAlign.CENTER, + y_align: Clutter.ActorAlign.CENTER + }); +- container.add_actor(this.statusLabel); ++ container.add_actor(this._statusLabel); + +- this.workspacesItems = []; ++ this._workspacesItems = []; + + this._workspaceManagerSignals = [ + workspaceManager.connect('notify::n-workspaces', +@@ -63,11 +63,11 @@ var WorkspaceIndicator = GObject.registerClass({ + } + + _updateIndicator() { +- this.workspacesItems[this._currentWorkspace].setOrnament(PopupMenu.Ornament.NONE); ++ this._workspacesItems[this._currentWorkspace].setOrnament(PopupMenu.Ornament.NONE); + this._currentWorkspace = global.workspace_manager.get_active_workspace().index(); +- this.workspacesItems[this._currentWorkspace].setOrnament(PopupMenu.Ornament.DOT); ++ this._workspacesItems[this._currentWorkspace].setOrnament(PopupMenu.Ornament.DOT); + +- this.statusLabel.set_text(this._getStatusText()); ++ this._statusLabel.set_text(this._getStatusText()); + } + + _getStatusText() { +@@ -82,7 +82,7 @@ var WorkspaceIndicator = GObject.registerClass({ + let workspaceManager = global.workspace_manager; + + this.menu.removeAll(); +- this.workspacesItems = []; ++ this._workspacesItems = []; + this._currentWorkspace = workspaceManager.get_active_workspace().index(); + + for (let i = 0; i < workspaceManager.n_workspaces; i++) { +@@ -98,10 +98,10 @@ var WorkspaceIndicator = GObject.registerClass({ + item.setOrnament(PopupMenu.Ornament.DOT); + + this.menu.addMenuItem(item); +- this.workspacesItems[i] = item; ++ this._workspacesItems[i] = item; + } + +- this.statusLabel.set_text(this._getStatusText()); ++ this._statusLabel.set_text(this._getStatusText()); + } + + _activate(index) { +-- +2.21.0 + + +From 8097536f35b9c64f24e8c1c6c8211024c4f8f01d Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Florian=20M=C3=BCllner?= +Date: Wed, 5 Jun 2019 04:57:39 +0200 +Subject: [PATCH 09/22] window-list: Update workspace names in-place + +There's no good reason to rebuild the entire menu on workspace names +changes, we can simply update the labels in-place. + +https://gitlab.gnome.org/GNOME/gnome-shell-extensions/merge_requests/70 +--- + extensions/window-list/workspaceIndicator.js | 10 +++++++++- + 1 file changed, 9 insertions(+), 1 deletion(-) + +diff --git a/extensions/window-list/workspaceIndicator.js b/extensions/window-list/workspaceIndicator.js +index 7c0360a..9888838 100644 +--- a/extensions/window-list/workspaceIndicator.js ++++ b/extensions/window-list/workspaceIndicator.js +@@ -47,7 +47,7 @@ var WorkspaceIndicator = GObject.registerClass({ + + this._settings = new Gio.Settings({ schema_id: 'org.gnome.desktop.wm.preferences' }); + this._settingsChangedId = this._settings.connect( +- 'changed::workspace-names', this._updateMenu.bind(this)); ++ 'changed::workspace-names', this._updateMenuLabels.bind(this)); + } + + _onDestroy() { +@@ -78,6 +78,14 @@ var WorkspaceIndicator = GObject.registerClass({ + return '%d / %d'.format(current + 1, total); + } + ++ _updateMenuLabels() { ++ for (let i = 0; i < this._workspacesItems.length; i++) { ++ let item = this._workspacesItems[i]; ++ let name = Meta.prefs_get_workspace_name(i); ++ item.label.text = name; ++ } ++ } ++ + _updateMenu() { + let workspaceManager = global.workspace_manager; + +-- +2.21.0 + + +From c51e35e0dc9937ab19577be41654f914f1cfe894 Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Florian=20M=C3=BCllner?= +Date: Wed, 5 Jun 2019 04:59:19 +0200 +Subject: [PATCH 10/22] window-list: Minor cleanup + +Mutter has a dedicated method for getting the index of the active +workspace, use that instead of getting first the active workspace +and then its index. + +https://gitlab.gnome.org/GNOME/gnome-shell-extensions/merge_requests/70 +--- + extensions/window-list/workspaceIndicator.js | 8 ++++---- + 1 file changed, 4 insertions(+), 4 deletions(-) + +diff --git a/extensions/window-list/workspaceIndicator.js b/extensions/window-list/workspaceIndicator.js +index 9888838..1f2e1c1 100644 +--- a/extensions/window-list/workspaceIndicator.js ++++ b/extensions/window-list/workspaceIndicator.js +@@ -25,7 +25,7 @@ var WorkspaceIndicator = GObject.registerClass({ + + let workspaceManager = global.workspace_manager; + +- this._currentWorkspace = workspaceManager.get_active_workspace().index(); ++ this._currentWorkspace = workspaceManager.get_active_workspace_index(); + this._statusLabel = new St.Label({ + text: this._getStatusText(), + x_align: Clutter.ActorAlign.CENTER, +@@ -64,7 +64,7 @@ var WorkspaceIndicator = GObject.registerClass({ + + _updateIndicator() { + this._workspacesItems[this._currentWorkspace].setOrnament(PopupMenu.Ornament.NONE); +- this._currentWorkspace = global.workspace_manager.get_active_workspace().index(); ++ this._currentWorkspace = global.workspace_manager.get_active_workspace_index(); + this._workspacesItems[this._currentWorkspace].setOrnament(PopupMenu.Ornament.DOT); + + this._statusLabel.set_text(this._getStatusText()); +@@ -72,7 +72,7 @@ var WorkspaceIndicator = GObject.registerClass({ + + _getStatusText() { + let workspaceManager = global.workspace_manager; +- let current = workspaceManager.get_active_workspace().index(); ++ let current = workspaceManager.get_active_workspace_index(); + let total = workspaceManager.n_workspaces; + + return '%d / %d'.format(current + 1, total); +@@ -91,7 +91,7 @@ var WorkspaceIndicator = GObject.registerClass({ + + this.menu.removeAll(); + this._workspacesItems = []; +- this._currentWorkspace = workspaceManager.get_active_workspace().index(); ++ this._currentWorkspace = workspaceManager.get_active_workspace_index(); + + for (let i = 0; i < workspaceManager.n_workspaces; i++) { + let name = Meta.prefs_get_workspace_name(i); +-- +2.21.0 + + +From 2eb09e7cc7e830bf4004a7c25a46d285e5e0bac1 Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Florian=20M=C3=BCllner?= +Date: Wed, 5 Jun 2019 05:11:34 +0200 +Subject: [PATCH 11/22] window-list: Improve workspace label styling + +The border currently looks off - it extends all the way vertically +and leaves zero spacing to the label horizontally. Fix both issues +by setting appropriate padding/margins. + +https://gitlab.gnome.org/GNOME/gnome-shell-extensions/merge_requests/70 +--- + extensions/window-list/stylesheet.css | 8 +++----- + extensions/window-list/workspaceIndicator.js | 13 ++++++++----- + 2 files changed, 11 insertions(+), 10 deletions(-) + +diff --git a/extensions/window-list/stylesheet.css b/extensions/window-list/stylesheet.css +index f5285cb..3d3f2d7 100644 +--- a/extensions/window-list/stylesheet.css ++++ b/extensions/window-list/stylesheet.css +@@ -70,13 +70,11 @@ + height: 24px; + } + +-.window-list-workspace-indicator { +- padding: 3px; +-} +- +-.window-list-workspace-indicator > StWidget { ++.window-list-workspace-indicator .status-label-bin { + background-color: rgba(200, 200, 200, .3); + border: 1px solid #cccccc; ++ padding: 0 3px; ++ margin: 3px 0; + } + + .notification { +diff --git a/extensions/window-list/workspaceIndicator.js b/extensions/window-list/workspaceIndicator.js +index 1f2e1c1..598c516 100644 +--- a/extensions/window-list/workspaceIndicator.js ++++ b/extensions/window-list/workspaceIndicator.js +@@ -26,12 +26,15 @@ var WorkspaceIndicator = GObject.registerClass({ + let workspaceManager = global.workspace_manager; + + this._currentWorkspace = workspaceManager.get_active_workspace_index(); +- this._statusLabel = new St.Label({ +- text: this._getStatusText(), +- x_align: Clutter.ActorAlign.CENTER, +- y_align: Clutter.ActorAlign.CENTER ++ this._statusLabel = new St.Label({ text: this._getStatusText() }); ++ ++ this._statusBin = new St.Bin({ ++ style_class: 'status-label-bin', ++ x_expand: true, ++ y_expand: true, ++ child: this._statusLabel + }); +- container.add_actor(this._statusLabel); ++ container.add_actor(this._statusBin); + + this._workspacesItems = []; + +-- +2.21.0 + + +From 477a532a5f5fb2f8a1ddcfc2277fe62fa1a7fb74 Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Florian=20M=C3=BCllner?= +Date: Wed, 5 Jun 2019 05:08:31 +0200 +Subject: [PATCH 12/22] window-list: Refactor workspace signal handlers + +We are about to support a separate representation if horizontal +workspaces are used. To prepare for that, rename the handlers to +something more generic and split out menu-specific bits into a +dedicated help function. + +https://gitlab.gnome.org/GNOME/gnome-shell-extensions/merge_requests/70 +--- + extensions/window-list/workspaceIndicator.js | 25 +++++++++++++++----- + 1 file changed, 19 insertions(+), 6 deletions(-) + +diff --git a/extensions/window-list/workspaceIndicator.js b/extensions/window-list/workspaceIndicator.js +index 598c516..78ca97e 100644 +--- a/extensions/window-list/workspaceIndicator.js ++++ b/extensions/window-list/workspaceIndicator.js +@@ -40,9 +40,9 @@ var WorkspaceIndicator = GObject.registerClass({ + + this._workspaceManagerSignals = [ + workspaceManager.connect('notify::n-workspaces', +- this._updateMenu.bind(this)), ++ this._nWorkspacesChanged.bind(this)), + workspaceManager.connect_after('workspace-switched', +- this._updateIndicator.bind(this)) ++ this._onWorkspaceSwitched.bind(this)) + ]; + + this.connect('scroll-event', this._onScrollEvent.bind(this)); +@@ -65,14 +65,27 @@ var WorkspaceIndicator = GObject.registerClass({ + super._onDestroy(); + } + +- _updateIndicator() { +- this._workspacesItems[this._currentWorkspace].setOrnament(PopupMenu.Ornament.NONE); +- this._currentWorkspace = global.workspace_manager.get_active_workspace_index(); +- this._workspacesItems[this._currentWorkspace].setOrnament(PopupMenu.Ornament.DOT); ++ _onWorkspaceSwitched() { ++ let workspaceManager = global.workspace_manager; ++ this._currentWorkspace = workspaceManager.get_active_workspace_index(); ++ ++ this._updateMenuOrnament(); + + this._statusLabel.set_text(this._getStatusText()); + } + ++ _nWorkspacesChanged() { ++ this._updateMenu(); ++ } ++ ++ _updateMenuOrnament() { ++ for (let i = 0; i < this._workspacesItems.length; i++) { ++ this._workspacesItems[i].setOrnament(i == this._currentWorkspace ++ ? PopupMenu.Ornament.DOT ++ : PopupMenu.Ornament.NONE); ++ } ++ } ++ + _getStatusText() { + let workspaceManager = global.workspace_manager; + let current = workspaceManager.get_active_workspace_index(); +-- +2.21.0 + + +From 8d2a00ef3a1bbd9c5bccd1542049b22a3bb2a79d Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Florian=20M=C3=BCllner?= +Date: Wed, 5 Jun 2019 02:53:38 +0000 +Subject: [PATCH 13/22] window-list: Support horizontal workspace layout + +Unlike in GNOME 2, the workspace indicator we display in the window list +isn't a workspace switcher, but a menu button that allows switching +workspaces via its menu. The reason for that is that a horizontal +in-place switcher would be at odds with the vertical workspace layout +used in GNOME 3. + +However that reasoning doesn't apply when the layout is changed to a +horizontal one, so replace the button with a traditional workspace +switcher in that case. + +https://gitlab.gnome.org/GNOME/gnome-shell-extensions/merge_requests/70 +--- + extensions/window-list/stylesheet.css | 24 +++++++ + extensions/window-list/workspaceIndicator.js | 66 +++++++++++++++++++- + 2 files changed, 89 insertions(+), 1 deletion(-) + +diff --git a/extensions/window-list/stylesheet.css b/extensions/window-list/stylesheet.css +index 3d3f2d7..b383b97 100644 +--- a/extensions/window-list/stylesheet.css ++++ b/extensions/window-list/stylesheet.css +@@ -77,6 +77,30 @@ + margin: 3px 0; + } + ++.window-list-workspace-indicator .workspaces-box { ++ spacing: 3px; ++ padding: 3px; ++} ++ ++.window-list-workspace-indicator .workspace { ++ border: 1px solid #cccccc; ++ width: 52px; ++} ++ ++.window-list-workspace-indicator .workspace:first-child:ltr, ++.window-list-workspace-indicator .workspace:last-child:rtl { ++ border-radius: 4px 0 0 4px; ++} ++ ++.window-list-workspace-indicator .workspace:first-child:rtl, ++.window-list-workspace-indicator .workspace:last-child:ltr { ++ border-radius: 0 4px 4px 0; ++} ++ ++.window-list-workspace-indicator .workspace.active { ++ background-color: rgba(200, 200, 200, .3); ++} ++ + .notification { + font-weight: normal; + } +diff --git a/extensions/window-list/workspaceIndicator.js b/extensions/window-list/workspaceIndicator.js +index 78ca97e..1258ed2 100644 +--- a/extensions/window-list/workspaceIndicator.js ++++ b/extensions/window-list/workspaceIndicator.js +@@ -7,6 +7,25 @@ const PopupMenu = imports.ui.popupMenu; + const Gettext = imports.gettext.domain('gnome-shell-extensions'); + const _ = Gettext.gettext; + ++let WorkspaceThumbnail = GObject.registerClass({ ++ GTypeName: 'WindowListWorkspaceThumbnail' ++}, class WorkspaceThumbnail extends St.Button { ++ _init(index) { ++ super._init({ ++ style_class: 'workspace' ++ }); ++ ++ this._index = index; ++ } ++ ++ // eslint-disable-next-line camelcase ++ on_clicked() { ++ let ws = global.workspace_manager.get_workspace_by_index(this._index); ++ if (ws) ++ ws.activate(global.get_current_time()); ++ } ++}); ++ + var WorkspaceIndicator = GObject.registerClass({ + GTypeName: 'WindowListWorkspaceIndicator' + }, class WorkspaceIndicator extends PanelMenu.Button { +@@ -36,17 +55,30 @@ var WorkspaceIndicator = GObject.registerClass({ + }); + container.add_actor(this._statusBin); + ++ this._thumbnailsBox = new St.BoxLayout({ ++ style_class: 'workspaces-box', ++ y_expand: true, ++ reactive: true ++ }); ++ this._thumbnailsBox.connect('scroll-event', ++ this._onScrollEvent.bind(this)); ++ container.add_actor(this._thumbnailsBox); ++ + this._workspacesItems = []; + + this._workspaceManagerSignals = [ + workspaceManager.connect('notify::n-workspaces', + this._nWorkspacesChanged.bind(this)), + workspaceManager.connect_after('workspace-switched', +- this._onWorkspaceSwitched.bind(this)) ++ this._onWorkspaceSwitched.bind(this)), ++ workspaceManager.connect('notify::layout-rows', ++ this._onWorkspaceOrientationChanged.bind(this)) + ]; + + this.connect('scroll-event', this._onScrollEvent.bind(this)); + this._updateMenu(); ++ this._updateThumbnails(); ++ this._onWorkspaceOrientationChanged(); + + this._settings = new Gio.Settings({ schema_id: 'org.gnome.desktop.wm.preferences' }); + this._settingsChangedId = this._settings.connect( +@@ -65,17 +97,27 @@ var WorkspaceIndicator = GObject.registerClass({ + super._onDestroy(); + } + ++ _onWorkspaceOrientationChanged() { ++ let vertical = global.workspace_manager.layout_rows == -1; ++ this.reactive = vertical; ++ ++ this._statusBin.visible = vertical; ++ this._thumbnailsBox.visible = !vertical; ++ } ++ + _onWorkspaceSwitched() { + let workspaceManager = global.workspace_manager; + this._currentWorkspace = workspaceManager.get_active_workspace_index(); + + this._updateMenuOrnament(); ++ this._updateActiveThumbnail(); + + this._statusLabel.set_text(this._getStatusText()); + } + + _nWorkspacesChanged() { + this._updateMenu(); ++ this._updateThumbnails(); + } + + _updateMenuOrnament() { +@@ -86,6 +128,16 @@ var WorkspaceIndicator = GObject.registerClass({ + } + } + ++ _updateActiveThumbnail() { ++ let thumbs = this._thumbnailsBox.get_children(); ++ for (let i = 0; i < thumbs.length; i++) { ++ if (i == this._currentWorkspace) ++ thumbs[i].add_style_class_name('active'); ++ else ++ thumbs[i].remove_style_class_name('active'); ++ } ++ } ++ + _getStatusText() { + let workspaceManager = global.workspace_manager; + let current = workspaceManager.get_active_workspace_index(); +@@ -128,6 +180,18 @@ var WorkspaceIndicator = GObject.registerClass({ + this._statusLabel.set_text(this._getStatusText()); + } + ++ _updateThumbnails() { ++ let workspaceManager = global.workspace_manager; ++ ++ this._thumbnailsBox.destroy_all_children(); ++ ++ for (let i = 0; i < workspaceManager.n_workspaces; i++) { ++ let thumb = new WorkspaceThumbnail(i); ++ this._thumbnailsBox.add_actor(thumb); ++ } ++ this._updateActiveThumbnail(); ++ } ++ + _activate(index) { + let workspaceManager = global.workspace_manager; + +-- +2.21.0 + + +From 09e1fab05fe622ea811f9618d7f97ca228f4c282 Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Florian=20M=C3=BCllner?= +Date: Wed, 5 Jun 2019 03:31:16 +0000 +Subject: [PATCH 14/22] classic: Add 'horizontal-workspaces' extension + +Vertical workspaces are another defining characteristics of GNOME 3, +and thus rather un-classic. That switch was driven by the overall +layout of the overview, and now that we disable the overview in +GNOME Classic, we can just return to the traditional workspace +layout as well. + +Add a small extension that does just that. + +https://gitlab.gnome.org/GNOME/gnome-shell-extensions/merge_requests/72 +--- + extensions/horizontal-workspaces/extension.js | 18 ++++++++++++++++++ + extensions/horizontal-workspaces/meson.build | 5 +++++ + .../horizontal-workspaces/metadata.json.in | 10 ++++++++++ + .../horizontal-workspaces/stylesheet.css | 1 + + meson.build | 1 + + 5 files changed, 35 insertions(+) + create mode 100644 extensions/horizontal-workspaces/extension.js + create mode 100644 extensions/horizontal-workspaces/meson.build + create mode 100644 extensions/horizontal-workspaces/metadata.json.in + create mode 100644 extensions/horizontal-workspaces/stylesheet.css + +diff --git a/extensions/horizontal-workspaces/extension.js b/extensions/horizontal-workspaces/extension.js +new file mode 100644 +index 0000000..b3937ce +--- /dev/null ++++ b/extensions/horizontal-workspaces/extension.js +@@ -0,0 +1,18 @@ ++/* exported enable disable */ ++const { Meta } = imports.gi; ++ ++function enable() { ++ global.workspace_manager.override_workspace_layout( ++ Meta.DisplayCorner.TOPLEFT, ++ false, ++ 1, ++ -1); ++} ++ ++function disable() { ++ global.workspace_manager.override_workspace_layout( ++ Meta.DisplayCorner.TOPLEFT, ++ false, ++ -1, ++ 1); ++} +diff --git a/extensions/horizontal-workspaces/meson.build b/extensions/horizontal-workspaces/meson.build +new file mode 100644 +index 0000000..48504f6 +--- /dev/null ++++ b/extensions/horizontal-workspaces/meson.build +@@ -0,0 +1,5 @@ ++extension_data += configure_file( ++ input: metadata_name + '.in', ++ output: metadata_name, ++ configuration: metadata_conf ++) +diff --git a/extensions/horizontal-workspaces/metadata.json.in b/extensions/horizontal-workspaces/metadata.json.in +new file mode 100644 +index 0000000..f109e06 +--- /dev/null ++++ b/extensions/horizontal-workspaces/metadata.json.in +@@ -0,0 +1,10 @@ ++{ ++"extension-id": "@extension_id@", ++"uuid": "@uuid@", ++"settings-schema": "@gschemaname@", ++"gettext-domain": "@gettext_domain@", ++"name": "Horizontal workspaces", ++"description": "Use a horizontal workspace layout", ++"shell-version": [ "@shell_current@" ], ++"url": "@url@" ++} +diff --git a/extensions/horizontal-workspaces/stylesheet.css b/extensions/horizontal-workspaces/stylesheet.css +new file mode 100644 +index 0000000..25134b6 +--- /dev/null ++++ b/extensions/horizontal-workspaces/stylesheet.css +@@ -0,0 +1 @@ ++/* This extensions requires no special styling */ +diff --git a/meson.build b/meson.build +index 32743ed..23bd5ad 100644 +--- a/meson.build ++++ b/meson.build +@@ -34,6 +34,7 @@ uuid_suffix = '@gnome-shell-extensions.gcampax.github.com' + classic_extensions = [ + 'apps-menu', + 'desktop-icons', ++ 'horizontal-workspaces', + 'places-menu', + 'launch-new-instance', + 'top-icons', +-- +2.21.0 + + +From 85a49ec1e4eb01a32f9bf9b3b8a1bde805936d7b Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Florian=20M=C3=BCllner?= +Date: Tue, 14 May 2019 19:51:22 +0200 +Subject: [PATCH 15/22] window-list: Add window picker button +MIME-Version: 1.0 +Content-Type: text/plain; charset=UTF-8 +Content-Transfer-Encoding: 8bit + +With the latest changes, GNOME Classic has become so classic that it +is bordering dull. Salvage at least a tiny piece of GNOME 3 in form +of a window-pick button which toggles an exposé-like reduced overview. + +https://gitlab.gnome.org/GNOME/gnome-shell-extensions/merge_requests/73 +--- + extensions/window-list/classic.css | 20 +- + extensions/window-list/extension.js | 36 +++- + extensions/window-list/meson.build | 2 +- + extensions/window-list/stylesheet.css | 27 ++- + extensions/window-list/windowPicker.js | 250 +++++++++++++++++++++++++ + 5 files changed, 322 insertions(+), 13 deletions(-) + create mode 100644 extensions/window-list/windowPicker.js + +diff --git a/extensions/window-list/classic.css b/extensions/window-list/classic.css +index f3c44a3..c506bea 100644 +--- a/extensions/window-list/classic.css ++++ b/extensions/window-list/classic.css +@@ -6,14 +6,13 @@ + height: 2.25em ; + } + +- .bottom-panel .window-button > StWidget { ++ .bottom-panel .window-button > StWidget, ++ .bottom-panel .window-picker-toggle > StWidget { + background-gradient-drection: vertical; + background-color: #fff; + background-gradient-start: #fff; + background-gradient-end: #eee; + color: #000; +- -st-natural-width: 18.7em; +- max-width: 18.75em; + color: #2e3436; + background-color: #eee; + border-radius: 2px; +@@ -22,7 +21,17 @@ + text-shadow: 0 0 transparent; + } + +- .bottom-panel .window-button:hover > StWidget { ++ .bottom-panel .window-button > StWidget { ++ -st-natural-width: 18.7em; ++ max-width: 18.75em; ++ } ++ ++ .bottom-panel .window-picker-toggle > StWidet { ++ border: 1px solid rgba(0,0,0,0.3); ++ } ++ ++ .bottom-panel .window-button:hover > StWidget, ++ .bottom-panel .window-picker-toggle:hover > StWidget { + background-color: #f9f9f9; + } + +@@ -31,7 +40,8 @@ + box-shadow: inset 1px 1px 2px rgba(0,0,0,0.5); + } + +- .bottom-panel .window-button.focused > StWidget { ++ .bottom-panel .window-button.focused > StWidget, ++ .bottom-panel .window-picker-toggle:checked > StWidget { + background-color: #ddd; + box-shadow: inset 1px 1px 1px rgba(0,0,0,0.5); + } +diff --git a/extensions/window-list/extension.js b/extensions/window-list/extension.js +index 5275d9c..1f854aa 100644 +--- a/extensions/window-list/extension.js ++++ b/extensions/window-list/extension.js +@@ -3,10 +3,13 @@ const { Clutter, Gio, GLib, Gtk, Meta, Shell, St } = imports.gi; + + const DND = imports.ui.dnd; + const Main = imports.ui.main; ++const Overview = imports.ui.overview; + const PopupMenu = imports.ui.popupMenu; ++const Tweener = imports.ui.tweener; + + const ExtensionUtils = imports.misc.extensionUtils; + const Me = ExtensionUtils.getCurrentExtension(); ++const { WindowPicker, WindowPickerToggle } = Me.imports.windowPicker; + const { WorkspaceIndicator } = Me.imports.workspaceIndicator; + + const Gettext = imports.gettext.domain('gnome-shell-extensions'); +@@ -661,6 +664,12 @@ class WindowList { + let box = new St.BoxLayout({ x_expand: true, y_expand: true }); + this.actor.add_actor(box); + ++ let toggle = new WindowPickerToggle(); ++ box.add_actor(toggle); ++ ++ toggle.connect('notify::checked', ++ this._updateWindowListVisibility.bind(this)); ++ + let layout = new Clutter.BoxLayout({ homogeneous: true }); + this._windowList = new St.Widget({ + style_class: 'window-list', +@@ -810,6 +819,19 @@ class WindowList { + this._workspaceIndicator.actor.visible = hasWorkspaces && workspacesOnMonitor; + } + ++ _updateWindowListVisibility() { ++ let visible = !Main.windowPicker.visible; ++ ++ Tweener.addTween(this._windowList, { ++ opacity: visible ? 255 : 0, ++ transition: 'ease-out-quad', ++ time: Overview.ANIMATION_TIME ++ }); ++ ++ this._windowList.reactive = visible; ++ this._windowList.get_children().forEach(c => c.reactive = visible); ++ } ++ + _getPreferredUngroupedWindowListWidth() { + if (this._windowList.get_n_children() == 0) + return this._windowList.get_preferred_width(-1)[1]; +@@ -1080,7 +1102,7 @@ class WindowList { + class Extension { + constructor() { + this._windowLists = null; +- this._injections = {}; ++ this._hideOverviewOrig = Main.overview.hide; + } + + enable() { +@@ -1095,6 +1117,13 @@ class Extension { + Main.layoutManager.connect('monitors-changed', + this._buildWindowLists.bind(this)); + ++ Main.windowPicker = new WindowPicker(); ++ ++ Main.overview.hide = () => { ++ Main.windowPicker.close(); ++ this._hideOverviewOrig.call(Main.overview); ++ }; ++ + this._buildWindowLists(); + } + +@@ -1125,6 +1154,11 @@ class Extension { + windowList.actor.destroy(); + }); + this._windowLists = null; ++ ++ Main.windowPicker.actor.destroy(); ++ delete Main.windowPicker; ++ ++ Main.overview.hide = this._hideOverviewOrig; + } + + someWindowListContains(actor) { +diff --git a/extensions/window-list/meson.build b/extensions/window-list/meson.build +index 74025f9..34d7c3f 100644 +--- a/extensions/window-list/meson.build ++++ b/extensions/window-list/meson.build +@@ -4,7 +4,7 @@ extension_data += configure_file( + configuration: metadata_conf + ) + +-extension_sources += files('prefs.js', 'workspaceIndicator.js') ++extension_sources += files('prefs.js', 'windowPicker.js', 'workspaceIndicator.js') + extension_schemas += files(metadata_conf.get('gschemaname') + '.gschema.xml') + + if classic_mode_enabled +diff --git a/extensions/window-list/stylesheet.css b/extensions/window-list/stylesheet.css +index b383b97..7aee64f 100644 +--- a/extensions/window-list/stylesheet.css ++++ b/extensions/window-list/stylesheet.css +@@ -26,9 +26,8 @@ + spacing: 4px; + } + +-.window-button > StWidget { +- -st-natural-width: 18.75em; +- max-width: 18.75em; ++.window-button > StWidget, ++.window-picker-toggle > StWidget { + color: #bbb; + background-color: black; + border-radius: 4px; +@@ -37,7 +36,21 @@ + text-shadow: 1px 1px 4px rgba(0,0,0,0.8); + } + +-.window-button:hover > StWidget { ++.window-picker-toggle { ++ padding: 3px; ++} ++ ++.window-picker-toggle > StWidet { ++ border: 1px solid rgba(255,255,255,0.3); ++} ++ ++.window-button > StWidget { ++ -st-natural-width: 18.75em; ++ max-width: 18.75em; ++} ++ ++.window-button:hover > StWidget, ++.window-picker-toggle:hover > StWidget { + color: white; + background-color: #1f1f1f; + } +@@ -47,12 +60,14 @@ + box-shadow: inset 2px 2px 4px rgba(255,255,255,0.5); + } + +-.window-button.focused > StWidget { ++.window-button.focused > StWidget, ++.window-picker-toggle:checked > StWidget { + color: white; + box-shadow: inset 1px 1px 4px rgba(255,255,255,0.7); + } + +-.window-button.focused:active > StWidget { ++.window-button.focused:active > StWidget, ++.window-picker-toggle:checked:active > StWidget { + box-shadow: inset 2px 2px 4px rgba(255,255,255,0.7); + } + +diff --git a/extensions/window-list/windowPicker.js b/extensions/window-list/windowPicker.js +new file mode 100644 +index 0000000..484f6bb +--- /dev/null ++++ b/extensions/window-list/windowPicker.js +@@ -0,0 +1,250 @@ ++/* exported WindowPicker, WindowPickerToggle */ ++const { Clutter, GLib, GObject, Meta, Shell, St } = imports.gi; ++const Signals = imports.signals; ++ ++const Layout = imports.ui.layout; ++const Main = imports.ui.main; ++const Overview = imports.ui.overview; ++const { WorkspacesDisplay } = imports.ui.workspacesView; ++ ++let MyWorkspacesDisplay = class extends WorkspacesDisplay { ++ constructor() { ++ super(); ++ ++ this.actor.add_constraint( ++ new Layout.MonitorConstraint({ ++ primary: true, ++ work_area: true ++ })); ++ ++ this.actor.connect('destroy', this._onDestroy.bind(this)); ++ ++ this._workareasChangedId = global.display.connect('workareas-changed', ++ this._onWorkAreasChanged.bind(this)); ++ this._onWorkAreasChanged(); ++ } ++ ++ show(...args) { ++ if (this._scrollEventId == 0) ++ this._scrollEventId = Main.windowPicker.connect('scroll-event', ++ this._onScrollEvent.bind(this)); ++ ++ super.show(...args); ++ } ++ ++ hide(...args) { ++ if (this._scrollEventId > 0) ++ Main.windowPicker.disconnect(this._scrollEventId); ++ this._scrollEventId = 0; ++ ++ super.hide(...args); ++ } ++ ++ _onWorkAreasChanged() { ++ let { primaryIndex } = Main.layoutManager; ++ let workarea = Main.layoutManager.getWorkAreaForMonitor(primaryIndex); ++ this.setWorkspacesFullGeometry(workarea); ++ } ++ ++ _updateWorkspacesViews() { ++ super._updateWorkspacesViews(); ++ ++ this._workspacesViews.forEach(v => { ++ Main.layoutManager.overviewGroup.remove_actor(v.actor); ++ Main.windowPicker.actor.add_actor(v.actor); ++ }); ++ } ++ ++ _onDestroy() { ++ if (this._workareasChangedId) ++ global.display.disconnect(this._workareasChangedId); ++ this._workareasChangedId = 0; ++ } ++}; ++ ++var WindowPicker = class { ++ constructor() { ++ this._visible = false; ++ this._modal = false; ++ ++ this.actor = new Clutter.Actor(); ++ ++ this.actor.connect('destroy', this._onDestroy.bind(this)); ++ ++ global.bind_property('screen-width', ++ this.actor, 'width', ++ GObject.BindingFlags.SYNC_CREATE); ++ global.bind_property('screen-height', ++ this.actor, 'height', ++ GObject.BindingFlags.SYNC_CREATE); ++ ++ this._backgroundGroup = new Meta.BackgroundGroup({ reactive: true }); ++ this.actor.add_child(this._backgroundGroup); ++ ++ this._backgroundGroup.connect('scroll-event', (a, ev) => { ++ this.emit('scroll-event', ev); ++ }); ++ ++ // Trick WorkspacesDisplay constructor into adding actions here ++ let addActionOrig = Main.overview.addAction; ++ Main.overview.addAction = a => this._backgroundGroup.add_action(a); ++ ++ this._workspacesDisplay = new MyWorkspacesDisplay(); ++ this.actor.add_child(this._workspacesDisplay.actor); ++ ++ Main.overview.addAction = addActionOrig; ++ ++ this._bgManagers = []; ++ ++ this._monitorsChangedId = Main.layoutManager.connect('monitors-changed', ++ this._updateBackgrounds.bind(this)); ++ this._updateBackgrounds(); ++ ++ Main.uiGroup.insert_child_below(this.actor, global.window_group); ++ } ++ ++ get visible() { ++ return this._visible; ++ } ++ ++ open() { ++ if (this._visible) ++ return; ++ ++ this._visible = true; ++ ++ if (!this._syncGrab()) ++ return; ++ ++ this._fakeOverviewVisible(true); ++ this._shadeBackgrounds(); ++ this._fakeOverviewAnimation(); ++ this._workspacesDisplay.show(false); ++ ++ this.emit('open-state-changed', this._visible); ++ } ++ ++ close() { ++ if (!this._visible) ++ return; ++ ++ this._visible = false; ++ ++ if (!this._syncGrab()) ++ return; ++ ++ this._workspacesDisplay.animateFromOverview(false); ++ this._unshadeBackgrounds(); ++ this._fakeOverviewAnimation(() => { ++ this._workspacesDisplay.hide(); ++ this._fakeOverviewVisible(false); ++ }); ++ ++ this.emit('open-state-changed', this._visible); ++ } ++ ++ _fakeOverviewAnimation(onComplete) { ++ Main.overview.animationInProgress = true; ++ GLib.timeout_add( ++ GLib.PRIORITY_DEFAULT, ++ Overview.ANIMATION_TIME * 1000, ++ () => { ++ Main.overview.animationInProgress = false; ++ if (onComplete) ++ onComplete(); ++ }); ++ } ++ ++ _fakeOverviewVisible(visible) { ++ // Fake overview state for WorkspacesDisplay ++ Main.overview.visible = visible; ++ ++ // Hide real windows ++ Main.layoutManager._inOverview = visible; ++ Main.layoutManager._updateVisibility(); ++ } ++ ++ _syncGrab() { ++ if (this._visible) { ++ if (this._modal) ++ return true; ++ ++ this._modal = Main.pushModal(this.actor, { ++ actionMode: Shell.ActionMode.OVERVIEW ++ }); ++ ++ if (!this._modal) { ++ this.hide(); ++ return false; ++ } ++ } else if (this._modal) { ++ Main.popModal(this.actor); ++ this._modal = false; ++ } ++ return true; ++ } ++ ++ _onDestroy() { ++ if (this._monitorsChangedId) ++ Main.layoutManager.disconnect(this._monitorsChangedId); ++ this._monitorsChangedId = 0; ++ } ++ ++ _updateBackgrounds() { ++ Main.overview._updateBackgrounds.call(this); ++ } ++ ++ _shadeBackgrounds() { ++ Main.overview._shadeBackgrounds.call(this); ++ } ++ ++ _unshadeBackgrounds() { ++ Main.overview._unshadeBackgrounds.call(this); ++ } ++}; ++Signals.addSignalMethods(WindowPicker.prototype); ++ ++var WindowPickerToggle = GObject.registerClass( ++class WindowPickerToggle extends St.Button { ++ _init() { ++ let iconBin = new St.Widget({ ++ layout_manager: new Clutter.BinLayout() ++ }); ++ iconBin.add_child(new St.Icon({ ++ icon_name: 'focus-windows-symbolic', ++ icon_size: 16, ++ x_expand: true, ++ y_expand: true, ++ x_align: Clutter.ActorAlign.CENTER, ++ y_align: Clutter.ActorAlign.CENTER ++ })); ++ super._init({ ++ style_class: 'window-picker-toggle', ++ child: iconBin, ++ visible: !Main.sessionMode.hasOverview, ++ x_fill: true, ++ y_fill: true, ++ toggle_mode: true ++ }); ++ ++ this.connect('notify::checked', () => { ++ if (this.checked) ++ Main.windowPicker.open(); ++ else ++ Main.windowPicker.close(); ++ }); ++ ++ if (!Main.sessionMode.hasOverview) { ++ global.display.connect('overlay-key', () => { ++ if (!Main.windowPicker.visible) ++ Main.windowPicker.open(); ++ else ++ Main.windowPicker.close(); ++ }); ++ } ++ ++ Main.windowPicker.connect('open-state-changed', () => { ++ this.checked = Main.windowPicker.visible; ++ }); ++ } ++}); +-- +2.21.0 + + +From 36f9a7ea01eed0481572459afc81f8fb433b95d3 Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Florian=20M=C3=BCllner?= +Date: Tue, 11 Jun 2019 23:01:20 +0000 +Subject: [PATCH 16/22] window-list: Turn workspace thumbs into drop targets + +It makes some sense to allow using the workspace indicator for moving +windows between workspaces as well as for workspace switching. This +applies particularly in GNOME classic after we disabled the overview +there, so that there is again a non-shortcut way of moving windows +between workspaces. + +https://gitlab.gnome.org/GNOME/gnome-shell-extensions/merge_requests/74 +--- + extensions/window-list/workspaceIndicator.js | 35 +++++++++++++++++++- + 1 file changed, 34 insertions(+), 1 deletion(-) + +diff --git a/extensions/window-list/workspaceIndicator.js b/extensions/window-list/workspaceIndicator.js +index 1258ed2..8a93e84 100644 +--- a/extensions/window-list/workspaceIndicator.js ++++ b/extensions/window-list/workspaceIndicator.js +@@ -1,6 +1,7 @@ + /* exported WorkspaceIndicator */ +-const { Clutter, Gio, GObject, Meta, St } = imports.gi; ++const { Clutter, Gio, GObject, Meta, Shell, St } = imports.gi; + ++const Main = imports.ui.main; + const PanelMenu = imports.ui.panelMenu; + const PopupMenu = imports.ui.popupMenu; + +@@ -16,6 +17,38 @@ let WorkspaceThumbnail = GObject.registerClass({ + }); + + this._index = index; ++ this._delegate = this; // needed for DND ++ ++ this._grabOpEndId = global.display.connect('grab-op-end', ++ (dpy, dpy_, window, grabOp) => { ++ if (grabOp != Meta.GrabOp.MOVING) ++ return; ++ ++ let [x, y] = global.get_pointer(); ++ let alloc = Shell.util_get_transformed_allocation(this); ++ if (alloc.contains(x, y)) ++ this._moveWindow(window); ++ }); ++ ++ this.connect('destroy', () => { ++ global.display.disconnect(this._grabOpEndId); ++ }); ++ } ++ ++ acceptDrop(source) { ++ if (!source.realWindow) ++ return false; ++ ++ let window = source.realWindow.get_meta_window(); ++ this._moveWindow(window); ++ return true; ++ } ++ ++ _moveWindow(window) { ++ let monitorIndex = Main.layoutManager.findIndexForActor(this); ++ if (monitorIndex != window.get_monitor()) ++ window.move_to_monitor(monitorIndex); ++ window.change_workspace_by_index(this._index, false); + } + + // eslint-disable-next-line camelcase +-- +2.21.0 + + +From 3ac15a848809da6053c224e86c5f5c72243f6c5b Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Florian=20M=C3=BCllner?= +Date: Sun, 9 Jun 2019 22:58:29 +0000 +Subject: [PATCH 17/22] workspace-indicator: Make some properties private + +There's no reason why they should be public. + +https://gitlab.gnome.org/GNOME/gnome-shell-extensions/merge_requests/71 +--- + extensions/workspace-indicator/extension.js | 30 ++++++++++----------- + 1 file changed, 15 insertions(+), 15 deletions(-) + +diff --git a/extensions/workspace-indicator/extension.js b/extensions/workspace-indicator/extension.js +index 3be1268..76224b9 100644 +--- a/extensions/workspace-indicator/extension.js ++++ b/extensions/workspace-indicator/extension.js +@@ -23,14 +23,14 @@ class WorkspaceIndicator extends PanelMenu.Button { + let workspaceManager = global.workspace_manager; + + this._currentWorkspace = workspaceManager.get_active_workspace().index(); +- this.statusLabel = new St.Label({ ++ this._statusLabel = new St.Label({ + y_align: Clutter.ActorAlign.CENTER, + text: this._labelText() + }); + +- this.add_actor(this.statusLabel); ++ this.add_actor(this._statusLabel); + +- this.workspacesItems = []; ++ this._workspacesItems = []; + this._workspaceSection = new PopupMenu.PopupMenuSection(); + this.menu.addMenuItem(this._workspaceSection); + +@@ -46,7 +46,7 @@ class WorkspaceIndicator extends PanelMenu.Button { + this._createWorkspacesSection(); + + //styling +- this.statusLabel.add_style_class_name('panel-workspace-indicator'); ++ this._statusLabel.add_style_class_name('panel-workspace-indicator'); + + this._settings = new Gio.Settings({ schema_id: WORKSPACE_SCHEMA }); + this._settingsChangedId = +@@ -67,11 +67,11 @@ class WorkspaceIndicator extends PanelMenu.Button { + } + + _updateIndicator() { +- this.workspacesItems[this._currentWorkspace].setOrnament(PopupMenu.Ornament.NONE); ++ this._workspacesItems[this._currentWorkspace].setOrnament(PopupMenu.Ornament.NONE); + this._currentWorkspace = global.workspace_manager.get_active_workspace().index(); +- this.workspacesItems[this._currentWorkspace].setOrnament(PopupMenu.Ornament.DOT); ++ this._workspacesItems[this._currentWorkspace].setOrnament(PopupMenu.Ornament.DOT); + +- this.statusLabel.set_text(this._labelText()); ++ this._statusLabel.set_text(this._labelText()); + } + + _labelText(workspaceIndex) { +@@ -86,24 +86,24 @@ class WorkspaceIndicator extends PanelMenu.Button { + let workspaceManager = global.workspace_manager; + + this._workspaceSection.removeAll(); +- this.workspacesItems = []; ++ this._workspacesItems = []; + this._currentWorkspace = workspaceManager.get_active_workspace().index(); + + let i = 0; + for (; i < workspaceManager.n_workspaces; i++) { +- this.workspacesItems[i] = new PopupMenu.PopupMenuItem(this._labelText(i)); +- this._workspaceSection.addMenuItem(this.workspacesItems[i]); +- this.workspacesItems[i].workspaceId = i; +- this.workspacesItems[i].label_actor = this.statusLabel; +- this.workspacesItems[i].connect('activate', (actor, _event) => { ++ this._workspacesItems[i] = new PopupMenu.PopupMenuItem(this._labelText(i)); ++ this._workspaceSection.addMenuItem(this._workspacesItems[i]); ++ this._workspacesItems[i].workspaceId = i; ++ this._workspacesItems[i].label_actor = this._statusLabel; ++ this._workspacesItems[i].connect('activate', (actor, _event) => { + this._activate(actor.workspaceId); + }); + + if (i == this._currentWorkspace) +- this.workspacesItems[i].setOrnament(PopupMenu.Ornament.DOT); ++ this._workspacesItems[i].setOrnament(PopupMenu.Ornament.DOT); + } + +- this.statusLabel.set_text(this._labelText()); ++ this._statusLabel.set_text(this._labelText()); + } + + _activate(index) { +-- +2.21.0 + + +From 2b1cb8c6b27d63ed7093f4b171f0837a193061f5 Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Florian=20M=C3=BCllner?= +Date: Sun, 9 Jun 2019 23:03:55 +0000 +Subject: [PATCH 18/22] workspace-indicator: Update workspace names in-place + +There's no good reason to rebuild the entire menu on workspace names +changes, we can simply update the labels in-place. + +https://gitlab.gnome.org/GNOME/gnome-shell-extensions/merge_requests/71 +--- + extensions/workspace-indicator/extension.js | 11 ++++++++--- + 1 file changed, 8 insertions(+), 3 deletions(-) + +diff --git a/extensions/workspace-indicator/extension.js b/extensions/workspace-indicator/extension.js +index 76224b9..6bcac0c 100644 +--- a/extensions/workspace-indicator/extension.js ++++ b/extensions/workspace-indicator/extension.js +@@ -49,9 +49,9 @@ class WorkspaceIndicator extends PanelMenu.Button { + this._statusLabel.add_style_class_name('panel-workspace-indicator'); + + this._settings = new Gio.Settings({ schema_id: WORKSPACE_SCHEMA }); +- this._settingsChangedId = +- this._settings.connect(`changed::${WORKSPACE_KEY}`, +- this._createWorkspacesSection.bind(this)); ++ this._settingsChangedId = this._settings.connect( ++ `changed::${WORKSPACE_KEY}`, ++ this._updateMenuLabels.bind(this)); + } + + _onDestroy() { +@@ -82,6 +82,11 @@ class WorkspaceIndicator extends PanelMenu.Button { + return Meta.prefs_get_workspace_name(workspaceIndex); + } + ++ _updateMenuLabels() { ++ for (let i = 0; i < this._workspacesItems.length; i++) ++ this._workspacesItems[i].label.text = this._labelText(i); ++ } ++ + _createWorkspacesSection() { + let workspaceManager = global.workspace_manager; + +-- +2.21.0 + + +From a16a7c47475a7d19da43597b810ea9224af745b4 Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Florian=20M=C3=BCllner?= +Date: Sun, 9 Jun 2019 23:05:00 +0000 +Subject: [PATCH 19/22] workspace-indicator: Minor cleanup + +Mutter has a dedicated method for getting the index of the active +workspace, use that instead of getting first the active workspace +and then its index. + +https://gitlab.gnome.org/GNOME/gnome-shell-extensions/merge_requests/71 +--- + extensions/workspace-indicator/extension.js | 8 ++++---- + 1 file changed, 4 insertions(+), 4 deletions(-) + +diff --git a/extensions/workspace-indicator/extension.js b/extensions/workspace-indicator/extension.js +index 6bcac0c..dc31a5c 100644 +--- a/extensions/workspace-indicator/extension.js ++++ b/extensions/workspace-indicator/extension.js +@@ -22,7 +22,7 @@ class WorkspaceIndicator extends PanelMenu.Button { + + let workspaceManager = global.workspace_manager; + +- this._currentWorkspace = workspaceManager.get_active_workspace().index(); ++ this._currentWorkspace = workspaceManager.get_active_workspace_index(); + this._statusLabel = new St.Label({ + y_align: Clutter.ActorAlign.CENTER, + text: this._labelText() +@@ -68,7 +68,7 @@ class WorkspaceIndicator extends PanelMenu.Button { + + _updateIndicator() { + this._workspacesItems[this._currentWorkspace].setOrnament(PopupMenu.Ornament.NONE); +- this._currentWorkspace = global.workspace_manager.get_active_workspace().index(); ++ this._currentWorkspace = global.workspace_manager.get_active_workspace_index(); + this._workspacesItems[this._currentWorkspace].setOrnament(PopupMenu.Ornament.DOT); + + this._statusLabel.set_text(this._labelText()); +@@ -92,7 +92,7 @@ class WorkspaceIndicator extends PanelMenu.Button { + + this._workspaceSection.removeAll(); + this._workspacesItems = []; +- this._currentWorkspace = workspaceManager.get_active_workspace().index(); ++ this._currentWorkspace = workspaceManager.get_active_workspace_index(); + + let i = 0; + for (; i < workspaceManager.n_workspaces; i++) { +@@ -131,7 +131,7 @@ class WorkspaceIndicator extends PanelMenu.Button { + return; + } + +- let newIndex = global.workspace_manager.get_active_workspace().index() + diff; ++ let newIndex = global.workspace_manager.get_active_workspace_index() + diff; + this._activate(newIndex); + } + }); +-- +2.21.0 + + +From 707dc4df33efd9779e0839c816c1bfd1efffc699 Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Florian=20M=C3=BCllner?= +Date: Sun, 9 Jun 2019 23:09:12 +0000 +Subject: [PATCH 20/22] workspace-indicator: Refactor workspace signal handlers + +We are about to support a separate representation if horizontal +workspaces are used. To prepare for that, rename the handlers to +something more generic and split out menu-specific bits into a +dedicated help function. + +https://gitlab.gnome.org/GNOME/gnome-shell-extensions/merge_requests/71 +--- + extensions/workspace-indicator/extension.js | 31 ++++++++++++++------- + 1 file changed, 21 insertions(+), 10 deletions(-) + +diff --git a/extensions/workspace-indicator/extension.js b/extensions/workspace-indicator/extension.js +index dc31a5c..1961b1c 100644 +--- a/extensions/workspace-indicator/extension.js ++++ b/extensions/workspace-indicator/extension.js +@@ -34,13 +34,12 @@ class WorkspaceIndicator extends PanelMenu.Button { + this._workspaceSection = new PopupMenu.PopupMenuSection(); + this.menu.addMenuItem(this._workspaceSection); + +- this._workspaceManagerSignals = []; +- this._workspaceManagerSignals.push(workspaceManager.connect_after('workspace-added', +- this._createWorkspacesSection.bind(this))); +- this._workspaceManagerSignals.push(workspaceManager.connect_after('workspace-removed', +- this._createWorkspacesSection.bind(this))); +- this._workspaceManagerSignals.push(workspaceManager.connect_after('workspace-switched', +- this._updateIndicator.bind(this))); ++ this._workspaceManagerSignals = [ ++ workspaceManager.connect_after('notify::n-workspaces', ++ this._nWorkspacesChanged.bind(this)), ++ workspaceManager.connect_after('workspace-switched', ++ this._onWorkspaceSwitched.bind(this)) ++ ]; + + this.connect('scroll-event', this._onScrollEvent.bind(this)); + this._createWorkspacesSection(); +@@ -66,14 +65,26 @@ class WorkspaceIndicator extends PanelMenu.Button { + super._onDestroy(); + } + +- _updateIndicator() { +- this._workspacesItems[this._currentWorkspace].setOrnament(PopupMenu.Ornament.NONE); ++ _onWorkspaceSwitched() { + this._currentWorkspace = global.workspace_manager.get_active_workspace_index(); +- this._workspacesItems[this._currentWorkspace].setOrnament(PopupMenu.Ornament.DOT); ++ ++ this._updateMenuOrnament(); + + this._statusLabel.set_text(this._labelText()); + } + ++ _nWorkspacesChanged() { ++ this._createWorkspacesSection(); ++ } ++ ++ _updateMenuOrnament() { ++ for (let i = 0; i < this._workspacesItems.length; i++) { ++ this._workspacesItems[i].setOrnament(i == this._currentWorkspace ++ ? PopupMenu.Ornament.DOT ++ : PopupMenu.Ornament.NONE); ++ } ++ } ++ + _labelText(workspaceIndex) { + if (workspaceIndex == undefined) { + workspaceIndex = this._currentWorkspace; +-- +2.21.0 + + +From 83ad6a01f58ae451cc3086a01cdfd500eaa536d2 Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Florian=20M=C3=BCllner?= +Date: Sun, 9 Jun 2019 23:17:35 +0000 +Subject: [PATCH 21/22] workspace-indicator: Minor cleanup + +Pass the style class at construction time instead of setting it later. + +https://gitlab.gnome.org/GNOME/gnome-shell-extensions/merge_requests/71 +--- + extensions/workspace-indicator/extension.js | 4 +--- + 1 file changed, 1 insertion(+), 3 deletions(-) + +diff --git a/extensions/workspace-indicator/extension.js b/extensions/workspace-indicator/extension.js +index 1961b1c..18f5450 100644 +--- a/extensions/workspace-indicator/extension.js ++++ b/extensions/workspace-indicator/extension.js +@@ -24,6 +24,7 @@ class WorkspaceIndicator extends PanelMenu.Button { + + this._currentWorkspace = workspaceManager.get_active_workspace_index(); + this._statusLabel = new St.Label({ ++ style_class: 'panel-workspace-indicator', + y_align: Clutter.ActorAlign.CENTER, + text: this._labelText() + }); +@@ -44,9 +45,6 @@ class WorkspaceIndicator extends PanelMenu.Button { + this.connect('scroll-event', this._onScrollEvent.bind(this)); + this._createWorkspacesSection(); + +- //styling +- this._statusLabel.add_style_class_name('panel-workspace-indicator'); +- + this._settings = new Gio.Settings({ schema_id: WORKSPACE_SCHEMA }); + this._settingsChangedId = this._settings.connect( + `changed::${WORKSPACE_KEY}`, +-- +2.21.0 + + +From 4e128c9cbc631fa47f9e5959212c35d9c6d53a01 Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Florian=20M=C3=BCllner?= +Date: Sun, 9 Jun 2019 23:45:24 +0000 +Subject: [PATCH 22/22] workspace-indicator: Support horizontal workspace + layout + +Just like we did for the workspace indicator in the window-list, improve +the handling of horizontal workspace layouts by showing the switcher +in-place instead of delegating the functionality to a menu. + +https://gitlab.gnome.org/GNOME/gnome-shell-extensions/merge_requests/71 +--- + extensions/workspace-indicator/extension.js | 63 ++++++++++++++++++- + extensions/workspace-indicator/stylesheet.css | 18 +++++- + 2 files changed, 77 insertions(+), 4 deletions(-) + +diff --git a/extensions/workspace-indicator/extension.js b/extensions/workspace-indicator/extension.js +index 18f5450..5f7cb7c 100644 +--- a/extensions/workspace-indicator/extension.js ++++ b/extensions/workspace-indicator/extension.js +@@ -20,6 +20,13 @@ class WorkspaceIndicator extends PanelMenu.Button { + _init() { + super._init(0.0, _('Workspace Indicator')); + ++ let container = new St.Widget({ ++ layout_manager: new Clutter.BinLayout(), ++ x_expand: true, ++ y_expand: true ++ }); ++ this.add_actor(container); ++ + let workspaceManager = global.workspace_manager; + + this._currentWorkspace = workspaceManager.get_active_workspace_index(); +@@ -29,7 +36,15 @@ class WorkspaceIndicator extends PanelMenu.Button { + text: this._labelText() + }); + +- this.add_actor(this._statusLabel); ++ container.add_actor(this._statusLabel); ++ ++ this._thumbnailsBox = new St.BoxLayout({ ++ style_class: 'panel-workspace-indicator-box', ++ y_expand: true, ++ reactive: true ++ }); ++ ++ container.add_actor(this._thumbnailsBox); + + this._workspacesItems = []; + this._workspaceSection = new PopupMenu.PopupMenuSection(); +@@ -39,11 +54,16 @@ class WorkspaceIndicator extends PanelMenu.Button { + workspaceManager.connect_after('notify::n-workspaces', + this._nWorkspacesChanged.bind(this)), + workspaceManager.connect_after('workspace-switched', +- this._onWorkspaceSwitched.bind(this)) ++ this._onWorkspaceSwitched.bind(this)), ++ workspaceManager.connect('notify::layout-rows', ++ this._onWorkspaceOrientationChanged.bind(this)) + ]; + + this.connect('scroll-event', this._onScrollEvent.bind(this)); ++ this._thumbnailsBox.connect('scroll-event', this._onScrollEvent.bind(this)); + this._createWorkspacesSection(); ++ this._updateThumbnails(); ++ this._onWorkspaceOrientationChanged(); + + this._settings = new Gio.Settings({ schema_id: WORKSPACE_SCHEMA }); + this._settingsChangedId = this._settings.connect( +@@ -63,16 +83,26 @@ class WorkspaceIndicator extends PanelMenu.Button { + super._onDestroy(); + } + ++ _onWorkspaceOrientationChanged() { ++ let vertical = global.workspace_manager.layout_rows == -1; ++ this.reactive = vertical; ++ ++ this._statusLabel.visible = vertical; ++ this._thumbnailsBox.visible = !vertical; ++ } ++ + _onWorkspaceSwitched() { + this._currentWorkspace = global.workspace_manager.get_active_workspace_index(); + + this._updateMenuOrnament(); ++ this._updateActiveThumbnail(); + + this._statusLabel.set_text(this._labelText()); + } + + _nWorkspacesChanged() { + this._createWorkspacesSection(); ++ this._updateThumbnails(); + } + + _updateMenuOrnament() { +@@ -83,6 +113,16 @@ class WorkspaceIndicator extends PanelMenu.Button { + } + } + ++ _updateActiveThumbnail() { ++ let thumbs = this._thumbnailsBox.get_children(); ++ for (let i = 0; i < thumbs.length; i++) { ++ if (i == this._currentWorkspace) ++ thumbs[i].add_style_class_name('active'); ++ else ++ thumbs[i].remove_style_class_name('active'); ++ } ++ } ++ + _labelText(workspaceIndex) { + if (workspaceIndex == undefined) { + workspaceIndex = this._currentWorkspace; +@@ -120,6 +160,25 @@ class WorkspaceIndicator extends PanelMenu.Button { + this._statusLabel.set_text(this._labelText()); + } + ++ _updateThumbnails() { ++ let workspaceManager = global.workspace_manager; ++ ++ this._thumbnailsBox.destroy_all_children(); ++ ++ for (let i = 0; i < workspaceManager.n_workspaces; i++) { ++ let thumb = new St.Button({ ++ style_class: 'workspace', ++ child: new St.Label({ text: `${i + 1}` }) ++ }); ++ thumb.connect('clicked', () => { ++ this._activate(i); ++ }); ++ ++ this._thumbnailsBox.add_actor(thumb); ++ } ++ this._updateActiveThumbnail(); ++ } ++ + _activate(index) { + let workspaceManager = global.workspace_manager; + +diff --git a/extensions/workspace-indicator/stylesheet.css b/extensions/workspace-indicator/stylesheet.css +index 1271f1c..a15081e 100644 +--- a/extensions/workspace-indicator/stylesheet.css ++++ b/extensions/workspace-indicator/stylesheet.css +@@ -1,5 +1,19 @@ +-.panel-workspace-indicator { ++.panel-workspace-indicator, ++.panel-workspace-indicator-box .workspace { + padding: 0 8px; +- background-color: rgba(200, 200, 200, .5); + border: 1px solid #cccccc; + } ++ ++.panel-workspace-indicator, ++.panel-workspace-indicator-box .workspace.active { ++ background-color: rgba(200, 200, 200, .5); ++} ++ ++.panel-workspace-indicator-box .workspace { ++ background-color: rgba(200, 200, 200, .3); ++ border-left-width: 0; ++} ++ ++.panel-workspace-indicator-box .workspace:first-child { ++ border-left-width: 1px; ++} +-- +2.21.0 + diff --git a/SOURCES/resurrect-system-monitor.patch b/SOURCES/resurrect-system-monitor.patch new file mode 100644 index 0000000..9c4daf4 --- /dev/null +++ b/SOURCES/resurrect-system-monitor.patch @@ -0,0 +1,1311 @@ +From 7c3b0af4fde0b542089f2b0c84250404eef0ecca 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/5] 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 6e8c41f..6764f9a 100644 +--- a/meson.build ++++ b/meson.build +@@ -55,6 +55,7 @@ all_extensions += [ + 'native-window-placement', + 'no-hot-corner', + 'panel-favorites', ++ 'systemMonitor', + 'top-icons', + 'updates-dialog', + 'user-theme', +-- +2.21.0 + + +From ddf4d70df56321366a2cb8b89689d59be4dbb718 Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Florian=20M=C3=BCllner?= +Date: Fri, 17 May 2019 22:55:48 +0000 +Subject: [PATCH 2/5] systemMonitor: Modernise code + + - port to ES6 classes + - replace Lang.bind() + - destructure imports + - fix style issues (stray/missing spaces/semi-colons, indent, ...) +--- + extensions/systemMonitor/extension.js | 377 +++++++++++++------------- + 1 file changed, 192 insertions(+), 185 deletions(-) + +diff --git a/extensions/systemMonitor/extension.js b/extensions/systemMonitor/extension.js +index 7b09df0..89f8916 100644 +--- a/extensions/systemMonitor/extension.js ++++ b/extensions/systemMonitor/extension.js +@@ -1,22 +1,16 @@ + /* -*- 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; ++/* exported init */ + ++const { Clutter, GLib, GTop, Shell, St } = imports.gi; ++ ++const ExtensionUtils = imports.misc.extensionUtils; + 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; + +@@ -24,32 +18,38 @@ 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() { ++const Indicator = class { ++ constructor() { + 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() { ++ this._drawingArea = new St.DrawingArea({ reactive: true }); ++ this._drawingArea.connect('repaint', this._draw.bind(this)); ++ this._drawingArea.connect('button-press-event', () => { + 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.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._drawingArea); + +- this._timeout = Mainloop.timeout_add(INDICATOR_UPDATE_INTERVAL, Lang.bind(this, function () { +- this._updateValues(); +- this.drawing_area.queue_repaint(); +- return true; +- })); +- }, ++ this.actor.connect('destroy', this._onDestroy.bind(this)); + +- showLabel: function() { ++ this._timeout = GLib.timeout_add(GLib.PRIORITY_DEFAULT, ++ INDICATOR_UPDATE_INTERVAL, ++ () => { ++ this._updateValues(); ++ this._drawingArea.queue_repaint(); ++ return GLib.SOURCE_CONTINUE; ++ }); ++ } ++ ++ showLabel() { + if (this.label == null) + return; + +@@ -58,12 +58,10 @@ const Indicator = new Lang.Class({ + + 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 itemWidth = this.actor.allocation.x2 - this.actor.allocation.x1; + +- let labelWidth = this.label.width; +- let labelHeight = this.label.height; +- let xOffset = Math.floor((itemWidth - labelWidth) / 2) ++ let labelWidth = this.label.width; ++ let xOffset = Math.floor((itemWidth - labelWidth) / 2); + + let x = stageX + xOffset; + +@@ -73,48 +71,51 @@ const Indicator = new Lang.Class({ + 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) { ++ Tweener.addTween(this.label, { ++ opacity: 255, ++ time: ITEM_LABEL_SHOW_TIME, ++ transition: 'easeOutQuad', ++ }); ++ } ++ ++ setLabelText(text) { + if (this.label == null) +- this.label = new St.Label({ style_class: 'extension-systemMonitor-indicator-label'}); ++ 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); ++ } + ++ hideLabel() { ++ Tweener.addTween(this.label, { ++ opacity: 0, ++ time: ITEM_LABEL_HIDE_TIME, ++ transition: 'easeOutQuad', ++ onComplete: () => this.label.hide() ++ }); ++ } ++ ++ destroy() { + this.actor.destroy(); +- if (this.label) +- this.label.destroy(); +- }, ++ } ++ ++ _onDestroy() { ++ GLib.source_remove(this._timeout); ++ ++ if (this.label) ++ this.label.destroy(); ++ } + +- _initValues: function() { +- }, ++ _initValues() { ++ } + +- _updateValues: function() { +- }, ++ _updateValues() { ++ } + +- _draw: function(area) { ++ _draw(area) { + let [width, height] = area.get_surface_size(); + let themeNode = this.actor.get_theme_node(); + let cr = area.get_context(); +@@ -123,12 +124,12 @@ const Indicator = new Lang.Class({ + 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); ++ 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.setDash([4, 1], 0); + cr.stroke(); + + //draw the foreground +@@ -155,12 +156,12 @@ const Indicator = new Lang.Class({ + let renderStats = this.renderStats; + + // Make sure we don't have more sample points than pixels +- renderStats.map(Lang.bind(this, function(k){ ++ renderStats.map(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]]; +@@ -178,10 +179,10 @@ const Indicator = new Lang.Class({ + cr.lineTo(0, height); + cr.closePath(); + } else { +- let nextStat = this.stats[renderStats[i+1]]; ++ let nextStat = this.stats[renderStats[i + 1]]; + makePath(nextStat.values, true); + } +- cr.closePath() ++ cr.closePath(); + Clutter.cairo_set_source_color(cr, color); + cr.fill(); + +@@ -193,40 +194,42 @@ const Indicator = new Lang.Class({ + cr.stroke(); + } + } +-}); +- +-const CpuIndicator = new Lang.Class({ +- Name: 'SystemMonitor.CpuIndicator', +- Extends: Indicator, ++}; + +- _init: function() { +- this.parent(); ++const CpuIndicator = class extends Indicator { ++ constructor() { ++ super(); + + this.gridColor = '-grid-color'; +- this.renderStats = [ 'cpu-user', 'cpu-sys', 'cpu-iowait' ]; ++ 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) { ++ let renderStatOrder = { ++ 'cpu-total': 0, ++ 'cpu-user': 1, ++ 'cpu-sys': 2, ++ 'cpu-iowait': 3 ++ }; ++ this.renderStats = this.renderStats.sort((a, b) => { + return renderStatOrder[a] - renderStatOrder[b]; + }); + +- this.setLabelText(_("CPU")); +- }, ++ this.setLabelText(_('CPU')); ++ } + +- _initValues: function() { ++ _initValues() { + 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() { ++ '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() { + let cpu = new GTop.glibtop_cpu; + let t = 0.0; + GTop.glibtop_get_cpu(cpu); +@@ -246,37 +249,34 @@ const CpuIndicator = new Lang.Class({ + + this._prev = cpu; + } +-}); ++}; + +-const MemoryIndicator = new Lang.Class({ +- Name: 'SystemMonitor.MemoryIndicator', +- Extends: Indicator, +- +- _init: function() { +- this.parent(); ++const MemoryIndicator = class extends Indicator { ++ constructor() { ++ super(); + + this.gridColor = '-grid-color'; +- this.renderStats = [ 'mem-user', 'mem-other', 'mem-cached' ]; ++ 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) { ++ this.renderStats = this.renderStats.sort((a, b) => { + return renderStatOrder[a] - renderStatOrder[b]; + }); + +- this.setLabelText(_("Memory")); +- }, ++ this.setLabelText(_('Memory')); ++ } + +- _initValues: function() { ++ _initValues() { + 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: [] } +- }; +- }, ++ 'mem-user': { color: '-mem-user-color', values: [] }, ++ 'mem-other': { color: '-mem-other-color', values: [] }, ++ 'mem-cached': { color: '-mem-cached-color', values: [] } ++ }; ++ } + +- _updateValues: function() { ++ _updateValues() { + GTop.glibtop_get_mem(this.mem); + + let t = this.mem.user / this.mem.total; +@@ -286,90 +286,97 @@ const MemoryIndicator = new Lang.Class({ + 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) { ++class Extension { ++ constructor() { ++ ExtensionUtils.initTranslations(); ++ ++ this._showLabelTimeoutId = 0; ++ this._resetHoverTimeoutId = 0; ++ this._labelShowing = false; ++ } ++ ++ enable() { ++ 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', () => { ++ 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() { ++ this._indicators.forEach(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(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; +- } ++ if (this._showLabelTimeoutId) ++ return; ++ ++ let timeout = this._labelShowing ? 0 : ITEM_HOVER_TIMEOUT; ++ this._showLabelTimeoutId = GLib.timeout_add(GLib.PRIORITY_DEFAULT, ++ timeout, ++ () => { ++ this._labelShowing = true; ++ item.showLabel(); ++ this._showLabelTimeoutId = 0; ++ return GLib.SOURCE_REMOVE; ++ }); ++ ++ if (this._resetHoverTimeoutId > 0) { ++ GLib.source_remove(this._resetHoverTimeoutId); ++ this._resetHoverTimeoutId = 0; + } + } else { + if (this._showLabelTimeoutId > 0) +- Mainloop.source_remove(this._showLabelTimeoutId); ++ GLib.source_remove(this._showLabelTimeoutId); + this._showLabelTimeoutId = 0; + item.hideLabel(); +- if (this._labelShowing) { +- this._resetHoverTimeoutId = Mainloop.timeout_add(ITEM_HOVER_TIMEOUT, +- Lang.bind(this, function() { +- this._labelShowing = false; +- return false; +- })); +- } ++ if (!this._labelShowing) ++ return; ++ ++ this._resetHoverTimeoutId = GLib.timeout_add(GLib.PRIORITY_DEFAULT, ++ ITEM_HOVER_TIMEOUT, ++ () => { ++ this._labelShowing = false; ++ return GLib.SOURCE_REMOVE; ++ }); + } +- }, +-}); ++ } ++} + + function init() { + return new Extension(); +-- +2.21.0 + + +From e7ea49cd416e8ede9767f5ade46a06764d1e9a5b 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 3/5] 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 | 65 ++++++++++++------------- + extensions/systemMonitor/stylesheet.css | 14 ------ + 2 files changed, 31 insertions(+), 48 deletions(-) + +diff --git a/extensions/systemMonitor/extension.js b/extensions/systemMonitor/extension.js +index 89f8916..0188960 100644 +--- a/extensions/systemMonitor/extension.js ++++ b/extensions/systemMonitor/extension.js +@@ -3,9 +3,11 @@ + /* exported init */ + + const { Clutter, GLib, GTop, Shell, St } = imports.gi; ++const Signals = imports.signals; + + const ExtensionUtils = imports.misc.extensionUtils; + const Main = imports.ui.main; ++const MessageList = imports.ui.messageList; + const Tweener = imports.ui.tweener; + + const Gettext = imports.gettext.domain('gnome-shell-extensions'); +@@ -21,22 +23,25 @@ const ITEM_HOVER_TIMEOUT = 300; + const Indicator = class { + constructor() { + this._initValues(); +- this._drawingArea = new St.DrawingArea({ reactive: true }); ++ this._drawingArea = new St.DrawingArea(); + this._drawingArea.connect('repaint', this._draw.bind(this)); +- this._drawingArea.connect('button-press-event', () => { ++ ++ this.actor = new St.Button({ ++ style_class: 'message message-content extension-systemMonitor-indicator-area', ++ child: this._drawingArea, ++ x_expand: true, ++ x_fill: true, ++ y_fill: true, ++ can_focus: true ++ }); ++ ++ this.actor.connect('clicked', () => { + 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 ++ Main.overview.hide(); ++ Main.panel.closeCalendar(); + }); +- this.actor.add_actor(this._drawingArea); + + this.actor.connect('destroy', this._onDestroy.bind(this)); + +@@ -71,6 +76,7 @@ const Indicator = 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, +@@ -98,6 +104,14 @@ const Indicator = class { + }); + } + ++ /* MessageList.Message boilerplate */ ++ canClose() { ++ return false; ++ } ++ ++ clear() { ++ } ++ + destroy() { + this.actor.destroy(); + } +@@ -195,6 +209,7 @@ const Indicator = class { + } + } + }; ++Signals.addSignalMethods(Indicator.prototype); // For MessageList.Message compat + + const CpuIndicator = class extends Indicator { + constructor() { +@@ -300,11 +315,7 @@ class Extension { + } + + enable() { +- 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,32 +324,18 @@ class Extension { + indicator.actor.connect('notify::hover', () => { + 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() { + this._indicators.forEach(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(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.21.0 + + +From f73fe9cfb5f9dbd6647e4eb30a9af0fb7ff79219 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 4/5] 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: https://gitlab.gnome.org/GNOME/gnome-shell-extensions3 +--- + extensions/systemMonitor/extension.js | 17 ++++++++++++++++- + 1 file changed, 16 insertions(+), 1 deletion(-) + +diff --git a/extensions/systemMonitor/extension.js b/extensions/systemMonitor/extension.js +index 0188960..b4d5a9d 100644 +--- a/extensions/systemMonitor/extension.js ++++ b/extensions/systemMonitor/extension.js +@@ -303,6 +303,21 @@ const MemoryIndicator = class extends Indicator { + } + }; + ++class SystemMonitorSection extends MessageList.MessageListSection { ++ constructor() { ++ super(_('System Monitor')); ++ } ++ ++ _onTitleClicked() { ++ super._onTitleClicked(); ++ ++ 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]; + + class Extension { +@@ -315,7 +330,7 @@ class Extension { + } + + enable() { +- this._section = new MessageList.MessageListSection(_('System Monitor')); ++ this._section = new SystemMonitorSection(); + this._indicators = []; + + for (let i = 0; i < INDICATORS.length; i++) { +-- +2.21.0 + + +From df76e98d6bbac7dccc86f66e82eac2977fb5ed87 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 5/5] 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.21.0 + diff --git a/SPECS/gnome-shell-extensions.spec b/SPECS/gnome-shell-extensions.spec new file mode 100644 index 0000000..f7e22e3 --- /dev/null +++ b/SPECS/gnome-shell-extensions.spec @@ -0,0 +1,978 @@ +%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.32.1 +Release: 5%{?dist} +Summary: Modify and extend GNOME Shell functionality and behavior + +Group: User Interface/Desktops +License: GPLv2+ +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-Include-top-icons-in-classic-session.patch +Patch0008: more-classic-classic-mode.patch + +%description +GNOME Shell Extensions is a collection of extensions providing additional and +optional functionality to GNOME Shell. + +Enabled extensions: + * apps-menu + * auto-move-windows + * dash-to-dock + * disable-screenshield + * desktop-icons + * horizontal-workspaces + * 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 +Obsoletes: %{pkg_prefix}-alternate-tab < 3.31.2 +# 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}-apps-menu = %{version}-%{release} +Requires: %{pkg_prefix}-desktop-icons = %{version}-%{release} +Requires: %{pkg_prefix}-horizontal-workspaces = %{version}-%{release} +Requires: %{pkg_prefix}-launch-new-instance = %{version}-%{release} +Requires: %{pkg_prefix}-places-menu = %{version}-%{release} +Requires: %{pkg_prefix}-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}-apps-menu +Summary: Application menu for GNOME Shell +Group: User Interface/Desktops +License: GPLv2+ +Requires: %{pkg_prefix}-common = %{version}-%{release} +Requires: gnome-menus + +%description -n %{pkg_prefix}-apps-menu +This GNOME Shell extension adds a GNOME 2.x style menu for applications. + + +%package -n %{pkg_prefix}-auto-move-windows +Summary: Assign specific workspaces to applications in GNOME Shell +Group: User Interface/Desktops +License: GPLv2+ +Requires: %{pkg_prefix}-common = %{version}-%{release} + +%description -n %{pkg_prefix}-auto-move-windows +This GNOME Shell extension enables easy workspace management. A specific +workspace can be assigned to each application as soon as it creates a window, in +a manner configurable with a GSettings key. + + +%package -n %{pkg_prefix}-dash-to-dock +Summary: Show the dash outside the activities overview +Group: User Interface/Desktops +License: GPLv2+ +Requires: %{pkg_prefix}-common = %{version}-%{release} + +%description -n %{pkg_prefix}-dash-to-dock +This GNOME Shell extension makes the dash available outside the activities overview. + + +%package -n %{pkg_prefix}-disable-screenshield +Summary: Disable GNOME Shell screen shield if lock is disabled +Group: User Interface/Desktops +License: GPLv2+ +Requires: %{pkg_prefix}-common = %{version}-%{release} + +%description -n %{pkg_prefix}-disable-screenshield +This GNOME Shell extension disabled the screen shield if screen locking is disabled. + + +%package -n %{pkg_prefix}-horizontal-workspaces +Summary: Desktop icons support for the classic experience +Group: User Interface/Desktops +License: GPLv3+ +Requires: %{pkg_prefix}-common = %{version}-%{release} + +%description -n %{pkg_prefix}-horizontal-workspaces +This GNOME Shell extension adds desktop icons support as seen in GNOME 2 + + +%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-grouper +Summary: Keep windows that belong to the same process on the same workspace +Group: User Interface/Desktops +License: GPLv2+ +Requires: %{pkg_prefix}-common = %{version}-%{release} + +%description -n %{pkg_prefix}-window-grouper +This GNOME Shell extension keeps windows that belong to the same process on the same workspace. + + +%package -n %{pkg_prefix}-window-list +Summary: Display a window list at the bottom of the screen in GNOME Shell +Group: User Interface/Desktops +License: GPLv2+ +Requires: %{pkg_prefix}-common = %{version}-%{release} + +%description -n %{pkg_prefix}-window-list +This GNOME Shell extension displays a window list at the bottom of the screen. + + +%package -n %{pkg_prefix}-windowsNavigator +Summary: Support for keyboard selection of windows and workspaces in GNOME Shell +Group: User Interface/Desktops +License: GPLv2+ +Requires: %{pkg_prefix}-common = %{version}-%{release} + +%description -n %{pkg_prefix}-windowsNavigator +This GNOME Shell extension enables keyboard selection of windows and workspaces +in overlay mode, by pressing the Alt and Ctrl key respectively. + + +%package -n %{pkg_prefix}-workspace-indicator +Summary: Workspace indicator for GNOME Shell +Group: User Interface/Desktops +License: GPLv2+ +Requires: %{pkg_prefix}-common = %{version}-%{release} + +%description -n %{pkg_prefix}-workspace-indicator +This GNOME Shell extension add a system status menu for quickly changing +workspaces. + + +%prep +%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 + +%find_lang %{name} + + +%files -n %{pkg_prefix}-common -f %{name}.lang +%doc NEWS README.md +%license COPYING + + +%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/00_org.gnome.shell.extensions.classic.gschema.override + +%files -n %{pkg_prefix}-apps-menu +%{_datadir}/gnome-shell/extensions/apps-menu*/ + + +%files -n %{pkg_prefix}-auto-move-windows +%{_datadir}/glib-2.0/schemas/org.gnome.shell.extensions.auto-move-windows.gschema.xml +%{_datadir}/gnome-shell/extensions/auto-move-windows*/ + + +%files -n %{pkg_prefix}-dash-to-dock +%{_datadir}/glib-2.0/schemas/org.gnome.shell.extensions.dash-to-dock.gschema.xml +%{_datadir}/gnome-shell/extensions/dash-to-dock*/ + + +%files -n %{pkg_prefix}-disable-screenshield +%{_datadir}/gnome-shell/extensions/disable-screenshield*/ + + +%files -n %{pkg_prefix}-horizontal-workspaces +%{_datadir}/gnome-shell/extensions/horizontal-workspaces*/ + + +%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-grouper +%{_datadir}/gnome-shell/extensions/window-grouper*/ +%{_datadir}/glib-2.0/schemas/org.gnome.shell.extensions.window-grouper.gschema.xml + + +%files -n %{pkg_prefix}-window-list +%{_datadir}/gnome-shell/extensions/window-list*/ +%{_datadir}/glib-2.0/schemas/org.gnome.shell.extensions.window-list.gschema.xml + + +%files -n %{pkg_prefix}-windowsNavigator +%{_datadir}/gnome-shell/extensions/windowsNavigator*/ + + +%files -n %{pkg_prefix}-workspace-indicator +%{_datadir}/gnome-shell/extensions/workspace-indicator*/ + + +%changelog +* Tue Jun 18 2019 Florian Müllner - 3.32.1-5 +- Small refinements after design feedback: + - use default icon size in picker button to avoid blurriness + - use shortcut to open window picker + Resolves: #1721195 + +* Tue Jun 18 2019 Florian Müllner - 3.32.1-4 +- Don't add apps-menu logo when activities button is present + Resolves: #1721195 + +* Wed Jun 12 2019 Florian Müllner - 3.32.1-3 +- Make classic mode more classic + Resolves: #1704360 + +* Fri May 31 2019 Florian Müllner - 3.32.1-2 +- Fix top-icons sizing issue + Resolves: #1715765 + +* Thu May 23 2019 Florian Müllner - 3.32.1-1 +- Update to 3.32.1 + Resolves: #1713453 + +* 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