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; - } -}); -