// -*- mode: js; js-indent-level: 4; indent-tabs-mode: nil -*- const Clutter = imports.gi.Clutter; const Gtk = imports.gi.Gtk; const Lang = imports.lang; const Meta = imports.gi.Meta; const Shell = imports.gi.Shell; const St = imports.gi.St; const Main = imports.ui.main; const SwitcherPopup = imports.ui.switcherPopup; const Params = imports.misc.params; const Tweener = imports.ui.tweener; const POPUP_APPICON_SIZE = 96; const POPUP_FADE_TIME = 0.1; // seconds const SortGroup = { TOP: 0, MIDDLE: 1, BOTTOM: 2 }; const CtrlAltTabManager = new Lang.Class({ Name: 'CtrlAltTabManager', _init: function() { this._items = []; }, addGroup: function(root, name, icon, params) { let item = Params.parse(params, { sortGroup: SortGroup.MIDDLE, proxy: root, focusCallback: null }); item.root = root; item.name = name; item.iconName = icon; this._items.push(item); root.connect('destroy', Lang.bind(this, function() { this.removeGroup(root); })); global.focus_manager.add_group(root); }, removeGroup: function(root) { global.focus_manager.remove_group(root); for (let i = 0; i < this._items.length; i++) { if (this._items[i].root == root) { this._items.splice(i, 1); return; } } }, focusGroup: function(item) { if (item.window) { Main.activateWindow(item.window); } else if (item.focusCallback) { item.focusCallback(); } else { if (global.stage_input_mode == Shell.StageInputMode.NONREACTIVE || global.stage_input_mode == Shell.StageInputMode.NORMAL) global.set_stage_input_mode(Shell.StageInputMode.FOCUSED); item.root.navigate_focus(null, Gtk.DirectionType.TAB_FORWARD, false); } }, // Sort the items into a consistent order; panel first, tray last, // and everything else in between, sorted by X coordinate, so that // they will have the same left-to-right ordering in the // Ctrl-Alt-Tab dialog as they do onscreen. _sortItems: function(a, b) { if (a.sortGroup != b.sortGroup) return a.sortGroup - b.sortGroup; let y; if (a.x == undefined) { if (a.window) a.x = a.window.get_compositor_private().x; else [a.x, y] = a.proxy.get_transformed_position(); } if (b.x == undefined) { if (b.window) b.x = b.window.get_compositor_private().x; else [b.x, y] = b.proxy.get_transformed_position(); } return a.x - b.x; }, popup: function(backward, binding, mask) { // Start with the set of focus groups that are currently mapped let items = this._items.filter(function (item) { return item.proxy.mapped; }); // And add the windows metacity would show in its Ctrl-Alt-Tab list if (!Main.overview.visible) { let screen = global.screen; let display = screen.get_display(); let windows = display.get_tab_list(Meta.TabList.DOCKS, screen, screen.get_active_workspace ()); let windowTracker = Shell.WindowTracker.get_default(); let textureCache = St.TextureCache.get_default(); for (let i = 0; i < windows.length; i++) { let icon; let app = windowTracker.get_window_app(windows[i]); if (app) icon = app.create_icon_texture(POPUP_APPICON_SIZE); else icon = textureCache.bind_pixbuf_property(windows[i], 'icon'); items.push({ window: windows[i], name: windows[i].title, iconActor: icon, sortGroup: SortGroup.MIDDLE }); } } if (!items.length) return; items.sort(Lang.bind(this, this._sortItems)); if (!this._popup) { this._popup = new CtrlAltTabPopup(items); this._popup.show(backward, binding, mask); this._popup.actor.connect('destroy', Lang.bind(this, function() { this._popup = null; })); } } }); const CtrlAltTabPopup = new Lang.Class({ Name: 'CtrlAltTabPopup', Extends: SwitcherPopup.SwitcherPopup, _createSwitcher: function() { this._switcherList = new CtrlAltTabSwitcher(this._items); return true; }, _initialSelection: function(backward, binding) { if (binding == 'switch-panels') { if (backward) this._selectedIndex = this._items.length - 1; } else if (binding == 'switch-panels-backward') { if (!backward) this._selectedIndex = this._items.length - 1; } this._select(this._selectedIndex); }, _keyPressHandler: function(keysym, backwards, action) { if (action == Meta.KeyBindingAction.SWITCH_PANELS) this._select(backwards ? this._previous() : this._next()); else if (action == Meta.KeyBindingAction.SWITCH_PANELS_BACKWARD) this._select(backwards ? this._next() : this._previous()); else if (keysym == Clutter.Left) this._select(this._previous()); else if (keysym == Clutter.Right) this._select(this._next()); }, _finish : function() { this.parent(); Main.ctrlAltTabManager.focusGroup(this._items[this._selectedIndex]); }, }); const CtrlAltTabSwitcher = new Lang.Class({ Name: 'CtrlAltTabSwitcher', Extends: SwitcherPopup.SwitcherList, _init : function(items) { this.parent(true); for (let i = 0; i < items.length; i++) this._addIcon(items[i]); }, _addIcon : function(item) { let box = new St.BoxLayout({ style_class: 'alt-tab-app', vertical: true }); let icon = item.iconActor; if (!icon) { icon = new St.Icon({ icon_name: item.iconName, icon_size: POPUP_APPICON_SIZE }); } box.add(icon, { x_fill: false, y_fill: false } ); let text = new St.Label({ text: item.name }); box.add(text, { x_fill: false }); this.addItem(box, text); } });