[gnome-shell-extensions] window-list: New extension



commit b843058c18410762d0ce662e9d13efb005d72385
Author: Florian MÃllner <fmuellner gnome org>
Date:   Fri Nov 9 22:43:30 2012 +0100

    window-list: New extension
    
    https://bugzilla.gnome.org/show_bug.cgi?id=693171

 configure.ac                            |    5 +-
 extensions/window-list/Makefile.am      |    3 +
 extensions/window-list/extension.js     |  410 +++++++++++++++++++++++++++++++
 extensions/window-list/metadata.json.in |   10 +
 extensions/window-list/stylesheet.css   |   67 +++++
 5 files changed, 493 insertions(+), 2 deletions(-)
---
diff --git a/configure.ac b/configure.ac
index 536ba3d..9ac1e1a 100644
--- a/configure.ac
+++ b/configure.ac
@@ -27,7 +27,7 @@ fi
 AC_SUBST([SHELL_VERSION])
 
 dnl keep this in alphabetic order
-CLASSIC_EXTENSIONS="apps-menu places-menu alternate-tab default-min-max launch-new-instance static-workspaces"
+CLASSIC_EXTENSIONS="apps-menu places-menu alternate-tab default-min-max launch-new-instance static-workspaces window-list"
 DEFAULT_EXTENSIONS="$CLASSIC_EXTENSIONS alternative-status-menu drive-menu windowsNavigator workspace-indicator"
 ALL_EXTENSIONS="$DEFAULT_EXTENSIONS auto-move-windows example native-window-placement systemMonitor user-theme xrandr-indicator"
 AC_SUBST(CLASSIC_EXTENSIONS, [$CLASSIC_EXTENSIONS])
@@ -71,7 +71,7 @@ for e in $enable_extensions; do
 					[AC_MSG_WARN([gnome-desktop-3.0 not found, disabling xrandr-indicator])])
 			;;
 dnl		keep this in alphabetic order
-		alternate-tab|alternative-status-menu|apps-menu|auto-move-windows|default-min-max|drive-menu|example|launch-new-instance|native-window-placement|places-menu|static-workspaces|user-theme|windowsNavigator|workspace-indicator)
+		alternate-tab|alternative-status-menu|apps-menu|auto-move-windows|default-min-max|drive-menu|example|launch-new-instance|native-window-placement|places-menu|static-workspaces|user-theme|window-list|windowsNavigator|workspace-indicator)
 			ENABLED_EXTENSIONS="$ENABLED_EXTENSIONS $e"
 			;;
 		*)
