[gnome-shell-extensions] apps-menu: Allow creating desktop launchers via DND



commit 243f700fa22ee6d2f3a46308a2b94b686641b32a
Author: Florian Müllner <fmuellner gnome org>
Date:   Wed Mar 15 01:30:53 2017 +0100

    apps-menu: Allow creating desktop launchers via DND
    
    Back in the olden days, it used to be possible to drag items from
    the application menu to the desktop to create a launcher shortcut.
    Reimplement that "classic" functionality in the apps menu extension.
    
    https://bugzilla.gnome.org/show_bug.cgi?id=780371

 extensions/apps-menu/extension.js |  118 ++++++++++++++++++++++++++++++++++++-
 1 files changed, 117 insertions(+), 1 deletions(-)
---
diff --git a/extensions/apps-menu/extension.js b/extensions/apps-menu/extension.js
index ced236c..404548d 100644
--- a/extensions/apps-menu/extension.js
+++ b/extensions/apps-menu/extension.js
@@ -1,16 +1,19 @@
 /* -*- mode: js2; js2-basic-offset: 4; indent-tabs-mode: nil -*- */
 
 const Atk = imports.gi.Atk;
+const DND = imports.ui.dnd;
 const GMenu = imports.gi.GMenu;
 const Lang = imports.lang;
 const Shell = imports.gi.Shell;
 const St = imports.gi.St;
 const Clutter = imports.gi.Clutter;
 const Main = imports.ui.main;
+const Meta = imports.gi.Meta;
 const PanelMenu = imports.ui.panelMenu;
 const PopupMenu = imports.ui.popupMenu;
 const Gtk = imports.gi.Gtk;
 const GLib = imports.gi.GLib;
+const Gio = imports.gi.Gio;
 const Signals = imports.signals;
 const Pango = imports.gi.Pango;
 
@@ -70,6 +73,16 @@ const ApplicationMenuItem = new Lang.Class({
                 textureCache.disconnect(iconThemeChangedId);
             }));
         this._updateIcon();
+
+        this.actor._delegate = this;
+        let draggable = DND.makeDraggable(this.actor);
+
+        draggable.connect('drag-begin', () => {
+            Shell.util_set_hidden_from_pick(Main.legacyTray.actor, true);
+        });
+        draggable.connect('drag-end', () => {
+            Shell.util_set_hidden_from_pick(Main.legacyTray.actor, false);
+        });
     },
 
     activate: function(event) {
@@ -85,8 +98,16 @@ const ApplicationMenuItem = new Lang.Class({
         this.parent(active, params);
     },
 
+    getDragActor: function() {
+        return this._app.create_icon_texture(APPLICATION_ICON_SIZE);
+    },
+
+    getDragActorSource: function() {
+        return this._iconBin;
+    },
+
     _updateIcon: function() {
-        this._iconBin.set_child(this._app.create_icon_texture(APPLICATION_ICON_SIZE));
+        this._iconBin.set_child(this.getDragActor());
     }
 });
 
@@ -246,6 +267,94 @@ const ApplicationsMenu = new Lang.Class({
     }
 });
 
+const DesktopTarget = new Lang.Class({
+    Name: 'DesktopTarget',
+
+    _init: function() {
+        this._desktop = null;
+        this._desktopDestroyedId = 0;
+
+        this._windowAddedId =
+            global.window_group.connect('actor-added',
+                                        Lang.bind(this, this._onWindowAdded));
+
+        global.get_window_actors().forEach(a => {
+            this._onWindowAdded(a.get_parent(), a);
+        });
+    },
+
+    _onWindowAdded: function(group, actor) {
+        if (!(actor instanceof Meta.WindowActor))
+            return;
+
+        if (actor.meta_window.get_window_type() == Meta.WindowType.DESKTOP)
+            this._setDesktop(actor);
+    },
+
+    _setDesktop: function(desktop) {
+        if (this._desktop) {
+            this._desktop.disconnect(this._desktopDestroyedId);
+            this._desktopDestroyedId = 0;
+
+            delete this._desktop._delegate;
+        }
+
+        this._desktop = desktop;
+
+        if (this._desktop) {
+            this._desktopDestroyedId = this._desktop.connect('destroy', () => {
+                this._setDesktop(null);
+            });
+            this._desktop._delegate = this;
+        }
+    },
+
+    _getSourceAppInfo: function(source) {
+        if (!(source instanceof ApplicationMenuItem))
+            return null;
+        return source._app.app_info;
+    },
+
+    destroy: function() {
+        if (this._windowAddedId)
+            global.window_group.disconnect(this._windowAddedId);
+        this._windowAddedId = 0;
+
+        this._setDesktop(null);
+    },
+
+    handleDragOver: function(source, actor, x, y, time) {
+        let appInfo = this._getSourceAppInfo(source);
+        if (!appInfo)
+            return DND.DragMotionResult.CONTINUE;
+
+        return DND.DragMotionResult.COPY_DROP;
+    },
+
+    acceptDrop: function(source, actor, x, y, time) {
+        let appInfo = this._getSourceAppInfo(source);
+        if (!appInfo)
+            return false;
+
+        this.emit('app-dropped');
+
+        let desktop = GLib.get_user_special_dir(GLib.UserDirectory.DIRECTORY_DESKTOP);
+
+        let src = Gio.File.new_for_path(appInfo.get_filename());
+        let dst = Gio.File.new_for_path(GLib.build_filenamev([desktop, src.get_basename()]));
+
+        try {
+            // copy_async() isn't introspectable :-(
+            src.copy(dst, Gio.FileCopyFlags.OVERWRITE, null, null);
+        } catch(e) {
+            log('Failed to copy to desktop: ' + e.message);
+        }
+
+        return true;
+    }
+});
+Signals.addSignalMethods(DesktopTarget.prototype);
+
 const ApplicationsButton = new Lang.Class({
     Name: 'ApplicationsButton',
     Extends: PanelMenu.Button,
@@ -286,6 +395,11 @@ const ApplicationsButton = new Lang.Class({
                                    Lang.bind(this, this._setKeybinding));
         this._setKeybinding();
 
+        this._desktopTarget = new DesktopTarget();
+        this._desktopTarget.connect('app-dropped', () => {
+            this.menu.close();
+        });
+
         this._applicationsButtons = new Map();
         this.reloadFlag = false;
         this._createLayout();
@@ -330,6 +444,8 @@ const ApplicationsButton = new Lang.Class({
                                            Main.sessionMode.hasOverview ?
                                            Lang.bind(Main.overview, Main.overview.toggle) :
                                            null);
+
+        this._desktopTarget.destroy();
     },
 
     _onCapturedEvent: function(actor, event) {


[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]