From 23d293d12ed22033441609e1a047196dbeca3f1c Mon Sep 17 00:00:00 2001 From: Andrea Azzarone Date: Fri, 1 Sep 2017 22:45:43 +0200 Subject: [PATCH 01/11] Implement a notification badge in MyAppIcon --- appIcons.js | 40 ++++++++++++++++++++++++++++++++++++++++ stylesheet.css | 10 ++++++++++ 2 files changed, 50 insertions(+) diff --git a/appIcons.js b/appIcons.js index 1b1f3b3f9..bb30004a9 100644 --- a/appIcons.js +++ b/appIcons.js @@ -145,6 +145,7 @@ const MyAppIcon = new Lang.Class({ this._optionalScrollCycleWindows(); this._numberOverlay(); + this._notificationBadge(); this._previewMenuManager = null; this._previewMenu = null; @@ -938,6 +939,45 @@ const MyAppIcon = new Lang.Class({ cr.$dispose(); }, + _notificationBadge: function() { + 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._iconContainer.add_child(this._notificationBadgeBin); + this._iconContainer.connect('allocation-changed', Lang.bind(this, this.updateNotificationBadge)); + }, + + updateNotificationBadge: function() { + let scaleFactor = St.ThemeContext.get_for_stage(global.stage).scale_factor; + let [minWidth, natWidth] = this._iconContainer.get_preferred_width(-1); + let font_size = Math.max(10, Math.round(natWidth / scaleFactor / 5)); + + this._notificationBadgeLabel.set_style( + 'font-size: ' + font_size + 'px;' + ); + }, + + setNotificationBadge: function(count) { + this._notificationBadgeCount = count; + this._notificationBadgeLabel.set_text(count.toString()); + }, + + toggleNotificationBadge: function(activate) { + if (activate && this._notificationBadgeCount > 0) { + this.updateNotificationBadge(); + this._notificationBadgeBin.show(); + } + else + this._notificationBadgeBin.hide(); + }, + _numberOverlay: function() { // Add label for a Hot-Key visual aid this._numberOverlayLabel = new St.Label(); diff --git a/stylesheet.css b/stylesheet.css index 6e9bf38cd..bce044432 100644 --- a/stylesheet.css +++ b/stylesheet.css @@ -101,6 +101,16 @@ 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; From 93358fa3c88a9a9634920f9805b025a59f1bb7a4 Mon Sep 17 00:00:00 2001 From: Andrea Azzarone Date: Sat, 2 Sep 2017 00:35:06 +0200 Subject: [PATCH 02/11] Ellipsize if the number is too big. --- appIcons.js | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/appIcons.js b/appIcons.js index bb30004a9..9384c0f0c 100644 --- a/appIcons.js +++ b/appIcons.js @@ -5,6 +5,7 @@ const GdkPixbuf = imports.gi.GdkPixbuf const Gio = imports.gi.Gio; const GLib = imports.gi.GLib; const Gtk = imports.gi.Gtk; +const Pango = imports.gi.Pango; const Signals = imports.signals; const Lang = imports.lang; const Meta = imports.gi.Meta; @@ -957,11 +958,17 @@ const MyAppIcon = new Lang.Class({ updateNotificationBadge: function() { let scaleFactor = St.ThemeContext.get_for_stage(global.stage).scale_factor; let [minWidth, natWidth] = this._iconContainer.get_preferred_width(-1); - let font_size = Math.max(10, Math.round(natWidth / scaleFactor / 5)); + 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;' + '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; }, setNotificationBadge: function(count) { From b67154fbfc3bd57d1b3e3fd9a7322d58cc82e845 Mon Sep 17 00:00:00 2001 From: Andrea Azzarone Date: Tue, 5 Sep 2017 18:44:06 +0200 Subject: [PATCH 03/11] Implement the dbus backed for the unity launcher api. --- .vscode/settings.json | 3 + .vscode/tasks.json | 43 +++++++++ Makefile | 2 +- appIcons.js | 76 ++++++++++++++- dash.js | 15 ++- docking.js | 41 +++++++- launcherAPI.js | 214 ++++++++++++++++++++++++++++++++++++++++++ 7 files changed, 381 insertions(+), 13 deletions(-) create mode 100644 .vscode/settings.json create mode 100644 .vscode/tasks.json create mode 100644 launcherAPI.js diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 000000000..b658c56aa --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,3 @@ +{ + "editor.tabSize": 4 +} \ No newline at end of file diff --git a/.vscode/tasks.json b/.vscode/tasks.json new file mode 100644 index 000000000..ca5e9a643 --- /dev/null +++ b/.vscode/tasks.json @@ -0,0 +1,43 @@ +{ + // See https://go.microsoft.com/fwlink/?LinkId=733558 + // for the documentation about the tasks.json format + "version": "2.0.0", + + "tasks": [ + { + "taskName": "rsync", + "command": "rsync -avz -e 'ssh -p 3022' . andrea@127.0.0.1:/home/andrea/Desktop/dash-to-dock --progress", + "type": "shell", + "group": { + "kind": "build" + }, + "problemMatcher": [] + }, + { + "taskName": "make", + "command": "ssh -p 3022 andrea@127.0.0.1 \"cd /home/andrea/Desktop/dash-to-dock; make\"", + "type": "shell", + "group": { + "kind": "build" + } + }, + { + "taskName": "make install", + "command": "ssh -p 3022 andrea@127.0.0.1 \"cd /home/andrea/Desktop/dash-to-dock; make install\"", + "type": "shell", + "group": { + "kind": "build" + }, + "problemMatcher": [] + }, + { + "taskName": "reload", + "command": "ssh -p 3022 andrea@127.0.0.1 \"export DISPLAY=:0.0 { `gnome-shell --replace &> /dev/null`; } < /dev/stdin &\"", + "type": "shell", + "group": { + "kind": "build" + }, + "problemMatcher": [] + } + ] +} \ No newline at end of file diff --git a/Makefile b/Makefile index 314bd9239..872875356 100644 --- a/Makefile +++ b/Makefile @@ -2,7 +2,7 @@ UUID = dash-to-dock@micxgx.gmail.com BASE_MODULES = extension.js stylesheet.css metadata.json COPYING README.md -EXTRA_MODULES = convenience.js dash.js docking.js appIcons.js windowPreview.js intellihide.js prefs.js theming.js utils.js Settings.ui +EXTRA_MODULES = convenience.js dash.js docking.js appIcons.js launcherAPI.js windowPreview.js intellihide.js prefs.js theming.js utils.js Settings.ui EXTRA_MEDIA = logo.svg glossy.svg TOLOCALIZE = prefs.js appIcons.js MSGSRC = $(wildcard po/*.po) diff --git a/appIcons.js b/appIcons.js index 9384c0f0c..65eb16ce6 100644 --- a/appIcons.js +++ b/appIcons.js @@ -100,6 +100,7 @@ const MyAppIcon = new Lang.Class({ this.monitorIndex = monitorIndex; this._signalsHandler = new Utils.GlobalSignalsHandler(); this._nWindows = 0; + this._remoteEntries = []; this.parent(app, iconParams); @@ -145,8 +146,8 @@ const MyAppIcon = new Lang.Class({ })); this._optionalScrollCycleWindows(); + this._notificationBadge(); this._numberOverlay(); - this._notificationBadge(); this._previewMenuManager = null; this._previewMenu = null; @@ -971,9 +972,32 @@ const MyAppIcon = new Lang.Class({ this._notificationBadgeLabel.clutter_text.ellipsize = Pango.EllipsizeMode.MIDDLE; }, + _notificationBadgeCountToText: function(count) { + if (count <= 9999) { + return count.toString(); + } else if (count < 10**5) { + let thousands = count / 10**3; + return thousands.toFixed(1).toString() + "k"; + } else if (count < 10**6) { + let thousands = count / 10**3; + return thousands.toFixed(0).toString() + "k"; + } else if (count < 10**8) { + let millions = count / 10**6; + return millions.toFixed(1).toString() + "M"; + } else if (count < 10**9) { + let millions = count / 10**6; + return millions.toFixed(0).toString() + "M"; + } else { + let billions = count / 10**9; + return billions.toFixed(1).toString() + "B"; + } + }, + setNotificationBadge: function(count) { + this._notificationBadgeCount = count; - this._notificationBadgeLabel.set_text(count.toString()); + let text = this._notificationBadgeCountToText(count); + this._notificationBadgeLabel.set_text(text); }, toggleNotificationBadge: function(activate) { @@ -1136,7 +1160,53 @@ const MyAppIcon = new Lang.Class({ // nautilus desktop window. getInterestingWindows: function() { return getInterestingWindows(this.app, this._dtdSettings, this.monitorIndex); - } + }, + + insertEntryRemote: function(remote) { + if (!remote || this._remoteEntries.includes(remote)) + return; + + this._remoteEntries.push(remote); + this._selectEntryRemote(remote); + }, + + removeEntryRemote: function(remote) { + if (!remote || !this._remoteEntries.includes(remote)) + 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); + } + }, + + _selectEntryRemote: function(remote) { + if (!remote) + return; + + this._signalsHandler.removeWithLabel('entry-remotes'); + + this._signalsHandler.addWithLabel('entry-remotes', [ + remote, + 'count-changed', + Lang.bind(this, (remote, value) => { + this.setNotificationBadge(value); + }) + ], [ + remote, + 'count-visible-changed', + Lang.bind(this, (remote, value) => { + this.toggleNotificationBadge(value); + }) + ]); + + this.setNotificationBadge(remote.count()); + this.toggleNotificationBadge(remote.countVisible()); + }, }); /** * Extend AppIconMenu diff --git a/dash.js b/dash.js index 593185a94..dfff42507 100644 --- a/dash.js +++ b/dash.js @@ -178,13 +178,14 @@ const baseIconSizes = [16, 22, 24, 32, 48, 64, 96, 128]; const MyDash = new Lang.Class({ Name: 'DashToDock.MyDash', - _init: function(settings, monitorIndex) { + _init: function(settings, remoteModel, monitorIndex) { this._maxHeight = -1; this.iconSize = 64; this._availableIconSizes = baseIconSizes; this._shownInitially = false; this._dtdSettings = settings; + this._remoteModel = remoteModel; this._monitorIndex = monitorIndex; this._position = Utils.getPosition(settings); this._isHorizontal = ((this._position == St.Side.TOP) || @@ -452,6 +453,10 @@ const MyDash = new Lang.Class({ let appIcon = new AppIcons.MyAppIcon(this._dtdSettings, app, this._monitorIndex, { setSizeManually: true, showLabel: false }); + this._remoteModel.lookupById(app.id).forEach(function(entry) { + appIcon.insertEntryRemote(entry); + }); + if (appIcon._draggable) { appIcon._draggable.connect('drag-begin', Lang.bind(this, function() { appIcon.actor.opacity = 50; @@ -515,7 +520,7 @@ const MyDash = new Lang.Class({ /** * Return an array with the "proper" appIcons currently in the dash */ - _getAppIcons: function() { + getAppIcons: function() { // Only consider children which are "proper" // icons (i.e. ignoring drag placeholders) and which are not // animating out (which means they will be destroyed at the end of @@ -535,7 +540,7 @@ const MyDash = new Lang.Class({ }, _updateAppsIconGeometry: function() { - let appIcons = this._getAppIcons(); + let appIcons = this.getAppIcons(); appIcons.forEach(function(icon) { icon.updateIconGeometry(); }); @@ -869,7 +874,7 @@ const MyDash = new Lang.Class({ }, _updateNumberOverlay: function() { - let appIcons = this._getAppIcons(); + let appIcons = this.getAppIcons(); let counter = 1; appIcons.forEach(function(icon) { if (counter < 10){ @@ -890,7 +895,7 @@ const MyDash = new Lang.Class({ }, toggleNumberOverlay: function(activate) { - let appIcons = this._getAppIcons(); + let appIcons = this.getAppIcons(); appIcons.forEach(function(icon) { icon.toggleNumberOverlay(activate); }); diff --git a/docking.js b/docking.js index bf9f77621..83cafdd85 100644 --- a/docking.js +++ b/docking.js @@ -29,6 +29,7 @@ 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; @@ -191,11 +192,12 @@ const DashSlideContainer = new Lang.Class({ const DockedDash = new Lang.Class({ Name: 'DashToDock.DockedDash', - _init: function(settings, monitorIndex) { + _init: function(settings, remoteModel, monitorIndex) { this._rtl = (Clutter.get_default_text_direction() == Clutter.TextDirection.RTL); // Load settings this._settings = settings; + this._remoteModel = remoteModel; this._monitorIndex = monitorIndex; // Connect global signals this._signalsHandler = new Utils.GlobalSignalsHandler(); @@ -240,7 +242,7 @@ const DockedDash = new Lang.Class({ this._dockDwellTimeoutId = 0 // Create a new dash object - this.dash = new MyDash.MyDash(this._settings, this._monitorIndex); + this.dash = new MyDash.MyDash(this._settings, this._remoteModel, this._monitorIndex); if (!this._settings.get_boolean('show-show-apps-button')) this.dash.hideShowAppsButton(); @@ -347,6 +349,14 @@ const DockedDash = new Lang.Class({ Lang.bind(this, function() { Main.overview.dashIconSize = this.dash.iconSize; }) + ], [ + this._remoteModel, + 'entry-added', + Lang.bind(this, this._onLauncherEntryRemoteAdded) + ], [ + this._remoteModel, + 'entry-removed', + Lang.bind(this, this._onLauncherEntryRemoteRemoved) ]); this._injectionsHandler = new Utils.InjectionsHandler(); @@ -1342,6 +1352,28 @@ const DockedDash = new Lang.Class({ } }, + _onLauncherEntryRemoteAdded: function(remoteModel, entry) { + if (!entry || !entry.appId()) + return; + + this.dash.getAppIcons().forEach(function(icon) { + if (icon && icon.app && icon.app.id == entry.appId()) { + icon.insertEntryRemote(entry); + } + }); + }, + + _onLauncherEntryRemoteRemoved: function(remoteModel, entry) { + if (!entry || !entry.appId()) + return; + + this.dash.getAppIcons().forEach(function(icon) { + if (icon && icon.app && icon.app.id == entry.appId()) { + icon.removeEntryRemote(entry); + } + }); + }, + _activateApp: function(appIndex) { let children = this.dash._box.get_children().filter(function(actor) { return actor.child && @@ -1636,6 +1668,7 @@ const DockManager = new Lang.Class({ Name: 'DashToDock.DockManager', _init: function() { + this._remoteModel = new LauncherAPI.LauncherEntryRemoteModel(); this._settings = Convenience.getSettings('org.gnome.shell.extensions.dash-to-dock'); this._oldDash = Main.overview._dash; /* Array of all the docks created */ @@ -1706,7 +1739,7 @@ const DockManager = new Lang.Class({ } // First we create the main Dock, to get the extra features to bind to this one - let dock = new DockedDash(this._settings, this._preferredMonitorIndex); + let dock = new DockedDash(this._settings, this._remoteModel, this._preferredMonitorIndex); this._mainShowAppsButton = dock.dash.showAppsButton; this._allDocks.push(dock); @@ -1724,7 +1757,7 @@ const DockManager = new Lang.Class({ for (let iMon = 0; iMon < nMon; iMon++) { if (iMon == this._preferredMonitorIndex) continue; - let dock = new DockedDash(this._settings, iMon); + let dock = new DockedDash(this._settings, this._remoteModel, iMon); this._allDocks.push(dock); // connect app icon into the view selector dock.dash.showAppsButton.connect('notify::checked', Lang.bind(this, this._onShowAppsButtonToggled)); diff --git a/launcherAPI.js b/launcherAPI.js new file mode 100644 index 000000000..a9455dde4 --- /dev/null +++ b/launcherAPI.js @@ -0,0 +1,214 @@ +// -*- mode: js; js-indent-level: 4; indent-tabs-mode: nil -*- + +const Gio = imports.gi.Gio; +const Lang = imports.lang; +const Signals = imports.signals; + +const LauncherEntryRemoteModel = new Lang.Class({ + Name: 'DashToDock.LauncherEntryRemoteModel', + + _init: function () { + this._entriesByDBusName = {}; + + this._launcher_entry_dbus_signal_id = + Gio.DBus.session.signal_subscribe(null, // sender + 'com.canonical.Unity.LauncherEntry', // iface + null, // member + null, // path + null, // arg0 + Gio.DBusSignalFlags.NONE, + Lang.bind(this, this._onEntrySignalReceived)); + + this._dbus_name_owner_changed_signal_id = + Gio.DBus.session.signal_subscribe('org.freedesktop.DBus', // sender + 'org.freedesktop.DBus', // interface + 'NameOwnerChanged', // member + '/org/freedesktop/DBus', // path + null, // arg0 + Gio.DBusSignalFlags.NONE, + Lang.bind(this, this._onDBusNameOwnerChanged)); + + this._acquireUnityDBus(); + }, + + destroy: function () { + if (this._launcher_entry_dbus_signal_id) { + Gio.DBus.session.signal_unsubscribe(this._launcher_entry_dbus_signal_id); + } + + if (this_._dbus_name_owner_changed_signal_id) { + Gio.DBus.session.signal_unsubscribe(this._dbus_name_owner_changed_signal_id); + } + + this._releaseUnityDBus(); + }, + + size: function () { + return Object.keys(this._entriesByDBusName).length; + }, + + lookupByDBusName: function (dbusName) { + return this._entriesByDBusName.hasOwnProperty(dbusName) ? this._entriesByDBusName[dbusName] : null; + }, + + lookupById: function (appId) { + let ret = []; + for (let dbusName in this._entriesByDBusName) { + let entry = this._entriesByDBusName[dbusName]; + if (entry && entry.appId() == appId) { + ret.push(entry); + } + } + + return ret; + }, + + addEntry: function (entry) { + let existingEntry = this.lookupByDBusName(entry.dbusName()); + if (existingEntry) { + existingEntry.update(entry); + } else { + this._entriesByDBusName[entry.dbusName()] = entry; + this.emit('entry-added', entry); + } + }, + + removeEntry: function (entry) { + delete this._entriesByDBusName[entry.dbusName()] + this.emit('entry-removed', entry); + }, + + _acquireUnityDBus: function () { + if (!this._unity_bus_id) { + Gio.DBus.session.own_name('com.canonical.Unity', + Gio.BusNameOwnerFlags.ALLOW_REPLACEMENT, null, null); + } + }, + + _releaseUnityDBus: function () { + if (this._unity_bus_id) { + Gio.DBus.session.unown_name(this._unity_bus_id); + this._unity_bus_id = 0; + } + }, + + _onEntrySignalReceived: function (connection, sender_name, object_path, + interface_name, signal_name, parameters, user_data) { + if (!parameters || !signal_name) + return; + + if (signal_name == 'Update') { + if (!sender_name) { + return; + } + + this._handleUpdateRequest(sender_name, parameters); + } + }, + + _onDBusNameOwnerChanged: function (connection, sender_name, object_path, + interface_name, signal_name, parameters, user_data) { + if (!parameters || !this.size()) + return; + + let [name, before, after] = parameters.deep_unpack(); + + if (!after) { + if (this._entriesByDBusName.hasOwnProperty(before)) { + this.removeEntry(this._entriesByDBusName[before]); + } + } + }, + + _handleUpdateRequest: function (senderName, parameters) { + if (!senderName || !parameters) { + return; + } + + let [appUri, properties] = parameters.deep_unpack(); + let appId = appUri.replace(/(^\w+:|^)\/\//, ''); + let entry = this.lookupByDBusName(senderName); + + if (entry) { + entry.setDBusName(senderName); + entry.update(properties); + } else { + let entry = new LauncherEntryRemote(senderName, appId, properties); + this.addEntry(entry); + } + }, +}); + +Signals.addSignalMethods(LauncherEntryRemoteModel.prototype); + +const LauncherEntryRemote = new Lang.Class({ + Name: 'DashToDock.LauncherEntryRemote', + + _init: function (dbusName, appId, properties) { + this._dbusName = dbusName; + this._appId = appId; + this._count = 0; + this._countVisible = false; + this.update(properties); + }, + + appId: function () { + return this._appId; + }, + + dbusName: function () { + return this._dbusName; + }, + + count: function () { + return this._count; + }, + + setCount: function (count) { + if (this._count != count) { + this._count = count; + this.emit('count-changed', this._count); + } + }, + + countVisible: function () { + return this._countVisible; + }, + + setCountVisible: function (countVisible) { + if (this._countVisible != countVisible) { + this._countVisible = countVisible; + this.emit('count-visible-changed', this._countVisible); + } + }, + + setDBusName(dbusName) { + if (this._dbusName != dbusName) { + let oldName = this._dbusName; + this._dbusName = dbusName; + this.emit('dbus-name-changed', oldName); + } + }, + + update: function (other) { + if (other instanceof LauncherEntryRemote) { + this.setDBusName(other.dbusName()) + this.setCount(other.count()); + this.setCountVisible(other.countVisible()); + } 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()); + } else { + // Not implemented yet + } + } + } + } + }, +}); + +Signals.addSignalMethods(LauncherEntryRemote.prototype); From 0027f74f789fa6a3f14febed7d62da5b9fe3735f Mon Sep 17 00:00:00 2001 From: Andrea Azzarone Date: Tue, 5 Sep 2017 19:23:46 +0200 Subject: [PATCH 04/11] remove .vscode directory --- .vscode/settings.json | 3 --- .vscode/tasks.json | 43 ------------------------------------------- 2 files changed, 46 deletions(-) delete mode 100644 .vscode/settings.json delete mode 100644 .vscode/tasks.json diff --git a/.vscode/settings.json b/.vscode/settings.json deleted file mode 100644 index b658c56aa..000000000 --- a/.vscode/settings.json +++ /dev/null @@ -1,3 +0,0 @@ -{ - "editor.tabSize": 4 -} \ No newline at end of file diff --git a/.vscode/tasks.json b/.vscode/tasks.json deleted file mode 100644 index ca5e9a643..000000000 --- a/.vscode/tasks.json +++ /dev/null @@ -1,43 +0,0 @@ -{ - // See https://go.microsoft.com/fwlink/?LinkId=733558 - // for the documentation about the tasks.json format - "version": "2.0.0", - - "tasks": [ - { - "taskName": "rsync", - "command": "rsync -avz -e 'ssh -p 3022' . andrea@127.0.0.1:/home/andrea/Desktop/dash-to-dock --progress", - "type": "shell", - "group": { - "kind": "build" - }, - "problemMatcher": [] - }, - { - "taskName": "make", - "command": "ssh -p 3022 andrea@127.0.0.1 \"cd /home/andrea/Desktop/dash-to-dock; make\"", - "type": "shell", - "group": { - "kind": "build" - } - }, - { - "taskName": "make install", - "command": "ssh -p 3022 andrea@127.0.0.1 \"cd /home/andrea/Desktop/dash-to-dock; make install\"", - "type": "shell", - "group": { - "kind": "build" - }, - "problemMatcher": [] - }, - { - "taskName": "reload", - "command": "ssh -p 3022 andrea@127.0.0.1 \"export DISPLAY=:0.0 { `gnome-shell --replace &> /dev/null`; } < /dev/stdin &\"", - "type": "shell", - "group": { - "kind": "build" - }, - "problemMatcher": [] - } - ] -} \ No newline at end of file From 11113a293f7605ec2f3f093f4fc2a1e9986be6cb Mon Sep 17 00:00:00 2001 From: Andrea Azzarone Date: Tue, 5 Sep 2017 19:25:17 +0200 Subject: [PATCH 05/11] Use var instead of const. --- launcherAPI.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/launcherAPI.js b/launcherAPI.js index a9455dde4..d8f0f19c6 100644 --- a/launcherAPI.js +++ b/launcherAPI.js @@ -4,7 +4,7 @@ const Gio = imports.gi.Gio; const Lang = imports.lang; const Signals = imports.signals; -const LauncherEntryRemoteModel = new Lang.Class({ +var LauncherEntryRemoteModel = new Lang.Class({ Name: 'DashToDock.LauncherEntryRemoteModel', _init: function () { @@ -141,7 +141,7 @@ const LauncherEntryRemoteModel = new Lang.Class({ Signals.addSignalMethods(LauncherEntryRemoteModel.prototype); -const LauncherEntryRemote = new Lang.Class({ +var LauncherEntryRemote = new Lang.Class({ Name: 'DashToDock.LauncherEntryRemote', _init: function (dbusName, appId, properties) { From b79ce266e23b4d0a36dada85b6ce4c21cd80b3b4 Mon Sep 17 00:00:00 2001 From: Andrea Azzarone Date: Tue, 5 Sep 2017 19:35:48 +0200 Subject: [PATCH 06/11] Remove trailing whitespaces --- appIcons.js | 7 +++---- dash.js | 2 +- 2 files changed, 4 insertions(+), 5 deletions(-) diff --git a/appIcons.js b/appIcons.js index 65eb16ce6..1f5308f7d 100644 --- a/appIcons.js +++ b/appIcons.js @@ -146,7 +146,7 @@ const MyAppIcon = new Lang.Class({ })); this._optionalScrollCycleWindows(); - this._notificationBadge(); + this._notificationBadge(); this._numberOverlay(); this._previewMenuManager = null; @@ -964,10 +964,10 @@ const MyAppIcon = new Lang.Class({ let margin_left = Math.round(logicalNatWidth / 4); this._notificationBadgeLabel.set_style( - 'font-size: ' + font_size + 'px;' + + '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; }, @@ -994,7 +994,6 @@ const MyAppIcon = new Lang.Class({ }, setNotificationBadge: function(count) { - this._notificationBadgeCount = count; let text = this._notificationBadgeCountToText(count); this._notificationBadgeLabel.set_text(text); diff --git a/dash.js b/dash.js index dfff42507..662cbd4e8 100644 --- a/dash.js +++ b/dash.js @@ -454,7 +454,7 @@ const MyDash = new Lang.Class({ { setSizeManually: true, showLabel: false }); this._remoteModel.lookupById(app.id).forEach(function(entry) { - appIcon.insertEntryRemote(entry); + appIcon.insertEntryRemote(entry); }); if (appIcon._draggable) { From 52fa8b372b7cb2f35a74df24d00298f005dcbce1 Mon Sep 17 00:00:00 2001 From: Andrea Azzarone Date: Wed, 6 Sep 2017 09:58:51 +0200 Subject: [PATCH 07/11] Support older version of gjs. --- appIcons.js | 18 +++++++++--------- launcherAPI.js | 2 +- 2 files changed, 10 insertions(+), 10 deletions(-) diff --git a/appIcons.js b/appIcons.js index 1f5308f7d..436662fcd 100644 --- a/appIcons.js +++ b/appIcons.js @@ -975,20 +975,20 @@ const MyAppIcon = new Lang.Class({ _notificationBadgeCountToText: function(count) { if (count <= 9999) { return count.toString(); - } else if (count < 10**5) { - let thousands = count / 10**3; + } else if (count < 1e5) { + let thousands = count / 1e3; return thousands.toFixed(1).toString() + "k"; - } else if (count < 10**6) { - let thousands = count / 10**3; + } else if (count < 1e6) { + let thousands = count / 1e3; return thousands.toFixed(0).toString() + "k"; - } else if (count < 10**8) { - let millions = count / 10**6; + } else if (count < 1e8) { + let millions = count / 1e6; return millions.toFixed(1).toString() + "M"; - } else if (count < 10**9) { - let millions = count / 10**6; + } else if (count < 1e9) { + let millions = count / 1e6; return millions.toFixed(0).toString() + "M"; } else { - let billions = count / 10**9; + let billions = count / 1e9; return billions.toFixed(1).toString() + "B"; } }, diff --git a/launcherAPI.js b/launcherAPI.js index d8f0f19c6..7dfd624d2 100644 --- a/launcherAPI.js +++ b/launcherAPI.js @@ -182,7 +182,7 @@ var LauncherEntryRemote = new Lang.Class({ } }, - setDBusName(dbusName) { + setDBusName: function (dbusName) { if (this._dbusName != dbusName) { let oldName = this._dbusName; this._dbusName = dbusName; From 2bed1e121516031453e0af5410a3aec2f6edbd97 Mon Sep 17 00:00:00 2001 From: Andrea Azzarone Date: Wed, 20 Sep 2017 12:55:28 +0200 Subject: [PATCH 08/11] Support progress bar. --- appIcons.js | 114 +++++++++++++++++++++++++++++++++++++++++++++++++ launcherAPI.js | 30 +++++++++++++ 2 files changed, 144 insertions(+) diff --git a/appIcons.js b/appIcons.js index e9aa97d7e..31829047d 100644 --- a/appIcons.js +++ b/appIcons.js @@ -12,6 +12,7 @@ const Meta = imports.gi.Meta; const Shell = imports.gi.Shell; const St = imports.gi.St; const Mainloop = imports.mainloop; +const Cairo = imports.cairo; // Use __ () and N__() for the extension gettext domain, and reuse // the shell domain with the default _() and N_() @@ -134,6 +135,8 @@ var MyAppIcon = new Lang.Class({ } this._dots = null; + this._progressOverlayArea = null; + this._progress = 0; let keys = ['apply-custom-theme', 'custom-theme-running-dots', @@ -1021,6 +1024,102 @@ var MyAppIcon = new Lang.Class({ this._notificationBadgeBin.hide(); }, + _showProgressOverlay: function() { + if (this._progressOverlayArea) { + this._updateProgressOverlay(); + return; + } + + this._progressOverlayArea = new St.DrawingArea({x_expand: true, y_expand: true}); + this._progressOverlayArea.connect('repaint', Lang.bind(this, function() { + this._drawProgressOverlay(this._progressOverlayArea); + })); + + this._iconContainer.add_child(this._progressOverlayArea); + this._updateProgressOverlay(); + }, + + _hideProgressOverlay: function() { + if (this._progressOverlayArea) + this._progressOverlayArea.destroy(); + this._progressOverlayArea = null; + }, + + _updateProgressOverlay: function() { + if (this._progressOverlayArea) + this._progressOverlayArea.queue_repaint(); + }, + + _drawProgressOverlay: function(area) { + let scaleFactor = St.ThemeContext.get_for_stage(global.stage).scale_factor; + let [surfaceWidth, surfaceHeight] = area.get_surface_size(); + let cr = area.get_context(); + + iconSize = this.icon.iconSize * scaleFactor; + + let x = Math.floor((surfaceWidth - iconSize) / 2); + let y = Math.floor((surfaceHeight - iconSize) / 2); + + let lineWidth = Math.floor(1.0 * scaleFactor); + let padding = Math.floor(iconSize * 0.05); + let width = iconSize - 2.0*padding; + let height = Math.floor(Math.min(18.0*scaleFactor, 0.20*iconSize)); + x += padding; + y += iconSize - height - padding; + + cr.setLineWidth(lineWidth); + + // Draw the outer stroke + let stroke = new Cairo.LinearGradient(0, y, 0, y + height); + let fill = null; + stroke.addColorStopRGBA(0.5, 0.5, 0.5, 0.5, 0.1); + stroke.addColorStopRGBA(0.9, 0.8, 0.8, 0.8, 0.4); + Utils.drawRoundedLine(cr, x + lineWidth/2.0, y + lineWidth/2.0, width, height, true, true, stroke, fill); + + // Draw the background + x += lineWidth; + y += lineWidth; + width -= 2.0*lineWidth; + height -= 2.0*lineWidth; + + stroke = Cairo.SolidPattern.createRGBA(0.20, 0.20, 0.20, 0.9); + fill = new Cairo.LinearGradient(0, y, 0, y + height); + fill.addColorStopRGBA(0.4, 0.25, 0.25, 0.25, 1.0); + fill.addColorStopRGBA(0.9, 0.35, 0.35, 0.35, 1.0); + Utils.drawRoundedLine(cr, x + lineWidth/2.0, y + lineWidth/2.0, width, height, true, true, stroke, fill); + + // Draw the finished bar + x += lineWidth; + y += lineWidth; + width -= 2.0*lineWidth; + height -= 2.0*lineWidth; + + let finishedWidth = Math.ceil(this._progress * width); + stroke = Cairo.SolidPattern.createRGBA(0.8, 0.8, 0.8, 1.0); + fill = Cairo.SolidPattern.createRGBA(0.9, 0.9, 0.9, 1.0); + + if (Clutter.get_default_text_direction() == Clutter.TextDirection.RTL) + Utils.drawRoundedLine(cr, x + lineWidth/2.0 + width - finishedWidth, y + lineWidth/2.0, finishedWidth, height, true, true, stroke, fill); + else + Utils.drawRoundedLine(cr, x + lineWidth/2.0, y + lineWidth/2.0, finishedWidth, height, true, true, stroke, fill); + + cr.$dispose(); + }, + + setProgress: function(progress) { + this._progress = Math.min(Math.max(progress, 0.0), 1.0); + this._updateProgressOverlay(); + }, + + toggleProgressOverlay: function(activate) { + if (activate) { + this._showProgressOverlay(); + } + else { + this._hideProgressOverlay(); + } + }, + _numberOverlay: function() { // Add label for a Hot-Key visual aid this._numberOverlayLabel = new St.Label(); @@ -1193,6 +1292,8 @@ var MyAppIcon = new Lang.Class({ } else { this.setNotificationBadge(0); this.toggleNotificationBadge(false); + this.setProgress(0); + this.toggleProgressOverlay(false); } }, @@ -1214,10 +1315,23 @@ var MyAppIcon = new Lang.Class({ Lang.bind(this, (remote, value) => { this.toggleNotificationBadge(value); }) + ], [remote, + 'progress-changed', + Lang.bind(this, (remote, value) => { + this.setProgress(value); + }) + ], [ + remote, + 'progress-visible-changed', + Lang.bind(this, (remote, value) => { + this.toggleProgressOverlay(value); + }) ]); this.setNotificationBadge(remote.count()); this.toggleNotificationBadge(remote.countVisible()); + this.setProgress(remote.progress()); + this.toggleProgressOverlay(remote.progressVisible()); }, }); /** diff --git a/launcherAPI.js b/launcherAPI.js index 7dfd624d2..bff77807c 100644 --- a/launcherAPI.js +++ b/launcherAPI.js @@ -149,6 +149,8 @@ var LauncherEntryRemote = new Lang.Class({ this._appId = appId; this._count = 0; this._countVisible = false; + this._progress = 0.0; + this._progressVisible = false; this.update(properties); }, @@ -182,6 +184,28 @@ var LauncherEntryRemote = new Lang.Class({ } }, + progress: function () { + return this._progress; + }, + + setProgress: function (progress) { + if (this._progress != progress) { + this._progress = progress; + this.emit('progress-changed', this._progress); + } + }, + + progressVisible: function () { + return this._progressVisible; + }, + + setProgressVisible: function (progressVisible) { + if (this._progressVisible != progressVisible) { + this._progressVisible = progressVisible; + this.emit('progress-visible-changed', this._progressVisible); + } + }, + setDBusName: function (dbusName) { if (this._dbusName != dbusName) { let oldName = this._dbusName; @@ -195,6 +219,8 @@ var LauncherEntryRemote = new Lang.Class({ 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)) { @@ -202,6 +228,10 @@ var LauncherEntryRemote = new Lang.Class({ 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 } From b273d8c66408bd6e2a5fa3c52ab0088233d46542 Mon Sep 17 00:00:00 2001 From: Andrea Azzarone Date: Wed, 20 Sep 2017 13:05:03 +0200 Subject: [PATCH 09/11] Add drawRoundedLine to utils.js --- utils.js | 33 +++++++++++++++++++++++++++++++++ 1 file changed, 33 insertions(+) diff --git a/utils.js b/utils.js index 21a74c8cd..8302ee4bb 100644 --- a/utils.js +++ b/utils.js @@ -219,3 +219,36 @@ function getPosition(settings) { } 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(); +} \ No newline at end of file From 5a370bb1a7eda7c2801f2c246c4823b0fd3891cc Mon Sep 17 00:00:00 2001 From: Andrea Azzarone Date: Thu, 21 Sep 2017 01:20:38 +0200 Subject: [PATCH 10/11] Minor changes after review --- appIcons.js | 2 +- docking.js | 1 + launcherAPI.js | 2 +- 3 files changed, 3 insertions(+), 2 deletions(-) diff --git a/appIcons.js b/appIcons.js index 31829047d..3f945939f 100644 --- a/appIcons.js +++ b/appIcons.js @@ -1055,7 +1055,7 @@ var MyAppIcon = new Lang.Class({ let [surfaceWidth, surfaceHeight] = area.get_surface_size(); let cr = area.get_context(); - iconSize = this.icon.iconSize * scaleFactor; + let iconSize = this.icon.iconSize * scaleFactor; let x = Math.floor((surfaceWidth - iconSize) / 2); let y = Math.floor((surfaceHeight - iconSize) / 2); diff --git a/docking.js b/docking.js index f1cccaf12..705f3b256 100644 --- a/docking.js +++ b/docking.js @@ -1920,6 +1920,7 @@ var DockManager = new Lang.Class({ this._deleteDocks(); this._revertPanelCorners(); this._restoreDash(); + this._remoteModel.destroy(); }, /** diff --git a/launcherAPI.js b/launcherAPI.js index bff77807c..d051a70a6 100644 --- a/launcherAPI.js +++ b/launcherAPI.js @@ -36,7 +36,7 @@ var LauncherEntryRemoteModel = new Lang.Class({ Gio.DBus.session.signal_unsubscribe(this._launcher_entry_dbus_signal_id); } - if (this_._dbus_name_owner_changed_signal_id) { + if (this._dbus_name_owner_changed_signal_id) { Gio.DBus.session.signal_unsubscribe(this._dbus_name_owner_changed_signal_id); } From 8932f877475fecb2852fb358ca1d220b82c483dd Mon Sep 17 00:00:00 2001 From: Andrea Azzarone Date: Fri, 22 Sep 2017 15:27:28 +0200 Subject: [PATCH 11/11] Change the default color --- stylesheet.css | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/stylesheet.css b/stylesheet.css index bce044432..0a36874dd 100644 --- a/stylesheet.css +++ b/stylesheet.css @@ -103,7 +103,7 @@ #dashtodockContainer .notification-badge { color: rgba(255,255,255,1); - background-color: rgba(255,0,0,1.0); + background-color: #e95420; padding: 0.2em 0.5em; border-radius: 1em; font-weight: bold;