@@ -97,6 +97,7 @@ AC_CONFIG_FILES([
   extensions/static-workspaces/Makefile
   extensions/systemMonitor/Makefile
   extensions/user-theme/Makefile
+  extensions/window-list/Makefile
   extensions/windowsNavigator/Makefile
   extensions/workspace-indicator/Makefile
   extensions/xrandr-indicator/Makefile
diff --git a/extensions/window-list/Makefile.am b/extensions/window-list/Makefile.am
new file mode 100644
index 0000000..48a1028
--- /dev/null
+++ b/extensions/window-list/Makefile.am
@@ -0,0 +1,3 @@
+EXTENSION_ID = window-list
+
+include ../../extension.mk
diff --git a/extensions/window-list/extension.js b/extensions/window-list/extension.js
new file mode 100644
index 0000000..76e2d1e
--- /dev/null
+++ b/extensions/window-list/extension.js
@@ -0,0 +1,410 @@
+const Clutter = imports.gi.Clutter;
+const Meta = imports.gi.Meta;
+const Shell = imports.gi.Shell;
+const St = imports.gi.St;
+
+const Hash = imports.misc.hash;
+const Lang = imports.lang;
+const Main = imports.ui.main;
+const MessageTray = imports.ui.messageTray;
+
+
+function _minimizeOrActivateWindow(window) {
+        let focusWindow = global.display.focus_window;
+        if (focusWindow == window ||
+            focusWindow && focusWindow.get_transient_for() == window)
+            window.minimize();
+        else
+            window.activate(global.get_current_time());
+}
+
+
+const WindowButton = new Lang.Class({
+    Name: 'WindowButton',
+
+    _init: function(metaWindow) {
+        this.metaWindow = metaWindow;
+
+        let box = new St.BoxLayout();
+        this.actor = new St.Button({ style_class: 'window-button',
+                                     x_fill: true,
+                                     can_focus: true,
+                                     child: box });
+        this.actor._delegate = this;
+
+        this.actor.connect('allocation-changed',
+                           Lang.bind(this, this._updateIconGeometry));
+
+        let textureCache = St.TextureCache.get_default();
+        let icon = textureCache.bind_pixbuf_property(this.metaWindow, "icon");
+        this._icon = new St.Bin({ style_class: 'window-button-icon',
+                                  child: icon });
+        box.add(this._icon);
+        this._label = new St.Label();
+        box.add(this._label);
+
+        this.actor.connect('clicked', Lang.bind(this, this._onClicked));
+        this.actor.connect('destroy', Lang.bind(this, this._onDestroy));
+
+        this._switchWorkspaceId =
+            global.window_manager.connect('switch-workspace',
+                                          Lang.bind(this, this._updateVisibility));
+        this._updateVisibility();
+
+        this._notifyTitleId =
+            this.metaWindow.connect('notify::title',
+                                    Lang.bind(this, this._updateTitle));
+        this._notifyMinimizedId =
+            this.metaWindow.connect('notify::minimized',
+                                    Lang.bind(this, this._minimizedChanged));
+        this._notifyFocusId =
+            global.display.connect('notify::focus-window',
+                                   Lang.bind(this, this._updateStyle));
+        this._minimizedChanged();
+    },
+
+    _onClicked: function() {
+        _minimizeOrActivateWindow(this.metaWindow);
+    },
+
+    _minimizedChanged: function() {
+        this._icon.opacity = this.metaWindow.minimized ? 128 : 255;
+        this._updateTitle();
+        this._updateStyle();
+    },
+
+    _updateTitle: function() {
+        if (this.metaWindow.minimized)
+            this._label.text = '[%s]'.format(this.metaWindow.title);
+        else
+            this._label.text = this.metaWindow.title;
+    },
+
+    _updateStyle: function() {
+        if (this.metaWindow.minimized)
+            this.actor.add_style_class_name('minimized');
+        else
+            this.actor.remove_style_class_name('minimized');
+
+        if (global.display.focus_window == this.metaWindow)
+            this.actor.add_style_class_name('focused');
+        else
+            this.actor.remove_style_class_name('focused');
+    },
+
+    _updateVisibility: function() {
+        let workspace = global.screen.get_active_workspace();
+        this.actor.visible = this.metaWindow.located_on_workspace(workspace);
+    },
+
+    _updateIconGeometry: function() {
+        let rect = new Meta.Rectangle();
+
+        [rect.x, rect.y] = this.actor.get_transformed_position();
+        [rect.width, rect.height] = this.actor.get_transformed_size();
+
+        this.metaWindow.set_icon_geometry(rect);
+    },
+
+    _onDestroy: function() {
+        global.window_manager.disconnect(this._switchWorkspaceId);
+        this.metaWindow.disconnect(this._notifyTitleId);
+        this.metaWindow.disconnect(this._notifyMinimizedId);
+        global.display.disconnect(this._notifyFocusId);
+    }
+});
+
+
+const TrayButton = new Lang.Class({
+    Name: 'TrayButton',
+
+    _init: function() {
+        this._counterLabel = new St.Label({ x_align: Clutter.ActorAlign.CENTER,
+                                            x_expand: true,
+                                            y_align: Clutter.ActorAlign.CENTER,
+                                            y_expand: true });
+        this.actor = new St.Button({ style_class: 'summary-source-counter',
+                                     child: this._counterLabel,
+                                     layoutManager: new Clutter.BinLayout() });
+        this.actor.set_x_align(Clutter.ActorAlign.END);
+        this.actor.set_x_expand(true);
+        this.actor.set_y_expand(true);
+
+        this.actor.connect('clicked', Lang.bind(this,
+            function() {
+                if (Main.messageTray._trayState == MessageTray.State.HIDDEN)
+                    Main.messageTray.toggle();
+            }));
+        this.actor.connect('destroy', Lang.bind(this, this._onDestroy));
+
+        this._trayItemCount = 0;
+        Main.messageTray.getSources().forEach(Lang.bind(this,
+            function(source) {
+                this._sourceAdded(Main.messageTray, source);
+            }));
+        this._sourceAddedId =
+            Main.messageTray.connect('source-added',
+                                     Lang.bind(this, this._sourceAdded));
+        this._sourceRemovedId =
+            Main.messageTray.connect('source-removed',
+                                     Lang.bind(this, this._sourceRemoved));
+        this._updateVisibility();
+    },
+
+    _sourceAdded: function(tray, source) {
+        this._trayItemCount++;
+        this._updateVisibility();
+    },
+
+    _sourceRemoved: function(source) {
+        this._trayItemCount--;
+        this.actor.checked = false;
+        this._updateVisibility();
+    },
+
+    _updateVisibility: function() {
+        this._counterLabel.text = this._trayItemCount.toString();
+        this.actor.visible = this._trayItemCount > 0;
+    },
+
+    _onDestroy: function() {
+        Main.messageTray.getSources().forEach(Lang.bind(this,
+            function(source) {
+                if (!source._windowListDestroyId)
+                    return;
+                source.disconnect(source._windowListDestroyId)
+                delete source._windowListDestroyId;
+            }));
+        Main.messageTray.disconnect(this._sourceAddedId);
+        Main.messageTray.disconnect(this._sourceRemovedId);
+    }
+});
+
+
+const WindowList = new Lang.Class({
+    Name: 'WindowList',
+
+    _init: function() {
+        this.actor = new St.Widget({ name: 'panel',
+                                     style_class: 'bottom-panel',
+                                     reactive: true,
+                                     track_hover: true,
+                                     layout_manager: new Clutter.BinLayout()});
+        this.actor.connect('destroy', Lang.bind(this, this._onDestroy));
+
+        let box = new St.BoxLayout({ x_expand: true, y_expand: true });
+        this.actor.add_actor(box);
+
+        let layout = new Clutter.BoxLayout({ homogeneous: true });
+        this._windowList = new St.Widget({ style_class: 'window-list',
+                                           layout_manager: layout,
+                                           x_align: Clutter.ActorAlign.START,
+                                           x_expand: true,
+                                           y_expand: true });
+        box.add(this._windowList, { expand: true });
+
+        this._windowList.connect('style-changed', Lang.bind(this,
+            function() {
+                let node = this._windowList.get_theme_node();
+                let spacing = node.get_length('spacing');
+                this._windowList.layout_manager.spacing = spacing;
+            }));
+
+        this._trayButton = new TrayButton();
+        box.add(this._trayButton.actor);
+
+        Main.layoutManager.addChrome(this.actor, { affectsStruts: true,
+                                                   trackFullscreen: true });
+        Main.ctrlAltTabManager.addGroup(this.actor, _('Window List'), 'start-here-symbolic');
+
+        this._monitorsChangedId =
+            Main.layoutManager.connect('monitors-changed',
+                                       Lang.bind(this, this._updatePosition));
+        this._updatePosition();
+
+        this._keyboardVisiblechangedId =
+            Main.layoutManager.connect('keyboard-visible-changed',
+                Lang.bind(this, function(o, state) {
+                    Main.layoutManager.keyboardBox.visible = state;
+                    Main.uiGroup.set_child_above_sibling(windowList.actor,
+                                                         Main.layoutManager.keyboardBox);
+                    this._updateKeyboardAnchor();
+                }));
+
+        this._workspaceSignals = new Hash.Map();
+        this._nWorkspacesChangedId =
+            global.screen.connect('notify::n-workspaces',
+                                  Lang.bind(this, this._onWorkspacesChanged));
+        this._onWorkspacesChanged();
+
+        this._overviewShowingId =
+            Main.overview.connect('showing', Lang.bind(this, function() {
+                this.actor.hide();
+                this._updateKeyboardAnchor();
+            }));
+
+        this._overviewHidingId =
+            Main.overview.connect('hiding', Lang.bind(this, function() {
+                this.actor.show();
+                this._updateKeyboardAnchor();
+            }));
+
+        let windows = Meta.get_window_actors(global.screen);
+        for (let i = 0; i < windows.length; i++)
+            this._onWindowAdded(null, windows[i].metaWindow);
+    },
+
+    _updatePosition: function() {
+        let monitor = Main.layoutManager.primaryMonitor;
+        this.actor.width = monitor.width;
+        this.actor.set_position(monitor.x, monitor.y + monitor.height - this.actor.height);
+    },
+
+    _updateKeyboardAnchor: function() {
+        if (!Main.keyboard.actor)
+            return;
+
+        let anchorY = Main.overview.visible ? 0 : this.actor.height;
+        Main.keyboard.actor.anchor_y = anchorY;
+    },
+
+    _onWindowAdded: function(ws, win) {
+        if (!Shell.WindowTracker.get_default().is_window_interesting(win))
+            return;
+
+        let button = new WindowButton(win);
+        this._windowList.layout_manager.pack(button.actor,
+                                             true, true, true,
+                                             Clutter.BoxAlignment.START,
+                                             Clutter.BoxAlignment.START);
+    },
+
+    _onWindowRemoved: function(ws, win) {
+        let children = this._windowList.get_children();
+        for (let i = 0; i < children.length; i++) {
+            if (children[i]._delegate.metaWindow == win) {
+                children[i].destroy();
+                return;
+            }
+        }
+    },
+
+    _onWorkspacesChanged: function() {
+        let numWorkspaces = global.screen.n_workspaces;
+        for (let i = 0; i < numWorkspaces; i++) {
+            let workspace = global.screen.get_workspace_by_index(i);
+            if (this._workspaceSignals.has(workspace))
+                continue;
+
+            let signals = { windowAddedId: 0, windowRemovedId: 0 };
+            signals._windowAddedId =
+                workspace.connect('window-added',
+                                  Lang.bind(this, this._onWindowAdded));
+            signals._windowRemovedId =
+                workspace.connect('window-removed',
+                                  Lang.bind(this, this._onWindowRemoved));
+            this._workspaceSignals.set(workspace, signals);
+        }
+    },
+
+    _disconnectWorkspaceSignals: function() {
+        let numWorkspaces = global.screen.n_workspaces;
+        for (let i = 0; i < numWorkspaces; i++) {
+            let workspace = global.screen.get_workspace_by_index(i);
+            let signals = this._workspaceSignals.delete(workspace)[1];
+            workspace.disconnect(signals._windowAddedId);
+            workspace.disconnect(signals._windowRemovedId);
+        }
+    },
+
+    _onDestroy: function() {
+        Main.ctrlAltTabManager.removeGroup(this.actor);
+
+        Main.layoutManager.disconnect(this._monitorsChangedId);
+        this._monitorsChangedId = 0;
+
+        Main.layoutManager.disconnect(this._keyboardVisiblechangedId);
+        this._keyboardVisiblechangedId = 0;
+
+        Main.layoutManager.hideKeyboard();
+
+        this._disconnectWorkspaceSignals();
+        global.screen.disconnect(this._nWorkspacesChangedId);
+        this._nWorkspacesChangedId = 0;
+
+        Main.overview.disconnect(this._overviewShowingId);
+        Main.overview.disconnect(this._overviewHidingId);
+
+        let windows = Meta.get_window_actors(global.screen);
+        for (let i = 0; i < windows.length; i++)
+            windows[i].metaWindow.set_icon_geometry(null);
+    }
+});
+
+let windowList;
+let injections = {};
+let notificationParent;
+
+function init() {
+}
+
+function enable() {
+    windowList = new WindowList();
+
+    windowList.actor.connect('notify::hover', Lang.bind(Main.messageTray,
+        function() {
+            this._pointerInTray = windowList.actor.hover;
+            this._updateState();
+        }));
+
+    injections['_trayDwellTimeout'] = MessageTray.MessageTray.prototype._trayDwellTimeout;
+    MessageTray.MessageTray.prototype._trayDwellTimeout = function() {
+        return false;
+    };
+
+    injections['_tween'] = MessageTray.MessageTray.prototype._tween;
+    MessageTray.MessageTray.prototype._tween = function(actor, statevar, value, params) {
+        if (!Main.overview.visible) {
+            let anchorY;
+            if (statevar == '_trayState')
+                anchorY = windowList.actor.height;
+            else if (statevar == '_notificationState')
+                anchorY = -windowList.actor.height;
+            else
+                anchorY = 0;
+            actor.anchor_y = anchorY;
+        }
+        injections['_tween'].call(Main.messageTray, actor, statevar, value, params);
+    };
+    injections['_onTrayHidden'] = MessageTray.MessageTray.prototype._onTrayHidden;
+    MessageTray.MessageTray.prototype._onTrayHidden = function() {
+        this.actor.anchor_y = 0;
+        injections['_onTrayHidden'].call(Main.messageTray);
+    };
+
+    notificationParent = Main.messageTray._notificationWidget.get_parent();
+    Main.messageTray._notificationWidget.hide();
+    Main.messageTray._notificationWidget.reparent(windowList.actor);
+    Main.messageTray._notificationWidget.show();
+}
+
+function disable() {
+    if (!windowList)
+        return;
+
+    windowList.actor.hide();
+
+    if (notificationParent) {
+        Main.messageTray._notificationWidget.reparent(notificationParent);
+        notificationParent = null;
+    }
+
+    windowList.actor.destroy();
+    windowList = null;
+
+    for (prop in injections)
+        MessageTray.MessageTray.prototype[prop] = injections[prop];
+
+    Main.messageTray._notificationWidget.set_anchor_point(0, 0);
+    Main.messageTray.actor.set_anchor_point(0, 0);
+}
diff --git a/extensions/window-list/metadata.json.in b/extensions/window-list/metadata.json.in
new file mode 100644
index 0000000..ebfaed6
--- /dev/null
+++ b/extensions/window-list/metadata.json.in
@@ -0,0 +1,10 @@
+{
+"extension-id": "@extension_id@",
+"uuid": "@uuid@",
+"settings-schema": "@gschemaname@",
+"gettext-domain": "@gettext_domain@",
+"name": "Window List",
+"description": "Display a window list at the bottom of the screen",
+"shell-version": [ "@shell_current@" ],
+"url": "@url@"
+}
diff --git a/extensions/window-list/stylesheet.css b/extensions/window-list/stylesheet.css
new file mode 100644
index 0000000..d98712f
--- /dev/null
+++ b/extensions/window-list/stylesheet.css
@@ -0,0 +1,67 @@
+.bottom-panel {
+  /* .window-button-icon height +
+     .window-button vertical padding +
+     .window-button > StWidget vertical padding) */
+  height: 30px;
+}
+
+.window-list {
+  spacing: 2px;
+  font-size: 10pt;
+}
+
+.window-button {
+  padding: 1px;
+}
+
+.window-button:first-child:ltr {
+  padding-left: 2px;
+}
+
+.window-button:last-child:rtl {
+  padding-right: 2px;
+}
+
+.window-button > StWidget {
+  max-width: 250px;
+  color: #bbb;
+  background-color: black;
+  border-radius: 4px;
+  padding: 3px 6px 1px;
+  box-shadow: inset 1px 1px 4px rgba(255,255,255,0.5);
+  text-shadow: 1px 1px 4px rgba(0,0,0,0.8);
+  spacing: 4px;
+}
+
+.window-button:hover > StWidget {
+  color: white;
+  background-color: #1f1f1f;
+}
+
+.window-button:active > StWidget,
+.window-button:focus > StWidget {
+  box-shadow: inset 2px 2px 4px rgba(255,255,255,0.5);
+}
+
+.window-button.focused > StWidget {
+  color: white;
+  box-shadow: inset 1px 1px 4px rgba(255,255,255,0.7);
+}
+
+.window-button.focused:active > StWidget {
+  box-shadow: inset 2px 2px 4px rgba(255,255,255,0.7);
+}
+
+.window-button.minimized > StWidget {
+  color: #666;
+  box-shadow: inset -1px -1px 4px rgba(255,255,255,0.5);
+}
+
+.window-button.minimized:active > StWidget {
+  box-shadow: inset -2px -2px 4px rgba(255,255,255,0.5);
+}
+
+.window-button-icon {
+  width: 24px;
+  height: 24px;
+}



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