123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486 |
- // -*- mode: js; js-indent-level: 4; indent-tabs-mode: nil -*-
- const Lang = imports.lang;
- const Clutter = imports.gi.Clutter;
- const St = imports.gi.St;
- const Meta = imports.gi.Meta;
- const Mainloop = imports.mainloop;
- const Main = imports.ui.main;
- const Cinnamon = imports.gi.Cinnamon;
- const DISABLE_HOVER_TIMEOUT = 500; // milliseconds
- function sortWindowsByUserTime(win1, win2) {
- let t1 = win1.get_user_time();
- let t2 = win2.get_user_time();
- this.minimizedAwareAltTab = global.settings.get_boolean("alttab-minimized-aware");
- if (this.minimizedAwareAltTab) {
- let m1 = win1.minimized;
- let m2 = win2.minimized;
- if (m1 == m2) {
- return (t2 > t1) ? 1 : -1;
- }
- else {
- return m1 ? 1 : -1;
- }
- }
- else {
- return (t2 > t1) ? 1 : -1;
- }
- }
- function matchSkipTaskbar(win) {
- return !win.is_skip_taskbar();
- }
- function matchWmClass(win) {
- return win.get_wm_class() == this && !win.is_skip_taskbar();
- }
- function matchWorkspace(win) {
- return win.get_workspace() == this && !win.is_skip_taskbar();
- }
- function primaryModifier(mask) {
- if (mask == 0)
- return 0;
- let primary = 1;
- while (mask > 1) {
- mask >>= 1;
- primary <<= 1;
- }
- return primary;
- }
- function getWindowsForBinding(binding) {
- // Construct a list with all windows
- let windows = [];
- let windowActors = global.get_window_actors();
- for (let i in windowActors)
- windows.push(windowActors[i].get_meta_window());
- windows = windows.filter(Main.isInteresting);
- windows = windows.filter(w => w.get_monitor() === global.screen.get_current_monitor()) // 过滤当前显示器的窗口
- switch (binding.get_name()) {
- case 'switch-panels':
- case 'switch-panels-backward':
- // Switch between windows of all workspaces
- windows = windows.filter(matchSkipTaskbar);
- break;
- case 'switch-group':
- case 'switch-group-backward':
- // Switch between windows of the same application
- let focused = global.display.focus_window ? global.display.focus_window : windows[0];
- windows = windows.filter(matchWmClass, focused.get_wm_class());
- this._showAllWorkspaces = global.settings.get_boolean("alttab-switcher-show-all-workspaces");
- if (!this._showAllWorkspaces) {
- windows = windows.filter(matchWorkspace, global.workspace_manager.get_active_workspace());
- }
- break;
- default:
- // Switch between windows of current workspace
- this._showAllWorkspaces = global.settings.get_boolean("alttab-switcher-show-all-workspaces");
- if (!this._showAllWorkspaces) {
- windows = windows.filter(matchWorkspace, global.workspace_manager.get_active_workspace());
- }
- break;
- }
- // Sort by user time
- windows.sort(sortWindowsByUserTime);
- return windows;
- }
- function AppSwitcher() {
- this._init.apply(this, arguments);
- }
- AppSwitcher.prototype = {
- _init: function (binding) {
- this._initialDelayTimeoutId = null;
- this._binding = binding;
- this._windows = getWindowsForBinding(binding);
- this._haveModal = false;
- this._destroyed = false;
- this._motionTimeoutId = 0;
- this._currentIndex = this._windows.indexOf(global.display.focus_window);
- if (this._currentIndex < 0) {
- this._currentIndex = 0;
- }
- this._modifierMask = primaryModifier(binding.get_mask());
- this._tracker = Cinnamon.WindowTracker.get_default();
- this._windowManager = global.window_manager;
- this._dcid = this._windowManager.connect('destroy', Lang.bind(this, this._windowDestroyed));
- this._mcid = this._windowManager.connect('map', Lang.bind(this, this._activateSelected));
- this._enforcePrimaryMonitor = global.settings.get_boolean("alttab-switcher-enforce-primary-monitor");
- this._updateActiveMonitor();
- },
- _setupModal: function () {
- this._haveModal = Main.pushModal(this.actor);
- if (!this._haveModal) {
- // Probably someone else has a pointer grab, try again with keyboard only
- this._haveModal = Main.pushModal(this.actor, global.get_current_time(), Meta.ModalOptions.POINTER_ALREADY_GRABBED);
- }
- if (!this._haveModal)
- this._failedGrabAction();
- else {
- // Initially disable hover so we ignore the enter-event if
- // the switcher appears underneath the current pointer location
- this._disableHover();
- 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('scroll-event', Lang.bind(this, this._scrollEvent));
- this.actor.connect('button-press-event', Lang.bind(this, this.destroy));
- // 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._failedGrabAction();
- return false;
- }
- // We delay showing the popup so that fast Alt+Tab users aren't
- // disturbed by the popup briefly flashing.
- let delay = global.settings.get_int("alttab-switcher-delay");
- this._initialDelayTimeoutId = Mainloop.timeout_add(delay, Lang.bind(this, this._show));
- }
- return this._haveModal;
- },
- _popModal: function () {
- if (this._haveModal) {
- Main.popModal(this.actor);
- this._haveModal = false;
- }
- },
- _show: function () {
- throw new Error("Abstract method _show not implemented");
- },
- _hide: function () {
- throw new Error("Abstract method _hide not implemented");
- },
- _onDestroy: function () {
- throw new Error("Abstract method _onDestroy not implemented");
- },
- _createList: function () {
- throw new Error("Abstract method _createList not implemented");
- },
- _updateList: function () {
- throw new Error("Abstract method _updateList not implemented");
- },
- _selectNext: function () {
- throw new Error("Abstract method _selectNext not implemented");
- },
- _selectPrevious: function () {
- throw new Error("Abstract method _selectPrevious not implemented");
- },
- _onWorkspaceSelected: function () {
- throw new Error("Abstract method _onWorkspaceSelected not implemented");
- },
- _checkSwitchTime: function () {
- return true;
- },
- _setCurrentWindow: function (window) {
- },
- _next: function () {
- if (!this._windows)
- return;
- if (this._windows.length <= 1) {
- this._currentIndex = 0;
- this._updateList(0);
- } else {
- this.actor.set_reactive(false);
- this._selectNext();
- this.actor.set_reactive(true);
- }
- this._setCurrentWindow(this._windows[this._currentIndex]);
- },
- _previous: function () {
- if (!this._windows)
- return;
- if (this._windows.length <= 1) {
- this._currentIndex = 0;
- this._updateList(0);
- } else {
- this.actor.set_reactive(false);
- this._selectPrevious();
- this.actor.set_reactive(true);
- }
- this._setCurrentWindow(this._windows[this._currentIndex]);
- },
- _select: function (index) {
- if (!this._windows)
- return;
- this._currentIndex = index;
- this._setCurrentWindow(this._windows[this._currentIndex]);
- },
- _updateActiveMonitor: function () {
- this._activeMonitor = null;
- if (!this._enforcePrimaryMonitor)
- this._activeMonitor = Main.layoutManager.currentMonitor;
- if (!this._activeMonitor)
- this._activeMonitor = Main.layoutManager.primaryMonitor;
- return this._activeMonitor;
- },
- _keyPressEvent: function (actor, event) {
- let modifiers = Cinnamon.get_event_state(event);
- let symbol = event.get_key_symbol();
- let keycode = event.get_key_code();
- // This relies on the fact that Clutter.ModifierType is the same as Gdk.ModifierType
- let action = global.display.get_keybinding_action(keycode, modifiers);
- this._disableHover();
- // Switch workspace
- if (modifiers & Clutter.ModifierType.CONTROL_MASK &&
- (symbol === Clutter.KEY_Right || symbol === Clutter.KEY_Left)) {
- if (this._switchWorkspace(symbol))
- return true;
- }
- // Extra keys
- switch (symbol) {
- case Clutter.KEY_Escape:
- // Esc -> Close switcher
- this.destroy();
- return true;
- case Clutter.KEY_Return:
- case Clutter.KEY_KP_Enter:
- // Enter -> Select active window
- this._activateSelected();
- return true;
- case Clutter.KEY_d:
- case Clutter.KEY_D:
- // D -> Show desktop
- this._showDesktop();
- return true;
- case Clutter.KEY_Right:
- case Clutter.KEY_Down:
- // Right/Down -> navigate to next preview
- if (this._checkSwitchTime())
- this._next();
- return true;
- case Clutter.KEY_Left:
- case Clutter.KEY_Up:
- // Left/Up -> navigate to previous preview
- if (this._checkSwitchTime())
- this._previous();
- return true;
- }
- // Default alt-tab
- switch (action) {
- case Meta.KeyBindingAction.SWITCH_GROUP:
- case Meta.KeyBindingAction.SWITCH_WINDOWS:
- case Meta.KeyBindingAction.SWITCH_PANELS:
- if (this._checkSwitchTime()) {
- // shift -> backwards
- if (modifiers & Clutter.ModifierType.SHIFT_MASK)
- this._previous();
- else
- this._next();
- }
- return true;
- case Meta.KeyBindingAction.SWITCH_GROUP_BACKWARD:
- case Meta.KeyBindingAction.SWITCH_WINDOWS_BACKWARD:
- case Meta.KeyBindingAction.SWITCH_PANELS_BACKWARD:
- if (this._checkSwitchTime())
- this._previous();
- return true;
- }
- return true;
- },
- _keyReleaseEvent: function (actor, event) {
- let [x, y, mods] = global.get_pointer();
- let state = mods & this._modifierMask;
- if (state == 0) {
- if (this._initialDelayTimeoutId !== 0)
- this._currentIndex = (this._currentIndex + 1) % this._windows.length;
- this._activateSelected();
- }
- return true;
- },
- _failedGrabAction: function () {
- if (!["coverflow", "timeline"].includes(global.settings.get_string('alttab-switcher-style'))) {
- this._keyReleaseEvent(null, null);
- }
- },
- // allow navigating by mouse-wheel scrolling
- _scrollEvent: function (actor, event) {
- if (event.get_scroll_direction() == Clutter.ScrollDirection.SMOOTH)
- return Clutter.EVENT_STOP;
- if (this._checkSwitchTime()) {
- actor.set_reactive(false);
- if (event.get_scroll_direction() == Clutter.ScrollDirection.UP)
- this._previous();
- else if (event.get_scroll_direction() == Clutter.ScrollDirection.DOWN)
- this._next();
- actor.set_reactive(true);
- }
- return true;
- },
- _disableHover: function () {
- this._mouseActive = false;
- if (this._motionTimeoutId != 0)
- Mainloop.source_remove(this._motionTimeoutId);
- this._motionTimeoutId = Mainloop.timeout_add(DISABLE_HOVER_TIMEOUT, Lang.bind(this, this._mouseTimedOut));
- },
- _mouseTimedOut: function () {
- this._motionTimeoutId = 0;
- this._mouseActive = true;
- },
- _switchWorkspace: function (direction) {
- if (global.workspace_manager.n_workspaces < 2)
- return false;
- let current = global.workspace_manager.get_active_workspace_index();
- if (direction === Clutter.KEY_Left)
- Main.wm.actionMoveWorkspaceLeft();
- else if (direction === Clutter.KEY_Right)
- Main.wm.actionMoveWorkspaceRight();
- else
- return false;
- if (current === global.workspace_manager.get_active_workspace_index())
- return false;
- let workspace = global.workspace_manager.get_active_workspace();
- this._onWorkspaceSelected(workspace);
- return true;
- },
- _windowDestroyed: function (wm, actor) {
- this._removeDestroyedWindow(actor.meta_window);
- },
- _removeDestroyedWindow: function (window) {
- for (let i in this._windows) {
- if (window == this._windows[i]) {
- if (this._windows.length == 1)
- this.destroy();
- else {
- this._windows.splice(i, 1);
- if (this._previews && this._previews[i]) {
- this._previews[i].destroy();
- this._previews.splice(i, 1);
- }
- if (i < this._currentIndex)
- this._currentIndex--;
- else
- this._currentIndex %= this._windows.length;
- this._updateList(0);
- this._setCurrentWindow(this._windows[this._currentIndex]);
- }
- return;
- }
- }
- },
- _activateSelected: function () {
- const _window = this._windows[this._currentIndex]
- const workspace_num = _window.get_workspace().index();
- Main.activateWindow(_window, global.get_current_time(), workspace_num);
- this._warpMouse = global.settings.get_boolean("alttab-switcher-warp-mouse-pointer");
- if (this._warpMouse) {
- const rect = _window.get_frame_rect();
- const x = rect.x + rect.width / 2;
- const y = rect.y + rect.height / 2;
- this._pointer = Clutter.get_default_backend().get_default_seat().create_virtual_device(Clutter.InputDeviceType.POINTER_DEVICE);
- this._pointer.notify_absolute_motion(global.get_current_time(), x, y);
- }
- if (!this._destroyed)
- this.destroy();
- },
- _showDesktop: function () {
- for (let i in this._windows) {
- if (!this._windows[i].minimized)
- this._windows[i].minimize();
- }
- this.destroy();
- },
- destroy: function () {
- this._destroyed = true;
- this._popModal();
- if (this._initialDelayTimeoutId !== 0)
- this._destroyActors();
- else
- this._hide();
- if (this._initialDelayTimeoutId !== null && this._initialDelayTimeoutId > 0) {
- Mainloop.source_remove(this._initialDelayTimeoutId);
- this._initialDelayTimeoutId = 0;
- }
- this._onDestroy();
- this._windows = null;
- if (this._motionTimeoutId != 0) {
- Mainloop.source_remove(this._motionTimeoutId);
- this._motionTimeoutId = 0;
- }
- if (this._dcid > 0) {
- this._windowManager.disconnect(this._dcid);
- this._dcid = 0;
- }
- if (this._mcid > 0) {
- this._windowManager.disconnect(this._mcid);
- this._mcid = 0;
- }
- }
- };
|