diff --git a/Makefile b/Makefile index 02aca8f..fad5383 100644 --- a/Makefile +++ b/Makefile @@ -2,7 +2,7 @@ UUID = dash-to-panel@jderose9.github.com BASE_MODULES = extension.js stylesheet.css metadata.json COPYING README.md -EXTRA_MODULES = appIcons.js convenience.js panel.js panelManager.js proximity.js intellihide.js panelStyle.js overview.js taskbar.js transparency.js windowPreview.js prefs.js utils.js Settings.ui +EXTRA_MODULES = appIcons.js convenience.js panel.js panelManager.js proximity.js intellihide.js panelStyle.js overview.js taskbar.js transparency.js windowPreview.js prefs.js update.js utils.js Settings.ui EXTRA_IMAGES = highlight_stacked_bg.svg TOLOCALIZE = prefs.js appIcons.js MSGSRC = $(wildcard po/*.po) diff --git a/extension.js b/extension.js index ddabe0a..2b2f028 100644 --- a/extension.js +++ b/extension.js @@ -33,6 +33,7 @@ const Signals = imports.signals; const Me = ExtensionUtils.getCurrentExtension(); const Convenience = Me.imports.convenience; const PanelManager = Me.imports.panelManager; +const Update = Me.imports.update; const Utils = Me.imports.utils; const UBUNTU_DOCK_UUID = 'ubuntu-dock@ubuntu.com'; @@ -90,6 +91,8 @@ function _enable() { panelManager = new PanelManager.dtpPanelManager(settings); panelManager.enable(); + settings.connect('changed::version-to-install', () => Update.installRelease(settings.get_string('version-to-install'))); + Utils.removeKeybinding('open-application-menu'); Utils.addKeybinding( 'open-application-menu', diff --git a/schemas/org.gnome.shell.extensions.dash-to-panel.gschema.xml b/schemas/org.gnome.shell.extensions.dash-to-panel.gschema.xml index 5349a9d..58cfd7b 100644 --- a/schemas/org.gnome.shell.extensions.dash-to-panel.gschema.xml +++ b/schemas/org.gnome.shell.extensions.dash-to-panel.gschema.xml @@ -66,6 +66,9 @@ + + '' + 'BOTTOM' Panel position diff --git a/update.js b/update.js new file mode 100644 index 0000000..ec31533 --- /dev/null +++ b/update.js @@ -0,0 +1,174 @@ +/* + * This file is part of the Dash-To-Panel extension for Gnome 3 + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +const GLib = imports.gi.GLib; +const Gio = imports.gi.Gio; +const Soup = imports.gi.Soup; +const FileUtils = imports.misc.fileUtils; + +const Me = imports.misc.extensionUtils.getCurrentExtension(); +const Gettext = imports.gettext.domain(Me.metadata['gettext-domain']); +const _ = Gettext.gettext; + +const releaseApiUrl = 'https://api.github.com/repos/home-sweet-gnome/dash-to-panel/releases/'; + +let httpSession; + +function getLatestReleaseInfo(cb) { + getReleaseInfo('latest', cb); +} + +function getTaggedReleaseInfo(releaseTag, cb) { + getReleaseInfo('tags/' + releaseTag, cb); +} + +function getReleaseInfo(suffix, cb) { + let message = Soup.form_request_new_from_hash('GET', releaseApiUrl + suffix, {}); + + getHttpMessageResponseBody(message, (err, body) => { + if (err) { + return cb(err); + } + + try { + let release = JSON.parse(body.data); + let releaseInfo = { + name: release.name, + tag: release.tag_name, + zipUrl: (release.assets.length ? release.assets[0].browser_download_url : ''), + }; + + cb(null, releaseInfo); + } catch (e) { + cb(e.message); + } + }); +} + +function installRelease(releaseAssetUrl) { + getHttpMessageResponseBody(Soup.form_request_new_from_hash('GET', releaseAssetUrl, {}), (err, body) => { + if (err) { + return notifyInstallResult(err); + } + + let name = 'dtp_install'; + let zipFile = createTmp(name + '.zip'); + let stream = zipFile.replace(null, false, Gio.FileCreateFlags.NONE, null); + let extDir = Gio.file_new_for_path(Me.path); + let tmpDir = createTmp(name, true); + let bckDir = createTmp(Me.uuid, true); + + stream.write_bytes(body.flatten().get_as_bytes(), null); + stream.close(null); + + unzipFile(zipFile, tmpDir, err => { + if (err) { + return notifyInstallResult(err); + } + + try { + let [success, out, err, exitCode] = GLib.spawn_command_line_sync('pidof "gnome-shell-extension-prefs"'); + + FileUtils.recursivelyMoveDir(extDir, bckDir); + FileUtils.recursivelyMoveDir(tmpDir, extDir); + + if (success && !exitCode) { + GLib.spawn_command_line_sync('kill -9 ' + imports.byteArray.toString(out)); + } + } catch (e) { + FileUtils.recursivelyDeleteDir(extDir, false); + FileUtils.recursivelyMoveDir(bckDir, extDir); + + return notifyInstallResult(e.message); + } + + notifyInstallResult(null); + }); + }); +} + +function notifyInstallResult(err) { + let Meta = imports.gi.Meta; + let icon = 'information'; + let action = 0; + + if (err) { + var msg = _('Error: ') + err; + icon = 'error'; + } else if (Meta.is_wayland_compositor()) { + msg = _('Update successful, please log out/in'); + action = { text: _('Log out'), func: () => new imports.misc.systemActions.getDefault().activateLogout() }; + } else { + msg = _('Update successful, please restart GNOME Shell'); + action = { text: _('Restart GNOME Shell'), func: () => Meta.restart(_("Restarting GNOME Shell…")) }; + } + + Me.imports.utils.notify('Dash to Panel', msg, 'dialog-' + icon, action); +} + +function createTmp(name, isDir) { + let tmp = Gio.file_new_for_path('/tmp/' + name); + + if (isDir && tmp.query_exists(null)) { + FileUtils.recursivelyDeleteDir(tmp, false); + } + + return tmp; +} + +function unzipFile(zipFile, destDir, cb) { + let [success, pid] = GLib.spawn_async( + null, + ['unzip', '-uod', destDir.get_path(), '--', zipFile.get_path()], + null, + GLib.SpawnFlags.SEARCH_PATH | GLib.SpawnFlags.DO_NOT_REAP_CHILD | GLib.SpawnFlags.STDOUT_TO_DEV_NULL, + null + ); + + if (!success) { + return cb('unzip spawn error'); + } + + GLib.child_watch_add(GLib.PRIORITY_DEFAULT, pid, (pid, status) => { + GLib.spawn_close_pid(pid); + + if (status != 0) { + return cb('extraction error') + } + + return cb(null); + }); +} + +function getHttpMessageResponseBody(message, cb) { + if (!httpSession) { + httpSession = new Soup.Session(); + httpSession.user_agent = Me.metadata.uuid; + } + + httpSession.queue_message(message, (httpSession, message) => { + try { + if (!message.response_body || !message.response_body.data) { + return cb('No data received'); + } + + return cb(null, message.response_body); + } catch (e) { + return cb(e.message); + } + }); +} diff --git a/utils.js b/utils.js index 205ea18..246a877 100644 --- a/utils.js +++ b/utils.js @@ -32,6 +32,7 @@ const Shell = imports.gi.Shell; const St = imports.gi.St; const Mainloop = imports.mainloop; const Main = imports.ui.main; +const MessageTray = imports.ui.messageTray; const Tweener = imports.ui.tweener; const Util = imports.misc.util; @@ -412,7 +413,25 @@ var activateSiblingWindow = function(windows, direction, startWindow) { if (windowIndex != nextWindowIndex) { Main.activateWindow(windows[nextWindowIndex]); } -} +}; + +var notify = function(title, text, iconName, action) { + let source = new MessageTray.SystemNotificationSource(); + let notification = new MessageTray.Notification(source, title, text); + + if (iconName) { + source.createIcon = function() { + return new St.Icon({ icon_name: iconName }); + }; + } + + if (action) { + notification.addAction(action.text, action.func); + } + + Main.messageTray.add(source); + source.notify(notification); +}; /* * This is a copy of the same function in utils.js, but also adjust horizontal scrolling