From fb3df46a25bbeb6e687708d1ad1460a76aa42b4c Mon Sep 17 00:00:00 2001 From: Giovanni Campagna Date: Wed, 8 Aug 2012 00:20:52 +0200 Subject: [PATCH] alternate-tab: rework again Kill the mode switch distinction, and kill the old and unmaintained Workspace&Icons mode. Introduce instead configurability for the All&Thumbnails code, which now independently allows choosing if only all windows or only those from current workspace should be shown, and in which way (thumbnail, icon, both). All configuration settings changed, so the extension must be configured again (although hopefully I chose reasonable defaults) --- extensions/alternate-tab/Makefile.am | 2 +- extensions/alternate-tab/allThumbnails.js | 353 ---------------- extensions/alternate-tab/extension.js | 379 +++++++++++++++++- ...ll.extensions.alternate-tab.gschema.xml.in | 34 +- extensions/alternate-tab/prefs.js | 79 +--- extensions/alternate-tab/workspaceIcons.js | 274 ------------- 6 files changed, 397 insertions(+), 724 deletions(-) delete mode 100644 extensions/alternate-tab/allThumbnails.js delete mode 100644 extensions/alternate-tab/workspaceIcons.js diff --git a/extensions/alternate-tab/Makefile.am b/extensions/alternate-tab/Makefile.am index d9e0f365..1f353920 100644 --- a/extensions/alternate-tab/Makefile.am +++ b/extensions/alternate-tab/Makefile.am @@ -1,6 +1,6 @@ EXTENSION_ID = alternate-tab -EXTRA_MODULES = allThumbnails.js workspaceIcons.js prefs.js +EXTRA_MODULES = prefs.js include ../../extension.mk include ../../settings.mk diff --git a/extensions/alternate-tab/allThumbnails.js b/extensions/alternate-tab/allThumbnails.js deleted file mode 100644 index 37af47ae..00000000 --- a/extensions/alternate-tab/allThumbnails.js +++ /dev/null @@ -1,353 +0,0 @@ -/* -*- mode: js2; js2-basic-offset: 4; indent-tabs-mode: nil -*- */ - -const Clutter = imports.gi.Clutter; -const Gdk = imports.gi.Gdk; -const Gio = imports.gi.Gio; -const Gtk = imports.gi.Gtk; -const Lang = imports.lang; -const Mainloop = imports.mainloop; -const Meta = imports.gi.Meta; -const Shell = imports.gi.Shell; -const St = imports.gi.St; - -const AltTab = imports.ui.altTab; -const Main = imports.ui.main; -const ModalDialog = imports.ui.modalDialog; -const Tweener = imports.ui.tweener; -const WindowManager = imports.ui.windowManager; - -const Gettext = imports.gettext.domain('gnome-shell-extensions'); -const _ = Gettext.gettext; -const N_ = function(e) { return e }; - -const SETTINGS_SHOW_APP_ICON_KEY = 'show-app-icon'; - -function mod(a, b) { - return ((a+b) % b); -} - -const AltTabPopupAllThumbnails = new Lang.Class({ - Name: 'AlternateTab.AltTabPopup.AllThumbnails', - - _init : function(settings) { - this._settings = settings; - - this.actor = new Shell.GenericContainer({ name: 'altTabPopup', - reactive: true }); - - this.actor.connect('get-preferred-width', Lang.bind(this, this._getPreferredWidth)); - this.actor.connect('get-preferred-height', Lang.bind(this, this._getPreferredHeight)); - this.actor.connect('allocate', Lang.bind(this, this._allocate)); - - this.actor.connect('destroy', Lang.bind(this, this._onDestroy)); - - this._haveModal = false; - - this._currentWindow = 0; - this._motionTimeoutId = 0; - - // Initially disable hover so we ignore the enter-event if - // the switcher appears underneath the current pointer location - this._disableHover(); - - Main.uiGroup.add_actor(this.actor); - }, - - _getPreferredWidth: function (actor, forHeight, alloc) { - alloc.min_size = global.screen_width; - alloc.natural_size = global.screen_width; - }, - - _getPreferredHeight: function (actor, forWidth, alloc) { - alloc.min_size = global.screen_height; - alloc.natural_size = global.screen_height; - }, - - _allocate: function (actor, box, flags) { - let childBox = new Clutter.ActorBox(); - let primary = Main.layoutManager.primaryMonitor; - - let leftPadding = this.actor.get_theme_node().get_padding(St.Side.LEFT); - let rightPadding = this.actor.get_theme_node().get_padding(St.Side.RIGHT); - let bottomPadding = this.actor.get_theme_node().get_padding(St.Side.BOTTOM); - let vPadding = this.actor.get_theme_node().get_vertical_padding(); - let hPadding = leftPadding + rightPadding; - - // Allocate the appSwitcher - // We select a size based on an icon size that does not overflow the screen - let [childMinHeight, childNaturalHeight] = this._appSwitcher.actor.get_preferred_height(primary.width - hPadding); - let [childMinWidth, childNaturalWidth] = this._appSwitcher.actor.get_preferred_width(childNaturalHeight); - childBox.x1 = Math.max(primary.x + leftPadding, primary.x + Math.floor((primary.width - childNaturalWidth) / 2)); - childBox.x2 = Math.min(primary.x + primary.width - rightPadding, childBox.x1 + childNaturalWidth); - childBox.y1 = primary.y + Math.floor((primary.height - childNaturalHeight) / 2); - childBox.y2 = childBox.y1 + childNaturalHeight; - this._appSwitcher.actor.allocate(childBox, flags); - }, - - show : function(backward, binding, mask) { - // This is roughly what meta_display_get_tab_list does, except - // that it doesn't filter on workspace - // See in particular src/core/window-private.h for the filters - let windows = global.get_window_actors().map(function(actor) { - return actor.meta_window; - }).filter(function(win) { - return !win.is_override_redirect() && - win.get_window_type() != Meta.WindowType.DESKTOP && - win.get_window_type() != Meta.WindowType.DOCK; - }).sort(function(one, two) { - return two.get_user_time() - one.get_user_time(); - }); - - if (!windows.length) { - this.destroy(); - return false; - } - - if (!Main.pushModal(this.actor)) { - // Probably someone else has a pointer grab, try again with keyboard only - if (!Main.pushModal(this.actor, global.get_current_time(), Meta.ModalOptions.POINTER_ALREADY_GRABBED)) { - return false; - } - } - this._haveModal = true; - this._modifierMask = AltTab.primaryModifier(mask); - - this.actor.connect('key-press-event', Lang.bind(this, this._keyPressEvent)); - this.actor.connect('key-release-event', Lang.bind(this, this._keyReleaseEvent)); - - this.actor.connect('button-press-event', Lang.bind(this, this._clickedOutside)); - this.actor.connect('scroll-event', Lang.bind(this, this._onScroll)); - - this._appSwitcher = new WindowList(windows, this._settings); - this.actor.add_actor(this._appSwitcher.actor); - this._appSwitcher.connect('item-activated', Lang.bind(this, this._windowActivated)); - this._appSwitcher.connect('item-entered', Lang.bind(this, this._windowEntered)); - - // make the initial selection - if (backward) - this._select(windows.length - 1); - else - this._select(1); - - this.actor.opacity = 0; - this.actor.show(); - - // There's a race condition; if the user released Alt before - // we got the grab, then we won't be notified. (See - // https://bugzilla.gnome.org/show_bug.cgi?id=596695 for - // details.) So we check now. (Have to do this after updating - // selection.) - let [x, y, mods] = global.get_pointer(); - if (!(mods & this._modifierMask)) { - this._finish(); - return false; - } - - // We delay showing the popup so that fast Alt+Tab users aren't - // disturbed by the popup briefly flashing. - this._initialDelayTimeoutId = Mainloop.timeout_add(AltTab.POPUP_DELAY_TIMEOUT, - Lang.bind(this, function () { - this.actor.opacity = 255; - this._initialDelayTimeoutId = 0; - })); - - return true - }, - - _windowActivated : function(thumbnailList, n) { - let win = this._appSwitcher.windows[n]; - Main.activateWindow(win); - this.destroy(); - }, - - _finish : function() { - let win = this._appSwitcher.windows[this._currentWindow]; - Main.activateWindow(win); - this.destroy(); - }, - - _keyPressEvent : function(actor, event) { - let keysym = event.get_key_symbol(); - let event_state = event.get_state(); - let backwards = event_state & Clutter.ModifierType.SHIFT_MASK; - let action = global.display.get_keybinding_action(event.get_key_code(), event_state); - - this._disableHover(); - - if (keysym == Clutter.Escape) { - this.destroy(); - } else if (action == Meta.KeyBindingAction.SWITCH_WINDOWS || - action == Meta.KeyBindingAction.SWITCH_GROUP) { - this._select(backwards ? this._previousWindow() : this._nextWindow()); - } else if (action == Meta.KeyBindingAction.SWITCH_WINDOWS_BACKWARD || - action == Meta.KeyBindingAction.SWITCH_GROUP_BACKWARD) { - this._select(this._previousWindow()); - } else { - if (keysym == Clutter.Left) - this._select(this._previousWindow()); - else if (keysym == Clutter.Right) - this._select(this._nextWindow()); - } - - return true; - }, - - _keyReleaseEvent : function(actor, event) { - let [x, y, mods] = global.get_pointer(); - let state = mods & this._modifierMask; - - if (state == 0) - this._finish(); - - return true; - }, - - _onScroll : function(actor, event) { - let direction = event.get_scroll_direction(); - if (direction == Clutter.ScrollDirection.UP) - this._select(this._previousWindow()); - else if (direction == Clutter.ScrollDirection.DOWN) - this._select(this._nextWindow()); - - return true; - }, - - _clickedOutside : function(actor, event) { - this.destroy(); - }, - - _windowEntered : function(windowSwitcher, n) { - if (!this._mouseActive) - return; - - this._select(n); - }, - - _disableHover : function() { - this._mouseActive = false; - - if (this._motionTimeoutId != 0) - Mainloop.source_remove(this._motionTimeoutId); - - this._motionTimeoutId = Mainloop.timeout_add(AltTab.DISABLE_HOVER_TIMEOUT, Lang.bind(this, this._mouseTimedOut)); - }, - - _mouseTimedOut : function() { - this._motionTimeoutId = 0; - this._mouseActive = true; - }, - - _popModal: function() { - if (this._haveModal) { - Main.popModal(this.actor); - this._haveModal = false; - } - }, - - destroy : function() { - this._popModal(); - if (this.actor.visible) { - Tweener.addTween(this.actor, - { opacity: 0, - time: AltTab.POPUP_FADE_OUT_TIME, - transition: 'easeOutQuad', - onComplete: Lang.bind(this, - function() { - this.actor.destroy(); - }) - }); - } else - this.actor.destroy(); - }, - - _onDestroy : function() { - this._popModal(); - - if (this._motionTimeoutId != 0) - Mainloop.source_remove(this._motionTimeoutId); - if (this._initialDelayTimeoutId != 0) - Mainloop.source_remove(this._initialDelayTimeoutId); - }, - - _select : function(window) { - this._currentWindow = window; - this._appSwitcher.highlight(window); - }, - - _nextWindow: function() { - return mod(this._currentWindow + 1, this._appSwitcher.windows.length); - }, - - _previousWindow: function() { - return mod(this._currentWindow - 1, this._appSwitcher.windows.length); - }, -}); - -const WindowIcon = new Lang.Class({ - Name: 'WindowIcon', - - _init: function(window, settings) { - this.window = window; - - this.actor = new St.BoxLayout({ style_class: 'alt-tab-app', - vertical: true }); - this.icon = null; - this._iconBin = new St.Widget({ layout_manager: new Clutter.BinLayout() }); - - this.actor.add(this._iconBin, { x_fill: false, y_fill: false } ); - this.label = new St.Label({ text: window.get_title() }); - this.actor.add(this.label, { x_fill: false }); - - if (settings.get_boolean(SETTINGS_SHOW_APP_ICON_KEY)) { - let tracker = Shell.WindowTracker.get_default(); - this.app = tracker.get_window_app(window); - } - }, - - set_size: function(size) { - let mutterWindow = this.window.get_compositor_private(); - let windowTexture = mutterWindow.get_texture(); - let [width, height] = windowTexture.get_size(); - let scale = Math.min(1.0, size / width, size / height); - - this.clone = new Clutter.Clone({ source: windowTexture, - width: width * scale, - height: height * scale, - // usual hack for the usual bug in ClutterBinLayout... - x_expand: true, - y_expand: true }); - - this._iconBin.set_size(size, size); - this._iconBin.destroy_all_children(); - this._iconBin.add_actor(this.clone); - - if (this.app) { - this.appIcon = this.app.create_icon_texture(size / 2); - this.appIcon.x_expand = this.appIcon.y_expand = true; - this.appIcon.x_align = Clutter.ActorAlign.END; - this.appIcon.y_align = Clutter.ActorAlign.END; - this._iconBin.add_actor(this.appIcon); - } - } -}); - -const WindowList = new Lang.Class({ - Name: 'AlternateTab.WindowList', - Extends: AltTab.SwitcherList, - - _init : function(windows, settings) { - this.parent(true); - - this.windows = windows; - this.icons = []; - - for (let i = 0; i < windows.length; i++) { - let win = windows[i]; - let icon = new WindowIcon(win, settings); - icon.set_size(128); - - this.addItem(icon.actor, icon.label); - this.icons.push(icon); - } - } -}); diff --git a/extensions/alternate-tab/extension.js b/extensions/alternate-tab/extension.js index f3cbaba7..febd040d 100644 --- a/extensions/alternate-tab/extension.js +++ b/extensions/alternate-tab/extension.js @@ -28,33 +28,382 @@ const N_ = function(e) { return e }; const ExtensionUtils = imports.misc.extensionUtils; const Me = ExtensionUtils.getCurrentExtension(); const Convenience = Me.imports.convenience; -const WorkspaceIcons = Me.imports.workspaceIcons; -const AllThumbnails = Me.imports.allThumbnails; let settings; -const SETTINGS_BEHAVIOUR_KEY = 'behaviour'; - -const MODES = { - all_thumbnails: AllThumbnails.AltTabPopupAllThumbnails, - workspace_icons: WorkspaceIcons.AltTabPopupWorkspaceIcons, +const AppIconMode = { + THUMBNAIL_ONLY: 1, + APP_ICON_ONLY: 2, + BOTH: 3, }; +const SETTINGS_APP_ICON_MODE = 'app-icon-mode'; +const SETTINGS_CURRENT_WORKSPACE_ONLY = 'current-workspace-only'; + +function mod(a, b) { + return ((a+b) % b); +} + +const AltTabPopup = new Lang.Class({ + Name: 'AlternateTab.AltTabPopup', + + _init : function(settings) { + this._settings = settings; + + this.actor = new Shell.GenericContainer({ name: 'altTabPopup', + reactive: true }); + + this.actor.connect('get-preferred-width', Lang.bind(this, this._getPreferredWidth)); + this.actor.connect('get-preferred-height', Lang.bind(this, this._getPreferredHeight)); + this.actor.connect('allocate', Lang.bind(this, this._allocate)); + + this.actor.connect('destroy', Lang.bind(this, this._onDestroy)); + + this._haveModal = false; + + this._currentWindow = 0; + this._motionTimeoutId = 0; + + // Initially disable hover so we ignore the enter-event if + // the switcher appears underneath the current pointer location + this._disableHover(); + + Main.uiGroup.add_actor(this.actor); + }, + + _getPreferredWidth: function (actor, forHeight, alloc) { + alloc.min_size = global.screen_width; + alloc.natural_size = global.screen_width; + }, + + _getPreferredHeight: function (actor, forWidth, alloc) { + alloc.min_size = global.screen_height; + alloc.natural_size = global.screen_height; + }, + + _allocate: function (actor, box, flags) { + let childBox = new Clutter.ActorBox(); + let primary = Main.layoutManager.primaryMonitor; + + let leftPadding = this.actor.get_theme_node().get_padding(St.Side.LEFT); + let rightPadding = this.actor.get_theme_node().get_padding(St.Side.RIGHT); + let bottomPadding = this.actor.get_theme_node().get_padding(St.Side.BOTTOM); + let vPadding = this.actor.get_theme_node().get_vertical_padding(); + let hPadding = leftPadding + rightPadding; + + // Allocate the appSwitcher + // We select a size based on an icon size that does not overflow the screen + let [childMinHeight, childNaturalHeight] = this._appSwitcher.actor.get_preferred_height(primary.width - hPadding); + let [childMinWidth, childNaturalWidth] = this._appSwitcher.actor.get_preferred_width(childNaturalHeight); + childBox.x1 = Math.max(primary.x + leftPadding, primary.x + Math.floor((primary.width - childNaturalWidth) / 2)); + childBox.x2 = Math.min(primary.x + primary.width - rightPadding, childBox.x1 + childNaturalWidth); + childBox.y1 = primary.y + Math.floor((primary.height - childNaturalHeight) / 2); + childBox.y2 = childBox.y1 + childNaturalHeight; + this._appSwitcher.actor.allocate(childBox, flags); + }, + + show : function(backward, binding, mask) { + let windows; + + if (!settings.get_boolean(SETTINGS_CURRENT_WORKSPACE_ONLY)) { + // This is roughly what meta_display_get_tab_list does, except + // that it doesn't filter on workspace + // See in particular src/core/window-private.h for the filters + windows = global.get_window_actors().map(function(actor) { + return actor.meta_window; + }).filter(function(win) { + return !win.is_override_redirect() && + win.get_window_type() != Meta.WindowType.DESKTOP && + win.get_window_type() != Meta.WindowType.DOCK; + }).sort(function(one, two) { + return two.get_user_time() - one.get_user_time(); + }); + } else { + windows = global.display.get_tab_list(Meta.TabList.NORMAL_ALL, global.screen, + global.screen.get_active_workspace()); + } + + if (!windows.length) { + this.destroy(); + return false; + } + + if (!Main.pushModal(this.actor)) { + // Probably someone else has a pointer grab, try again with keyboard only + if (!Main.pushModal(this.actor, global.get_current_time(), Meta.ModalOptions.POINTER_ALREADY_GRABBED)) { + return false; + } + } + this._haveModal = true; + this._modifierMask = AltTab.primaryModifier(mask); + + this.actor.connect('key-press-event', Lang.bind(this, this._keyPressEvent)); + this.actor.connect('key-release-event', Lang.bind(this, this._keyReleaseEvent)); + + this.actor.connect('button-press-event', Lang.bind(this, this._clickedOutside)); + this.actor.connect('scroll-event', Lang.bind(this, this._onScroll)); + + this._appSwitcher = new WindowList(windows, this._settings); + this.actor.add_actor(this._appSwitcher.actor); + this._appSwitcher.connect('item-activated', Lang.bind(this, this._windowActivated)); + this._appSwitcher.connect('item-entered', Lang.bind(this, this._windowEntered)); + + // make the initial selection + if (backward) + this._select(windows.length - 1); + else + this._select(1); + + this.actor.opacity = 0; + this.actor.show(); + + // There's a race condition; if the user released Alt before + // we got the grab, then we won't be notified. (See + // https://bugzilla.gnome.org/show_bug.cgi?id=596695 for + // details.) So we check now. (Have to do this after updating + // selection.) + let [x, y, mods] = global.get_pointer(); + if (!(mods & this._modifierMask)) { + this._finish(); + return false; + } + + // We delay showing the popup so that fast Alt+Tab users aren't + // disturbed by the popup briefly flashing. + this._initialDelayTimeoutId = Mainloop.timeout_add(AltTab.POPUP_DELAY_TIMEOUT, + Lang.bind(this, function () { + this.actor.opacity = 255; + this._initialDelayTimeoutId = 0; + })); + + return true + }, + + _windowActivated : function(thumbnailList, n) { + let win = this._appSwitcher.windows[n]; + Main.activateWindow(win); + this.destroy(); + }, + + _finish : function() { + let win = this._appSwitcher.windows[this._currentWindow]; + Main.activateWindow(win); + this.destroy(); + }, + + _keyPressEvent : function(actor, event) { + let keysym = event.get_key_symbol(); + let event_state = event.get_state(); + let backwards = event_state & Clutter.ModifierType.SHIFT_MASK; + let action = global.display.get_keybinding_action(event.get_key_code(), event_state); + + this._disableHover(); + + if (keysym == Clutter.Escape) { + this.destroy(); + } else if (action == Meta.KeyBindingAction.SWITCH_WINDOWS || + action == Meta.KeyBindingAction.SWITCH_GROUP) { + this._select(backwards ? this._previousWindow() : this._nextWindow()); + } else if (action == Meta.KeyBindingAction.SWITCH_WINDOWS_BACKWARD || + action == Meta.KeyBindingAction.SWITCH_GROUP_BACKWARD) { + this._select(this._previousWindow()); + } else { + if (keysym == Clutter.Left) + this._select(this._previousWindow()); + else if (keysym == Clutter.Right) + this._select(this._nextWindow()); + } + + return true; + }, + + _keyReleaseEvent : function(actor, event) { + let [x, y, mods] = global.get_pointer(); + let state = mods & this._modifierMask; + + if (state == 0) + this._finish(); + + return true; + }, + + _onScroll : function(actor, event) { + let direction = event.get_scroll_direction(); + if (direction == Clutter.ScrollDirection.UP) + this._select(this._previousWindow()); + else if (direction == Clutter.ScrollDirection.DOWN) + this._select(this._nextWindow()); + + return true; + }, + + _clickedOutside : function(actor, event) { + this.destroy(); + }, + + _windowEntered : function(windowSwitcher, n) { + if (!this._mouseActive) + return; + + this._select(n); + }, + + _disableHover : function() { + this._mouseActive = false; + + if (this._motionTimeoutId != 0) + Mainloop.source_remove(this._motionTimeoutId); + + this._motionTimeoutId = Mainloop.timeout_add(AltTab.DISABLE_HOVER_TIMEOUT, Lang.bind(this, this._mouseTimedOut)); + }, + + _mouseTimedOut : function() { + this._motionTimeoutId = 0; + this._mouseActive = true; + }, + + _popModal: function() { + if (this._haveModal) { + Main.popModal(this.actor); + this._haveModal = false; + } + }, + + destroy : function() { + this._popModal(); + if (this.actor.visible) { + Tweener.addTween(this.actor, + { opacity: 0, + time: AltTab.POPUP_FADE_OUT_TIME, + transition: 'easeOutQuad', + onComplete: Lang.bind(this, + function() { + this.actor.destroy(); + }) + }); + } else + this.actor.destroy(); + }, + + _onDestroy : function() { + this._popModal(); + + if (this._motionTimeoutId != 0) + Mainloop.source_remove(this._motionTimeoutId); + if (this._initialDelayTimeoutId != 0) + Mainloop.source_remove(this._initialDelayTimeoutId); + }, + + _select : function(window) { + this._currentWindow = window; + this._appSwitcher.highlight(window); + }, + + _nextWindow: function() { + return mod(this._currentWindow + 1, this._appSwitcher.windows.length); + }, + + _previousWindow: function() { + return mod(this._currentWindow - 1, this._appSwitcher.windows.length); + }, +}); + +const WindowIcon = new Lang.Class({ + Name: 'AlternateTab.WindowIcon', + + _init: function(window, settings) { + this.window = window; + this._settings = settings; + + this.actor = new St.BoxLayout({ style_class: 'alt-tab-app', + vertical: true }); + this.icon = null; + this._iconBin = new St.Widget({ layout_manager: new Clutter.BinLayout() }); + + this.actor.add(this._iconBin, { x_fill: false, y_fill: false } ); + this.label = new St.Label({ text: window.get_title() }); + this.actor.add(this.label, { x_fill: false }); + + let tracker = Shell.WindowTracker.get_default(); + this.app = tracker.get_window_app(window); + + let mutterWindow = this.window.get_compositor_private(); + let windowTexture = mutterWindow.get_texture(); + let [width, height] = windowTexture.get_size(); + let scale, size; + + this._iconBin.destroy_all_children(); + + switch (this._settings.get_enum(SETTINGS_APP_ICON_MODE)) { + case AppIconMode.THUMBNAIL_ONLY: + scale = Math.min(1.0, 128 / width, 128 / height); + size = 128; + this.clone = new Clutter.Clone({ source: windowTexture, + width: width * scale, + height: height * scale, + // usual hack for the usual bug in ClutterBinLayout... + x_expand: true, + y_expand: true }); + this._iconBin.add_actor(this.clone); + break; + + case AppIconMode.BOTH: + scale = Math.min(1.0, 128 / width, 128 / height); + size = 128; + this.clone = new Clutter.Clone({ source: windowTexture, + width: width * scale, + height: height * scale, + // usual hack for the usual bug in ClutterBinLayout... + x_expand: true, + y_expand: true }); + this._iconBin.add_actor(this.clone); + + this.appIcon = this.app.create_icon_texture(size / 2); + this.appIcon.x_expand = this.appIcon.y_expand = true; + this.appIcon.x_align = Clutter.ActorAlign.END; + this.appIcon.y_align = Clutter.ActorAlign.END; + this._iconBin.add_actor(this.appIcon); + break; + + case AppIconMode.APP_ICON_ONLY: + size = 96; + this.appIcon = this.app.create_icon_texture(size); + this.appIcon.x_expand = this.appIcon.y_expand = true; + this._iconBin.add_actor(this.appIcon); + } + + this._iconBin.set_size(size, size); + } +}); + +const WindowList = new Lang.Class({ + Name: 'AlternateTab.WindowList', + Extends: AltTab.SwitcherList, + + _init : function(windows, settings) { + this.parent(true); + + this.windows = windows; + this.icons = []; + + for (let i = 0; i < windows.length; i++) { + let win = windows[i]; + let icon = new WindowIcon(win, settings); + + this.addItem(icon.actor, icon.label); + this.icons.push(icon); + } + } +}); + function doAltTab(display, screen, window, binding) { - let behaviour = settings.get_string(SETTINGS_BEHAVIOUR_KEY); - - // alt-tab having no effect is unexpected, even with wrong settings - if (!(behaviour in MODES)) - behaviour = 'all_thumbnails'; - if (Main.wm._workspaceSwitcherPopup) Main.wm._workspaceSwitcherPopup.actor.hide(); let modifiers = binding.get_modifiers() let backwards = modifiers & Meta.VirtualModifier.SHIFT_MASK; - let constructor = MODES[behaviour]; - let popup = new constructor(settings); + let popup = new AltTabPopup(settings); if (!popup.show(backwards, binding.get_name(), binding.get_mask())) popup.destroy(); } diff --git a/extensions/alternate-tab/org.gnome.shell.extensions.alternate-tab.gschema.xml.in b/extensions/alternate-tab/org.gnome.shell.extensions.alternate-tab.gschema.xml.in index 1eec02f3..88ac7980 100644 --- a/extensions/alternate-tab/org.gnome.shell.extensions.alternate-tab.gschema.xml.in +++ b/extensions/alternate-tab/org.gnome.shell.extensions.alternate-tab.gschema.xml.in @@ -1,31 +1,25 @@ - - - + + + + - - 'all_thumbnails' - <_summary>The alt tab behaviour. + + 'both' + <_summary>The application icon mode. <_description> - Sets the Alt-Tab behaviour. Possible values are: all_thumbnails and workspace_icons. - See the configuration dialogs for details. + Configures how the windows are shown in the switcher. Valid possibilities + are 'thumbnail-only' (shows a thumbnail of the window), 'app-icon-only' + (shows only the application icon) or 'both'. - + false - Bring each selected window to the front. + Limit switcher to current workspace. - Bring each window to the front in turn as Alt+Tab is pressed. - This setting applies only when the selected mode is 'workspace_icons'. - - - - true - Show small application icon. - - If true, a small application icon is overlayed to each window thumbnail. - This setting applies only when the selected mode is 'all_thumbnails'. + If true, only windows from the current workspace are shown in the switcher. + Otherwise, all windows are included. diff --git a/extensions/alternate-tab/prefs.js b/extensions/alternate-tab/prefs.js index f5d34563..6f1856b3 100644 --- a/extensions/alternate-tab/prefs.js +++ b/extensions/alternate-tab/prefs.js @@ -19,31 +19,13 @@ const ExtensionUtils = imports.misc.extensionUtils; const Me = ExtensionUtils.getCurrentExtension(); const Convenience = Me.imports.convenience; -const SETTINGS_BEHAVIOUR_KEY = 'behaviour'; -const SETTINGS_HIGHLIGHT_KEY = 'highlight-selected'; -const SETTINGS_SHOW_APP_ICON_KEY = 'show-app-icon'; +const SETTINGS_APP_ICON_MODE = 'app-icon-mode'; +const SETTINGS_CURRENT_WORKSPACE_ONLY = 'current-workspace-only'; const MODES = { - all_thumbnails: { - name: N_("All & Thumbnails"), - description: N_("This mode presents all applications from all workspaces in one selection \ -list. Instead of using the application icon of every window, it uses small \ -thumbnails resembling the window itself."), - extra_widgets: [ - { label: N_("Show overlaid application icon"), key: SETTINGS_SHOW_APP_ICON_KEY } - ] - }, - workspace_icons: { - name: N_("Workspace & Icons"), - description: N_("This mode lets you switch between the applications of your current \ -workspace and gives you additionally the option to switch to the last used \ -application of your previous workspace. This is always the last symbol in \ -the list and is separated by a separator/vertical line if available. \n\ -Every window is represented by its application icon."), - extra_widgets: [ - { label: N_("Move current selection to front before closing the popup"), key: SETTINGS_HIGHLIGHT_KEY } - ] - } + thumbnail_only: N_("Thumbnail only"), + app_icon_only: N_("Application icon only"), + both: N_("Thumbnail and application icon"), }; const AltTabSettingsWidget = new GObject.Class({ @@ -53,65 +35,40 @@ const AltTabSettingsWidget = new GObject.Class({ _init : function(params) { this.parent(params); - this.column_spacing = 10; this.margin = 10; + this.orientation = Gtk.Orientation.VERTICAL; this._settings = Convenience.getSettings(); - let introLabel = _("The Alternate Tab can be used in different modes, that \ -affect the way windows are chosen and presented."); - - this.attach(new Gtk.Label({ label: introLabel, wrap: true, sensitive: true, - margin_bottom: 10, margin_top: 5 }), - 0, 0, 2, 1); + let presentLabel = _("Present windows as"); + this.add(new Gtk.Label({ label: presentLabel, sensitive: true, + margin_bottom: 10, margin_top: 5 })); let top = 1; let radio = null; - let currentMode = this._settings.get_string(SETTINGS_BEHAVIOUR_KEY); + let currentMode = this._settings.get_string(SETTINGS_APP_ICON_MODE); for (let mode in MODES) { // copy the mode variable because it has function scope, not block scope // so cannot be used in a closure let modeCapture = mode; - let obj = MODES[mode]; - let name = Gettext.gettext(obj.name); - let description = Gettext.gettext(obj.description); - let nextra = obj.extra_widgets.length; + let name = Gettext.gettext(MODES[mode]); radio = new Gtk.RadioButton({ group: radio, label: name, valign: Gtk.Align.START }); radio.connect('toggled', Lang.bind(this, function(widget) { if (widget.active) - this._settings.set_string(SETTINGS_BEHAVIOUR_KEY, modeCapture); - this._updateSensitivity(widget, widget.active); + this._settings.set_string(SETTINGS_APP_ICON_MODE, modeCapture); })); - this.attach(radio, 0, top, 1, nextra + 1); - - let descriptionLabel = new Gtk.Label({ label: description, wrap: true, sensitive: true, - xalign: 0.0, justify: Gtk.Justification.FILL }); - this.attach(descriptionLabel, 1, top, 1, 1); - - radio._extra = []; - for (let i = 0; i < nextra; i++) { - let key = obj.extra_widgets[i].key; - let label = Gettext.gettext(obj.extra_widgets[i].label); - - let extra = new Gtk.CheckButton({ label: label }); - this._settings.bind(key, extra, 'active', Gio.SettingsBindFlags.DEFAULT); - - radio._extra.push(extra); - this.attach(extra, 1, top + i + 1, 1, 1); - } + this.add(radio); if (mode == currentMode) radio.active = true; - this._updateSensitivity(radio, radio.active); - - top += nextra + 1; + top += 1; } - }, - _updateSensitivity: function(widget, active) { - for (let i = 0; i < widget._extra.length; i++) - widget._extra[i].sensitive = active; + let check = new Gtk.CheckButton({ label: _("Show only windows in the current workspace"), + margin_top: 12 }); + this._settings.bind(SETTINGS_CURRENT_WORKSPACE_ONLY, check, 'active', Gio.SettingsBindFlags.DEFAULT); + this.add(check); }, }); diff --git a/extensions/alternate-tab/workspaceIcons.js b/extensions/alternate-tab/workspaceIcons.js deleted file mode 100644 index 25ca7c5e..00000000 --- a/extensions/alternate-tab/workspaceIcons.js +++ /dev/null @@ -1,274 +0,0 @@ -/* -*- mode: js2; js2-basic-offset: 4; indent-tabs-mode: nil -*- */ - -const Clutter = imports.gi.Clutter; -const Gdk = imports.gi.Gdk; -const Gio = imports.gi.Gio; -const Gtk = imports.gi.Gtk; -const Lang = imports.lang; -const Mainloop = imports.mainloop; -const Meta = imports.gi.Meta; -const Shell = imports.gi.Shell; -const St = imports.gi.St; - -const AltTab = imports.ui.altTab; -const Main = imports.ui.main; -const ModalDialog = imports.ui.modalDialog; -const Tweener = imports.ui.tweener; -const WindowManager = imports.ui.windowManager; - -const Gettext = imports.gettext.domain('gnome-shell-extensions'); -const _ = Gettext.gettext; -const N_ = function(e) { return e }; - -const SETTINGS_HIGHLIGHT_SELECTED_KEY = 'highlight-selected'; - -const AltTabPopupWorkspaceIcons = new Lang.Class({ - Name: 'AlternateTab.AltTabPopupWorkspaceIcons', - Extends: AltTab.AltTabPopup, - - _init: function(settings) { - this.parent(); - - this._settings = settings; - }, - - _windowActivated : function(thumbnailList, n) { }, - - show : function(backward, binding, mask) { - let appSys = Shell.AppSystem.get_default(); - let apps = appSys.get_running (); - - if (!apps.length) - return false; - - if (!Main.pushModal(this.actor)) { - // Probably someone else has a pointer grab, try again with keyboard only - if (!Main.pushModal(this.actor, global.get_current_time(), Meta.ModalOptions.POINTER_ALREADY_GRABBED)) { - return false; - } - } - this._haveModal = true; - this._modifierMask = AltTab.primaryModifier(mask); - - this.actor.connect('key-press-event', Lang.bind(this, this._keyPressEvent)); - this.actor.connect('key-release-event', Lang.bind(this, this._keyReleaseEvent)); - - this.actor.connect('button-press-event', Lang.bind(this, this._clickedOutside)); - this.actor.connect('scroll-event', Lang.bind(this, this._onScroll)); - - this._appSwitcher = new WindowSwitcher(apps, this); - this.actor.add_actor(this._appSwitcher.actor); - this._appSwitcher.connect('item-activated', Lang.bind(this, this._appActivated)); - this._appSwitcher.connect('item-entered', Lang.bind(this, this._appEntered)); - - this._appIcons = this._appSwitcher.icons; - - // Need to force an allocation so we can figure out whether we - // need to scroll when selecting - this.actor.opacity = 0; - this.actor.show(); - this.actor.get_allocation_box(); - - this._highlight_selected = this._settings.get_boolean(SETTINGS_HIGHLIGHT_SELECTED_KEY); - - // Make the initial selection - if (binding == 'switch_group') { - //see AltTab.AltTabPopup.show function - //cached windows are always of length one, so select first app and the window - //the direction doesn't matter, so ignore backward - this._select(0, 0); - } else if (binding == 'switch_group_backward') { - this._select(0, 0); - } else if (binding == 'switch_windows_backward') { - this._select(this._appIcons.length - 1); - } else if (this._appIcons.length == 1) { - this._select(0); - } else if (backward) { - this._select(this._appIcons.length - 1); - } else { - this._select(1); - } - - - // There's a race condition; if the user released Alt before - // we got the grab, then we won't be notified. (See - // https://bugzilla.gnome.org/show_bug.cgi?id=596695 for - // details.) So we check now. (Have to do this after updating - // selection.) - let [x, y, mods] = global.get_pointer(); - if (!(mods & this._modifierMask)) { - this._finish(); - return false; - } - - // We delay showing the popup so that fast Alt+Tab users aren't - // disturbed by the popup briefly flashing. - this._initialDelayTimeoutId = Mainloop.timeout_add(AltTab.POPUP_DELAY_TIMEOUT, - Lang.bind(this, function () { - this.actor.opacity = 255; - this._initialDelayTimeoutId = 0; - })); - - return true; - }, - - _select : function(app, window, forceAppFocus) { - if (app != this._currentApp || window == null) { - if (this._thumbnails) - this._destroyThumbnails(); - } - - if (this._thumbnailTimeoutId != 0) { - Mainloop.source_remove(this._thumbnailTimeoutId); - this._thumbnailTimeoutId = 0; - } - - this._thumbnailsFocused = (window != null) && !forceAppFocus; - - this._currentApp = app; - this._currentWindow = window ? window : -1; - this._appSwitcher.highlight(app, this._thumbnailsFocused); - - if (window != null) { - if (!this._thumbnails) - this._createThumbnails(); - this._currentWindow = window; - this._thumbnails.highlight(window, forceAppFocus); - } else if (this._appIcons[this._currentApp].cachedWindows.length > 1 && - !forceAppFocus) { - this._thumbnailTimeoutId = Mainloop.timeout_add ( - AltTab.THUMBNAIL_POPUP_TIME, - Lang.bind(this, this._timeoutPopupThumbnails)); - } - if (this._highlight_selected) { - let current_app = this._appIcons[this._currentApp]; - Main.activateWindow(current_app.cachedWindows[0]); - } - }, - - _finish : function() { - let app = this._appIcons[this._currentApp]; - if (!app) - return; - - /* - * We've to restore the original Z-depth and order of all windows. - * - * Gnome-shell doesn't give an option to change Z-depth without - * messing the window's user_time. - * - * Pointless if the popup wasn't showed. - */ - if (this._highlight_selected && this.actor.opacity == 255) { - for (let i = this._appIcons.length - 2; i >= 0; i--) { - let app_walker = this._appIcons[i]; - Main.activateWindow(app_walker.cachedWindows[0], global.get_current_time() - i - 1); - } - } - - Main.activateWindow(app.cachedWindows[0]); - this.destroy(); - } - -}); - -const AppIcon = new Lang.Class({ - Name: 'AlternateTab.AppIcon', - Extends: AltTab.AppIcon, - - _init: function(app, window) { - this.app = app; - - this.cachedWindows = []; - this.cachedWindows.push(window); - - this.actor = new St.BoxLayout({ style_class: 'alt-tab-app', - vertical: true }); - this.icon = null; - this._iconBin = new St.Bin({ x_fill: true, y_fill: true }); - - this.actor.add(this._iconBin, { x_fill: false, y_fill: false } ); - - let title = window.get_title(); - if (title) { - this.label = new St.Label({ text: title }); - let bin = new St.Bin({ x_align: St.Align.MIDDLE }); - bin.add_actor(this.label); - this.actor.add(bin); - } - else { - this.label = new St.Label({ text: this.app.get_name() }); - this.actor.add(this.label, { x_fill: false }); - } - } -}); - -const WindowSwitcher = new Lang.Class({ - Name: 'AlternateTab.WindowSwitcher', - Extends: AltTab.AppSwitcher, - - _init : function(apps, altTabPopup) { - // Horrible HACK! - // We inherit from AltTab.AppSwitcher, but only chain up to - // AltTab.SwitcherList._init, to bypass AltTab.AppSwitcher._init - AltTab.SwitcherList.prototype._init.call(this, true); - - // Construct the AppIcons, sort by time, add to the popup - let activeWorkspace = global.screen.get_active_workspace(); - let workspaceIcons = []; - let otherIcons = []; - for (let i = 0; i < apps.length; i++) { - // Cache the window list now; we don't handle dynamic changes here, - // and we don't want to be continually retrieving it - let windows = apps[i].get_windows(); - - for(let j = 0; j < windows.length; j++) { - let appIcon = new AppIcon(apps[i], windows[j]); - if (this._isWindowOnWorkspace(windows[j], activeWorkspace)) { - workspaceIcons.push(appIcon); - } - else { - otherIcons.push(appIcon); - } - } - } - - workspaceIcons.sort(Lang.bind(this, this._sortAppIcon)); - otherIcons.sort(Lang.bind(this, this._sortAppIcon)); - - if(otherIcons.length > 0) { - let mostRecentOtherIcon = otherIcons[0]; - otherIcons = []; - otherIcons.push(mostRecentOtherIcon); - } - - this.icons = []; - this._arrows = []; - for (let i = 0; i < workspaceIcons.length; i++) - this._addIcon(workspaceIcons[i]); - if (workspaceIcons.length > 0 && otherIcons.length > 0) - this.addSeparator(); - for (let i = 0; i < otherIcons.length; i++) - this._addIcon(otherIcons[i]); - - this._curApp = -1; - this._iconSize = 0; - this._altTabPopup = altTabPopup; - this._mouseTimeOutId = 0; - }, - - - _isWindowOnWorkspace: function(w, workspace) { - if (w.get_workspace() == workspace) - return true; - return false; - }, - - _sortAppIcon : function(appIcon1, appIcon2) { - let t1 = appIcon1.cachedWindows[0].get_user_time(); - let t2 = appIcon2.cachedWindows[0].get_user_time(); - if (t2 > t1) return 1; - else return -1; - } -}); -