2682 lines
94 KiB
JavaScript
Raw Normal View History

2024-07-08 22:46:35 +02:00
/* extension.js
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
* SPDX-License-Identifier: GPL-2.0-or-later
*/
import Clutter from 'gi://Clutter';
import Meta from 'gi://Meta';
import St from 'gi://St';
import Shell from 'gi://Shell';
import Gio from 'gi://Gio';
import * as Main from 'resource:///org/gnome/shell/ui/main.js';
import { Extension } from 'resource:///org/gnome/shell/extensions/extension.js';
// Window edge action
const WindowEdgeAction = {
NONE: 0, // No action
WAIT_GESTURE: 0x01, // Wait for gesture flag
MOVE: 0x02, // Move flag
RESIZE: 0x04, // Resize flag
GESTURE_LEFT: 0x10, // Gesture Left
GESTURE_RIGHT: 0x20, // Gesture Right
GESTURE_UP: 0x40, // Gesture Up
GESTURE_DOWN: 0x80, // Gesture Down
GESTURE_UP_LEFT: 0x100, // Gesture Up Left
GESTURE_UP_RIGHT: 0x200, // Gesture Up Right
GESTURE_HORIZONTAL: 0x400, // Non-Window Gestures
GESTURE_VERTICAL: 0x800,
RESIZE_LEFT: 0x1000, // Resize Flags
RESIZE_RIGHT: 0x2000,
RESIZE_TOP: 0x4000,
RESIZE_BOTTOM: 0x8000,
MOVE_SNAP_TOP: 0x10000, // Snap Flags
MOVE_SNAP_LEFT: 0x20000,
MOVE_SNAP_RIGHT: 0x40000
};
// Window Blacklist Classes
const WindowClassBlacklist = [
"gjs"
];
// Manager Class
class Manager {
// Init Extension
constructor(ext) {
// Get settings
this._settings = ext.getSettings();
// Gestures are hooked by WGS
this._hooked = false;
// Create virtual devices
const seat = Clutter.get_default_backend().get_default_seat();
this._virtualTouchpad = seat.create_virtual_device(
Clutter.InputDeviceType.POINTER_DEVICE
);
this._virtualKeyboard = seat.create_virtual_device(
Clutter.InputDeviceType.KEYBOARD_DEVICE
);
// Init variables - keep enable() clean
this._clearVars();
// Capture Touchpad Event
this._gestureCallbackID = global.stage.connect(
'captured-event::touchpad',
this._touchpadEvent.bind(this)
);
// init 3 or 4 fingers config support
this._initFingerCountFlip();
// action widget holder
this._actionWidgets = {};
// Desktop Theme Setting
this._isettings = new Gio.Settings({
schema: 'org.gnome.desktop.interface'
});
}
// Clear potentially running timeout/interval
destroyTimers() {
if (this._holdTo) {
// hold waiter
clearTimeout(this._holdTo);
this._holdTo = null;
}
if (this._actionWidgets.resetWinlist) {
// Alt+Tab timeout
clearTimeout(this._actionWidgets.resetWinlist);
this._actionWidgets.resetWinlist = null;
}
if (this._keyRepeatInterval) {
// Key repeat interval
clearInterval(this._keyRepeatInterval);
this._keyRepeatInterval = null;
}
if (this._flingInterval) {
// Kinetic interval
clearInterval(this._flingInterval);
this._flingInterval = null;
}
if (this._actionWidgets.tilerHider) {
clearTimeout(this._actionWidgets.tilerHider);
this._actionWidgets.tilerHider = null;
}
}
// Cleanup Extension
destroy() {
// clear timers
this.destroyTimers();
// restore default GNOME 3 fingers gesture
this._restoreFingerCountFlip();
// Release Touchpad Event Capture
global.stage.disconnect(this._gestureCallbackID);
// Cleanup virtual devices
this._virtualTouchpad = null;
this._virtualKeyboard = null;
// Cleanup all variables
this._clearVars();
this._isettings = null;
this._settings = null;
this._ShaderClass = null;
}
// Initialize variables
_clearVars() {
// Target window to manage
this._targetWindow = null;
// Mouse start position
this._startPos = {
x: 0, y: 0
};
// Mouse start position
this._movePos = {
x: 0, y: 0
};
// Monitor Id
this._monitorId = 0;
// Monitor Workarea
this._monitorArea = null;
// Starting Window Area
this._startWinArea = null;
// Edge Action
this._edgeAction = WindowEdgeAction.NONE;
this._edgeGestured = 0;
this._swipeIsWin = false;
this._isActiveWin = false;
this._tapHold = 0;
this._tapHoldWin = null;
this._tapHoldTick = 0;
// Pinch
this._gesture = {
begin: false,
fingers: 0,
progress: 0,
velocity: null,
action: 0,
action_id: 0,
action_cmp: 0
};
// Clear window tile preview
// this._hidePreview();
}
// Init 3 or 4 finger count switch mode
_initFingerCountFlip() {
// Move 3-4 Finger Gesture
/*
* Original Hook Logic From (swap-finger-gestures):
* https://github.com/icedman/swap-finger-gestures-3-4
*
*/
this._swipeMods = [
Main.overview._swipeTracker._touchpadGesture,
Main.wm._workspaceAnimation._swipeTracker._touchpadGesture,
Main.overview._overview._controls
._workspacesDisplay._swipeTracker._touchpadGesture,
Main.overview._overview._controls
._appDisplay._swipeTracker._touchpadGesture
];
let me = this;
this._swipeMods.forEach((g) => {
g._newHandleEvent = (actor, event) => {
event._get_touchpad_gesture_finger_count =
event.get_touchpad_gesture_finger_count;
event.get_touchpad_gesture_finger_count = () => {
let real_count = event._get_touchpad_gesture_finger_count();
if (me._hooked || (real_count == me._gestureNumFinger())) {
return 0;
}
else if (real_count >= 3) {
return 3;
}
return 0;
};
if (me._hooked) {
return Clutter.EVENT_STOP;
}
return g._handleEvent(actor, event);
};
global.stage.disconnectObject(g);
global.stage.connectObject(
'captured-event::touchpad',
g._newHandleEvent.bind(g),
g
);
});
}
// Restore 3 or 4 finger count switch mode
_restoreFingerCountFlip() {
// Restore 3 finger gesture
this._swipeMods.forEach((g) => {
global.stage.disconnectObject(g);
global.stage.connectObject(
'captured-event::touchpad',
g._handleEvent.bind(g),
g
);
});
this._swipeMods = [];
}
// Is dark theme?
_isDarkTheme() {
let uit = this._settings.get_int('ui-theme');
if (uit == 0) {
return (this._isettings
.get_string('color-scheme') == 'prefer-dark');
}
return (uit == 2);
}
// Create UI Indicator
_createUi(ui_class, x, y, w, h, icon, parent) {
let ui = new St.Widget({ style_class: ui_class });
if (this._isDarkTheme()) {
ui.add_style_class_name("wgs-dark");
}
// ui.set_clip_to_allocation(true);
ui._icon = null;
ui._parent = parent ? parent : Main.layoutManager.uiGroup;
if (icon) {
ui._icon = new St.Icon({
icon_name: icon,
style_class: 'wgs-widget-icon'
});
ui.add_child(ui._icon);
}
ui.set_position(x, y);
ui.set_size(w, h);
ui.set_pivot_point(0.5, 0.5);
ui.viewShow = (prop, duration) => {
ui.show();
prop.mode = Clutter.AnimationMode.EASE_OUT_QUAD;
prop.duration = duration;
ui.ease(prop);
};
ui.viewHide = (prop) => {
prop.mode = Clutter.AnimationMode.EASE_OUT_QUAD;
prop.duration = duration;
prop.onStopped = () => {
ui.hide();
};
ui.ease(prop);
};
ui.aniRelease = (progress) => {
if (!progress) {
progress = 1.0;
}
if (progress > 0.2) {
ui.ease({
opacity: 0,
scale_x: 0,
scale_y: 0,
duration: Math.round(250 * progress),
mode: Clutter.AnimationMode.EASE_OUT_QUAD,
onStopped: () => {
ui.release();
}
});
}
else {
ui.release();
}
};
ui.release = () => {
// Cleanup
ui.hide();
ui._parent.remove_child(ui);
if (ui._icon) {
ui.remove_child(ui._icon);
ui._icon.destroy();
ui._icon = null;
}
ui.destroy();
ui = null;
};
ui._parent.add_child(ui);
return ui;
}
// Create Shader Effect
_createShader(type, actor, name) {
let fx = new Clutter.ShaderEffect(
{ shader_type: Clutter.ShaderType.FRAGMENT_SHADER }
);
let shader = '';
if (type == 'close') {
shader =
'color.g *= 1-(0.3*value); ' +
'color.b *= 1-(0.34*value); ';
}
else {
shader =
'color.rgb *= 1.0-value; ';
}
fx.set_shader_source(
'uniform sampler2D tex; ' +
'uniform float value; ' +
'void main() { ' +
'vec4 color=texture2D(tex,cogl_tex_coord_in[0].st);' +
shader +
'cogl_color_out = color * cogl_color_in;}'
);
fx.set_uniform_value('tex', 0);
fx.setValue = function (v) {
fx.set_uniform_value('value', v);
}
if (actor) {
fx._fxname = name;
actor.fx = fx;
if (actor.get_effect(name)) {
actor.remove_effect_by_name(name);
}
actor.add_effect_with_name(name, fx);
}
fx.release = () => {
if (actor) {
actor.remove_effect_by_name(actor.fx._fxname);
actor.fx = null;
actor = null;
}
fx = null;
};
return fx;
}
// Velocity functions
_velocityInit() {
let vel = {
data: [],
prev: 0
};
return vel;
}
_velocityTrim(vel) {
const thresholdTime = this._tick() - 150;
const index = vel.data.findIndex(r => r.time >= thresholdTime);
vel.data.splice(0, index);
}
_velocityAppend(vel, v) {
this._velocityTrim(vel);
let vb = Math.abs(v);
let d = vb - vel.prev;
vel.prev = vb;
vel.data.push({ time: this._tick(), delta: d });
}
_velocityCalc(vel) {
this._velocityTrim(vel);
if (vel.data.length < 2)
return 0;
const firstTime = vel.data[0].time;
const lastTime = vel.data[vel.data.length - 1].time;
if (firstTime === lastTime)
return 0;
const totalDelta = vel.data.slice(1).map(
a => a.delta).reduce((a, b) => a + b);
const period = lastTime - firstTime;
return totalDelta / period;
}
_velocityFlingHandler(me) {
if (me._velocityFlingQueue.length == 0) {
clearInterval(me._flingInterval);
me._flingInterval = 0;
return;
}
let now = me._velocityFlingQueue[0];
let clearIt = false;
now.target += now.v * 2;
now.v *= 0.98;
now.n++;
if (me._velocityFlingQueue.length != 1) {
// Another fling called
now.cb(1, target);
clearIt = true;
}
else if (now.target >= now.max || now.n >= now.maxframe) {
if (now.target >= now.max) {
now.target = now.max;
}
now.cb(1, now.target);
clearIt = true;
}
else {
now.cb(0, now.target);
}
if (clearIt) {
me._velocityFlingQueue.splice(0, 1);
}
}
_velocityFling(vel, curr, max, maxframe, cb) {
if (!this._velocityFlingQueue) {
this._velocityFlingQueue = [];
}
this._velocityFlingQueue.push({
target: curr,
v: vel,
max: max,
maxframe: maxframe,
cb: cb,
n: 0
});
if (!this._flingInterval) {
this._flingInterval = setInterval(
this._velocityFlingHandler, 4, this
);
}
}
_tick() {
return new Date().getTime();
}
_isWindowBlacklist(win) {
if (win) {
if (WindowClassBlacklist.indexOf(win.get_wm_class()) == -1 ||
win.get_window_type() === Meta.WindowType.DESKTOP) {
return false;
}
}
return true;
}
// Get padding edge size
_edgeSize() {
return this._settings.get_int('edge-size');
}
// Get top padding edge size
_topEdgeSize() {
return this._settings.get_int('top-edge-size');
}
// Get gesture threshold
_gestureThreshold() {
return this._settings.get_int('gesture-threshold');
}
// Get acceleration
_getAcceleration() {
return (this._settings.get_int('gesture-acceleration') * 0.1);
}
// Get gesture threshold
_gestureCancelThreshold() {
return this._settings.get_int('gesture-cancel-threshold');
}
// Is 3 Finger
_gestureNumFinger() {
return this._settings.get_boolean("three-finger") ? 3 : 4;
}
// Functions Settings
_getUseActiveWindow() {
return this._settings.get_boolean("use-active-window");
}
_getEnableResize() {
return this._settings.get_boolean("fn-resize");
}
_getEnableMove() {
return this._settings.get_boolean("fn-move");
}
_getEnableMaxSnap() {
return this._settings.get_boolean("fn-maximized-snap");
}
_getEnableMoveSnap() {
return this._settings.get_boolean("fn-move-snap");
}
_getEnableFullscreen() {
return this._settings.get_boolean("fn-fullscreen");
}
_getPinchInScale() {
return this._settings.get_int('pinch-in-scale');
}
_getPinchOutScale() {
return this._settings.get_int('pinch-out-scale');
}
_getPinchEnabled() {
return this._settings.get_boolean("pinch-enable");
}
_getTapHoldMove() {
return this._settings.get_boolean("taphold-move");
}
// Is On Overview
_isOnOverview() {
return Main.overview._shown;
}
// Check edge flags
_isEdge(edge) {
return ((this._edgeAction & edge) == edge);
}
// Show Preview
_showPreview(rx, ry, rw, rh) {
if (global.display.get_focus_window() == null) {
return;
}
global.window_manager.emit("show-tile-preview",
global.display.get_focus_window(), new Meta.Rectangle(
{ x: rx, y: ry, width: rw, height: rh }
)
, this._monitorId
);
}
// Find Target Window
_findPointerWindow() {
let target = null;
let [pointerX, pointerY, pointerZ] = global.get_pointer();
let currActor = global.stage.get_actor_at_pos(
Clutter.PickMode.REACTIVE, pointerX, pointerY
);
if (currActor) {
// Find root window for current actor
let currWindow = currActor.get_parent();
let i = 0;
while (currWindow && !currWindow.get_meta_window) {
currWindow = currWindow.get_parent();
if (!currWindow || (++i > 10)) {
currWindow = null;
break;
}
}
// Set meta window as target window to manage
target = currWindow?.get_meta_window();
}
return target;
}
// Get Alt Tabs List
_getWindowTabList() {
let wm = global.workspace_manager;
let workspace = wm.get_active_workspace();
let windows = global.display.get_tab_list(
Meta.TabList.NORMAL_ALL, workspace
);
return windows.map(w => {
return w.is_attached_dialog() ? w.get_transient_for() : w;
}).filter((w, i, a) => !w.skip_taskbar && a.indexOf(w) === i);
}
// Hide Preview
_hidePreview() {
global.window_manager.emit("hide-tile-preview");
}
// Simulate keypress (up -> down)
_sendKeyPress(combination) {
combination.forEach(key => this._virtualKeyboard.notify_keyval(
Clutter.get_current_event_time(), key, Clutter.KeyState.PRESSED)
);
combination.reverse().forEach(key =>
this._virtualKeyboard.notify_keyval(
Clutter.get_current_event_time(), key, Clutter.KeyState.RELEASED
));
}
// Move Mouse Pointer
_movePointer(x, y) {
if (!this._isActiveWin) {
// Move only if not use active window
this._virtualTouchpad.notify_relative_motion(
Meta.CURRENT_TIME, x, y
);
}
}
// window management
_winmanWinApp(win) {
return Shell.WindowTracker.get_default()
.get_window_app(win);
// App.create_icon_texture;
}
// Snap Window
_setSnapWindow(snapRight) {
if (this._targetWindow == null) {
return;
}
// TODO: Using non key shortcut for snap left/right
if (snapRight) {
this._sendKeyPress([Clutter.KEY_Super_L, Clutter.KEY_Right]);
}
else {
this._sendKeyPress([Clutter.KEY_Super_L, Clutter.KEY_Left]);
}
}
// Move to Workspace
_moveWindowWorkspace(moveRight) {
if ((this._targetWindow == null) ||
!Meta.prefs_get_dynamic_workspaces()) {
return;
}
let wsid = this._targetWindow.get_workspace().index();
if (wsid == 0 && !moveRight) {
this._insertWorkspace(0, this._targetWindow);
return;
}
let tw = this._targetWindow.get_workspace()
.get_neighbor(moveRight ? Meta.MotionDirection.RIGHT :
Meta.MotionDirection.LEFT);
this._targetWindow.change_workspace(tw);
this._targetWindow.activate(Meta.CURRENT_TIME);
}
// Insert new workspace
_insertWorkspace(pos, chkwin) {
// Main.wm.insertWorkspace(0);
let wm = global.workspace_manager;
if (!Meta.prefs_get_dynamic_workspaces()) {
return -1;
}
wm.append_new_workspace(false, Meta.CURRENT_TIME);
let windows = global.get_window_actors().map(a => a.meta_window);
windows.forEach(window => {
if (chkwin && chkwin == window)
return -1;
if (window.get_transient_for() != null)
return -1;
if (window.is_override_redirect())
return -1;
if (window.on_all_workspaces)
return -1;
let index = window.get_workspace().index();
if (index < pos)
return -1;
window.change_workspace_by_index(index + 1, true);
});
if (chkwin) {
wm.get_workspace_by_index(pos + 1).activate(
Meta.CURRENT_TIME
);
chkwin.change_workspace_by_index(pos, true);
this._targetWindow.activate(Meta.CURRENT_TIME);
}
return pos;
}
_isDynamicWorkspace() {
return Meta.prefs_get_dynamic_workspaces();
}
// Have Left Workspace
_workspaceHavePrev() {
if (this._targetWindow == null) {
return false;
}
return (this._targetWindow.get_workspace().index() > 0);
}
_resetWinPos() {
if (this._targetWindow == null) {
return;
}
// Reset
this._targetWindow.move_frame(
true,
this._startWinArea.x,
this._startWinArea.y
);
}
// Activate Target Window
_activateWindow() {
if (this._targetWindow == null) {
return;
}
// Activate window if not focused yet
if (!this._targetWindow.has_focus()) {
this._targetWindow.activate(
Meta.CURRENT_TIME
);
}
}
// Swipe window move handler
_swipeUpdateMove() {
this._activateWindow();
// gnome-shell-extension-tiling-assistant support
if (this._targetWindow.isTiled) {
let urct = this._targetWindow.untiledRect;
if (urct) {
let r = urct._rect;
this._startWinArea.x = r.x;
this._startWinArea.y = r.y;
this._startWinArea.width = r.width;
this._startWinArea.height = r.height;
// this._targetWindow.move_resize_frame(
// true,
// r.x, r.y, r.width, r.height
// );
this._sendKeyPress([Clutter.KEY_Super_L, Clutter.KEY_Down]);
}
}
let allowMoveSnap = this._getEnableMoveSnap();
// Move calculation
let mX = this._monitorArea.x;
let mY = this._monitorArea.y;
let mW = this._monitorArea.width;
let mR = mX + mW;
let winX = this._startWinArea.x + this._movePos.x;
let winY = this._startWinArea.y + this._movePos.y;
let winR = winX + this._startWinArea.width;
// Move action
this._targetWindow.move_frame(
true,
winX, winY
);
if (allowMoveSnap && winX < mX) {
this._showPreview(
this._monitorArea.x,
this._monitorArea.y,
this._monitorArea.width / 2,
this._monitorArea.height
);
this._edgeAction = WindowEdgeAction.MOVE
| WindowEdgeAction.MOVE_SNAP_LEFT;
}
else if (allowMoveSnap && winR > mR) {
this._showPreview(
this._monitorArea.x + (this._monitorArea.width / 2),
this._monitorArea.y,
this._monitorArea.width / 2,
this._monitorArea.height
);
this._edgeAction = WindowEdgeAction.MOVE
| WindowEdgeAction.MOVE_SNAP_RIGHT;
}
else if (allowMoveSnap && winY < mY) {
this._showPreview(
this._monitorArea.x,
this._monitorArea.y,
this._monitorArea.width,
this._monitorArea.height
);
this._edgeAction = WindowEdgeAction.MOVE
| WindowEdgeAction.MOVE_SNAP_TOP;
}
else {
this._edgeAction = WindowEdgeAction.MOVE;
this._hidePreview();
}
return Clutter.EVENT_STOP;
}
// Swipe window resize handler
_swipeUpdateResize(dx, dy) {
this._activateWindow();
// Move cursor pointer
this._movePointer(
(this._isEdge(WindowEdgeAction.RESIZE_LEFT) ||
this._isEdge(WindowEdgeAction.RESIZE_RIGHT)) ? dx : 0,
(this._isEdge(WindowEdgeAction.RESIZE_TOP) ||
this._isEdge(WindowEdgeAction.RESIZE_BOTTOM)) ? dy : 0
);
// Resize actions
let tX = this._startWinArea.x;
let tY = this._startWinArea.y;
let tW = this._startWinArea.width;
let tH = this._startWinArea.height;
if (this._isEdge(WindowEdgeAction.RESIZE_BOTTOM)) {
tH += this._movePos.y;
}
else if (this._isEdge(WindowEdgeAction.RESIZE_TOP)) {
tY += this._movePos.y;
tH -= this._movePos.y;
}
if (this._isEdge(WindowEdgeAction.RESIZE_RIGHT)) {
tW += this._movePos.x;
}
else if (this._isEdge(WindowEdgeAction.RESIZE_LEFT)) {
tX += this._movePos.x;
tW -= this._movePos.x;
}
let tR = tX + tW;
let tB = tY + tH;
let mX = this._monitorArea.x;
let mY = this._monitorArea.y;
let mW = this._monitorArea.width;
let mH = this._monitorArea.height;
let mR = mX + mW;
let mB = mY + mH;
// edge
if (tX < mX) {
tX = mX;
tW = tR - tX;
}
if (tY < mY) {
tY = mY;
tH = tB - tY;
}
if (tR > mR) {
tW = mR - tX;
}
if (tB > mB) {
tH = mB - tY;
}
// Resize Window
this._targetWindow.move_resize_frame(
true,
tX, tY, tW, tH
);
return Clutter.EVENT_STOP;
}
// Begin swipe gesture
_swipeBegin(numfingers) {
// Swipe Variables
this._swipeIsWin = (numfingers == this._gestureNumFinger());
// Workspace gesture only not on overview
if (this._isOnOverview() && !this._swipeIsWin) {
return Clutter.EVENT_PROPAGATE;
}
// Init gesture variables
this._gesture.begin = true;
this._gesture.fingers = numfingers;
this._gesture.progress = 0;
// Action Variables
this._gesture.action = 0;
this._gesture.action_cmp = 0;
this._gesture.action_id = 0;
// Velocity Variables
this._gesture.velocity = this._velocityInit();
this._velocityAppend(this._gesture.velocity, 0);
// Get current mouse position
let [pointerX, pointerY, pointerZ] = global.get_pointer();
this._startPos.x = pointerX;
this._startPos.y = pointerY;
this._movePos.x = this._movePos.y = 0;
// Configs
let allowResize = this._getEnableResize();
let allowMove = this._getEnableMove();
this._isActiveWin = false;
this._targetWindow = null;
let isTapHoldAction = false;
// Clear unswipe tap-hold
if ((this._tapHoldTick != 1) &&
(this._tapHoldTick < this._tick())) {
this._tapHold = this._tapHoldTick = 0;
this._tapHoldWin = null;
}
if (this._tapHoldWin) {
this._targetWindow = this._tapHoldWin;
this._tapHoldWin = null;
isTapHoldAction = true;
}
else if (!this._getUseActiveWindow() && !this._getTapHoldMove()) {
this._targetWindow = this._findPointerWindow();
}
if (!this._targetWindow) {
// Get Active Window
this._targetWindow = global.display.get_focus_window();
if (!this._targetWindow) {
return this._swipeIsWin ?
Clutter.EVENT_STOP : Clutter.EVENT_PROPAGATE;
}
allowResize = false;
this._isActiveWin = true;
}
// Set opener window as target if it was dialog
if (this._targetWindow.is_attached_dialog()) {
this._targetWindow = this._targetWindow.get_transient_for();
}
// Check blacklist window
if (this._isWindowBlacklist(this._targetWindow)) {
this._targetWindow = null;
return this._swipeIsWin ?
Clutter.EVENT_STOP : Clutter.EVENT_PROPAGATE;
}
// Get monitor area
this._monitorArea = this._targetWindow.get_work_area_current_monitor();
// Get monitor id
this._monitorId = global.display.get_monitor_index_for_rect(
this._monitorArea
);
// Get start frame rectangle
this._startWinArea = this._targetWindow.get_frame_rect();
// Window area as local value
let wLeft = this._startWinArea.x;
let wTop = this._startWinArea.y;
let wRight = wLeft + this._startWinArea.width;
let wBottom = wTop + this._startWinArea.height;
let wThirdX = wLeft + (this._startWinArea.width / 3);
let wThirdY = wTop + (this._startWinArea.height / 3);
let w34X = wLeft + ((this._startWinArea.width / 3) * 2);
let w34Y = wTop + ((this._startWinArea.height / 3) * 2);
// Detect window edge
let edge = this._edgeSize();
let topEdge = this._topEdgeSize();
// Default edge: need move event for more actions
this._edgeAction = WindowEdgeAction.NONE;
this._edgeGestured = 0;
// Check allow resize
if (this._swipeIsWin && !this._isActiveWin && allowResize &&
this._targetWindow.allows_resize() &&
this._targetWindow.allows_move() &&
!this._targetWindow.isTiled && !isTapHoldAction) {
// Edge cursor position detection
if (this._startPos.y >= wBottom - edge) {
if (this._startPos.y <= wBottom) {
// Cursor on bottom of window
this._edgeAction =
WindowEdgeAction.RESIZE |
WindowEdgeAction.RESIZE_BOTTOM;
// 1/3 from left|right
if (this._startPos.x <= wThirdX) {
this._edgeAction |= WindowEdgeAction.RESIZE_LEFT;
}
else if (this._startPos.x >= w34X) {
this._edgeAction |= WindowEdgeAction.RESIZE_RIGHT;
}
}
}
else {
if (this._startPos.x >= wLeft && this._startPos.x <= wRight) {
if (this._startPos.x <= wLeft + edge) {
// Cursor on left side of window
this._edgeAction =
WindowEdgeAction.RESIZE |
WindowEdgeAction.RESIZE_LEFT;
}
else if (this._startPos.x >= wRight - edge) {
// Cursor on right side of window
this._edgeAction =
WindowEdgeAction.RESIZE |
WindowEdgeAction.RESIZE_RIGHT;
}
if (this._isEdge(WindowEdgeAction.RESIZE)) {
// 1/3 from top|bottom
if (this._startPos.y <= wThirdY) {
this._edgeAction |= WindowEdgeAction.RESIZE_TOP;
}
else if (this._startPos.y >= w34Y) {
this._edgeAction |= WindowEdgeAction.RESIZE_BOTTOM;
}
}
}
}
}
if (this._swipeIsWin && !this._isEdge(WindowEdgeAction.RESIZE)) {
let setmove = false;
if (this._getTapHoldMove()) {
if (this._tapHold == this._gestureNumFinger()) {
// Tap and hold
setmove = true;
}
}
else if (allowMove &&
this._startPos.x <= wRight &&
this._startPos.x >= wLeft &&
this._startPos.y >= wTop &&
this._startPos.y <= wTop + topEdge) {
// Mouse in top side of window
setmove = true;
}
if (setmove) {
if (this._targetWindow.allows_move() &&
!this._targetWindow.get_maximized()) {
this._edgeAction = WindowEdgeAction.MOVE;
}
}
} else if (this._tapHold > 2 &&
this._tapHold != this._gestureNumFinger()) {
this._edgeAction = WindowEdgeAction.GESTURE_DOWN;
this._movePos.y = (this._gestureThreshold() / 4) + 1;
}
return this._swipeIsWin ?
Clutter.EVENT_STOP : Clutter.EVENT_PROPAGATE;
}
_swipeUpdate(dx, dy) {
if (!this._gesture.begin) {
return Clutter.EVENT_PROPAGATE;
}
// Moving coordinate
this._movePos.x += dx;
this._movePos.y += dy;
// Move & Resize Handler
if (this._isEdge(WindowEdgeAction.MOVE)) {
return this._swipeUpdateMove();
}
else if (this._isEdge(WindowEdgeAction.RESIZE)) {
return this._swipeUpdateResize(dx, dy);
}
// Get threshold setting
let threshold = this._gestureThreshold();
let combineTrigger = this._gestureThreshold() * 2;
let trigger = (threshold / 4) + 1;
let target = 1.00 * (trigger + (threshold * 10));
let absX = Math.abs(this._movePos.x);
let absY = Math.abs(this._movePos.y);
// Find gesture directions
if (this._edgeAction == WindowEdgeAction.NONE) {
if (absX >= trigger || absY >= trigger) {
if (absX > absY) {
if (this._movePos.x <= 0 - trigger) {
this._edgeAction = WindowEdgeAction.GESTURE_LEFT;
}
else if (this._movePos.x >= trigger) {
this._edgeAction = WindowEdgeAction.GESTURE_RIGHT;
}
this._movePos.y = 0;
}
else {
if (this._movePos.y <= 0 - trigger) {
this._edgeAction = WindowEdgeAction.GESTURE_UP;
}
else if (this._movePos.y >= trigger) {
this._edgeAction = WindowEdgeAction.GESTURE_DOWN;
if (this._swipeIsWin) {
let allowMove = this._getEnableMove();
let holdMove = this._getTapHoldMove();
if (!allowMove || holdMove) {
if (!this._targetWindow.get_maximized() &&
!this._targetWindow.isTiled) {
this._edgeGestured = 1;
}
else {
this._edgeGestured = 0;
}
}
else if (
!this._edgeGestured &&
!this._targetWindow.is_fullscreen() &&
!this._targetWindow.get_maximized() &&
this._targetWindow.allows_move()) {
this._edgeAction = WindowEdgeAction.MOVE;
return this._swipeUpdateMove();
}
}
}
this._movePos.x = 0;
}
this._edgeGestured = this._edgeGestured ? 2 : 1;
this._gesture.velocity = this._velocityInit();
this._velocityAppend(this._gesture.velocity, 0);
}
}
let prog = 0;
let vert = 0;
let horiz = 0;
if (this._isEdge(WindowEdgeAction.GESTURE_LEFT)) {
if (!this._swipeIsWin && !this._hooked) {
return Clutter.EVENT_PROPAGATE;
}
let xmove = this._movePos.x + trigger;
if (this._isEdge(WindowEdgeAction.GESTURE_UP) ||
this._isEdge(WindowEdgeAction.GESTURE_DOWN)) {
xmove = this._movePos.x + combineTrigger;
}
else if (!this._swipeIsWin) {
return Clutter.EVENT_PROPAGATE;
}
if (xmove < 0) {
prog = Math.abs(xmove) / target;
}
horiz = 1;
}
else if (this._isEdge(WindowEdgeAction.GESTURE_RIGHT)) {
if (!this._swipeIsWin && !this._hooked) {
return Clutter.EVENT_PROPAGATE;
}
let xmove = this._movePos.x - trigger;
if (this._isEdge(WindowEdgeAction.GESTURE_UP) ||
this._isEdge(WindowEdgeAction.GESTURE_DOWN)) {
xmove = this._movePos.x - combineTrigger;
}
else if (!this._swipeIsWin) {
return Clutter.EVENT_PROPAGATE;
}
if (xmove > 0) {
prog = xmove / target;
}
horiz = 2;
}
else if (this._isEdge(WindowEdgeAction.GESTURE_UP)) {
if (!this._swipeIsWin && !this._hooked) {
return Clutter.EVENT_PROPAGATE;
}
if (this._movePos.y < 0) {
prog = Math.abs(this._movePos.y) / target;
}
vert = 1;
}
else if (this._isEdge(WindowEdgeAction.GESTURE_DOWN)) {
if (!this._swipeIsWin && this._isOnOverview()) {
return Clutter.EVENT_PROPAGATE;
}
if (!this._swipeIsWin) {
this._hooked = true;
}
if (this._movePos.y > 0) {
let movey = this._movePos.y - (this._swipeIsWin ?
threshold : (threshold * 6));
if (movey < 0) {
movey = 0;
}
prog = movey / target;
}
vert = 2;
}
if (vert) {
if (absX >= trigger && ((vert == 1 && this._swipeIsWin) ||
(vert == 2 && !this._swipeIsWin))) {
if (vert == 1 || prog == 0) {
// Combination gesture (Down + Left/Right)
if (this._movePos.x <= 0 - combineTrigger) {
this._edgeAction |= WindowEdgeAction.GESTURE_LEFT;
this._gesture.velocity = this._velocityInit();
this._velocityAppend(this._gesture.velocity, 0);
this._movePos.x = 0 - combineTrigger;
return this._swipeUpdate(0, 0);
}
else if (this._movePos.x >= combineTrigger) {
this._edgeAction |= WindowEdgeAction.GESTURE_RIGHT;
this._gesture.velocity = this._velocityInit();
this._velocityAppend(this._gesture.velocity, 0);
this._movePos.x = combineTrigger;
return this._swipeUpdate(0, 0);
}
}
}
}
else if (horiz) {
if (this._isEdge(WindowEdgeAction.GESTURE_UP)) {
vert = 1;
// Reset to single gesture
if (Math.abs(this._movePos.x) < combineTrigger) {
this._edgeAction = WindowEdgeAction.GESTURE_UP;
this._gesture.velocity = this._velocityInit();
this._velocityAppend(this._gesture.velocity, 0);
this._movePos.y = 0 - combineTrigger;
return this._swipeUpdate(0, 0);
}
}
else if (this._isEdge(WindowEdgeAction.GESTURE_DOWN)) {
vert = 2;
// Reset to single gesture
if (Math.abs(this._movePos.x) < combineTrigger) {
this._edgeAction = WindowEdgeAction.GESTURE_DOWN;
this._gesture.velocity = this._velocityInit();
this._velocityAppend(this._gesture.velocity, 0);
this._movePos.y = combineTrigger;
return this._swipeUpdate(0, 0);
}
}
}
// Switch gestures direction
if (
(vert == 1 && (this._movePos.y > 0 - trigger)) ||
(vert == 2 && (this._movePos.y < 0)) ||
(horiz == 1 && (this._movePos.x > 0 - trigger)) ||
(horiz == 2 && (this._movePos.x < 0))
) {
if (!(this._isEdge(WindowEdgeAction.GESTURE_DOWN) && horiz)) {
if (vert) {
this._movePos.x = 0;
}
if (horiz) {
this._movePos.y = 0;
}
this._edgeAction = WindowEdgeAction.NONE;
this._gesture.velocity = this._velocityInit();
this._velocityAppend(this._gesture.velocity, 0);
return this._swipeUpdate(0, 0);
}
}
// Limit progress
let oprog = prog;
if (prog >= 1) {
prog = 1.0;
}
this._gesture.action = 0;
// Set action
if (vert == 0) {
this._gesture.action = horiz; // left=1, right=2
}
else {
if (!this._swipeIsWin) {
if (horiz && vert == 2) {
// down + horiz
this._gesture.action = horiz + 4; // left=5, right=6
}
else {
// vert
this._gesture.action = (vert == 2) ? 4 : 7; // d=4, u=7
}
}
else {
if (horiz && vert == 1) {
// up + horiz
this._gesture.action = horiz + 50; // left=51, right=52
}
else {
// vert
this._gesture.action = (vert == 2) ?
((this._edgeGestured == 1) ? 53 : 3) :
50; // up=50, dn=53, up+dn=3
}
}
}
// Cancel action
if (this._gesture.action_cmp &&
(this._gesture.action != this._gesture.action_cmp)) {
// Send Cancel State
let aid = this._actionIdGet(this._gesture.action_cmp);
if (aid) {
this._runAction(aid, 1, 0);
}
}
// Update progress & velocity
this._gesture.progress = prog;
this._velocityAppend(this._gesture.velocity,
this._gesture.progress);
if (this._gesture.action) {
let aid = this._actionIdGet(this._gesture.action);
if (aid) {
if (aid >= 50) {
this._activateWindow();
}
this._runAction(aid, 0, this._gesture.progress, oprog);
}
this._gesture.action_cmp = this._gesture.action;
}
return Clutter.EVENT_STOP;
}
_swipeEnd() {
// Check move snap
if (this._isEdge(WindowEdgeAction.MOVE)) {
this._hidePreview();
if (this._isEdge(WindowEdgeAction.MOVE_SNAP_TOP)) {
if (this._targetWindow.can_maximize()) {
this._resetWinPos();
this._targetWindow.maximize(Meta.MaximizeFlags.BOTH);
}
}
else if (this._isEdge(WindowEdgeAction.MOVE_SNAP_LEFT)) {
this._resetWinPos();
this._setSnapWindow(0);
}
else if (this._isEdge(WindowEdgeAction.MOVE_SNAP_RIGHT)) {
this._resetWinPos();
this._setSnapWindow(1);
}
this._hooked = false;
this._clearVars();
return Clutter.EVENT_STOP;
}
// Gesture Release
let retval = (!this._swipeIsWin && !this._hooked) ?
Clutter.EVENT_PROPAGATE : Clutter.EVENT_STOP;
this._hooked = false;
if (this._gesture.action) {
let aid = this._actionIdGet(this._gesture.action);
if (aid) {
let issnapaction = (aid == 51 || aid == 52);
if ((this._gesture.progress < 1.0) && !issnapaction) {
// Fling Velocity
let vel = this._velocityCalc(this._gesture.velocity);
if (vel > 0.001) {
let me = this;
this._velocityFling(vel,
this._gesture.progress,
1.0, 30,
function (state, prog) {
me._runAction(aid, state, prog);
}
);
this._clearVars();
return retval;
}
}
this._runAction(aid, 1, this._gesture.progress);
}
}
this._clearVars();
return retval;
}
// Swipe Handler
_swipeEventHandler(actor, event) {
let numfingers = event.get_touchpad_gesture_finger_count();
if (numfingers != 3 && numfingers != 4) {
return Clutter.EVENT_PROPAGATE;
}
// Process gestures state
switch (event.get_gesture_phase()) {
case Clutter.TouchpadGesturePhase.BEGIN:
// Begin event
return this._swipeBegin(numfingers);
case Clutter.TouchpadGesturePhase.UPDATE:
// Update / move event
let [dx, dy] = event.get_gesture_motion_delta();
return this._swipeUpdate(
dx * this._getAcceleration(),
dy * this._getAcceleration()
);
}
return this._swipeEnd();
}
// Get Current Action Id
_pinchGetCurrentActionId() {
if (this._gesture.begin && this._gesture.action != 0) {
if (this._gesture.action != this._gesture.action_cmp) {
this._gesture.action_id = this._actionIdGet(
(this._gesture.fingers == 3) ?
this._gesture.action + 7 :
this._gesture.action + 9
);
this._gesture.action_cmp = this._gesture.action;
}
return this._gesture.action_id;
}
return 0;
}
// Update Pinch
_pinchUpdate(pinch_scale) {
if (this._gesture.begin) {
let pIn = (this._getPinchInScale() / 100.0);
let pOut = (this._getPinchOutScale() / 100.0);
// Get prediction action & current progress position
if (pinch_scale < 1.0) {
if (pinch_scale < pIn) {
pinch_scale = pIn;
}
this._gesture.action = 1;
this._gesture.progress = (1.0 - pinch_scale) / (1.0 - pIn);
}
else if (pinch_scale > 1.0) {
if (pinch_scale > pOut) {
pinch_scale = pOut;
}
this._gesture.action = 2;
this._gesture.progress = (pinch_scale - 1.0) / (pOut - 1.0);
}
else {
this._gesture.action = 0;
this._gesture.progress = 0;
}
if (this._gesture.action_cmp &&
(this._gesture.action != this._gesture.action_cmp)) {
// Send Cancel State
let aid = this._actionIdGet(
(this._gesture.fingers == 3) ?
this._gesture.action_cmp + 7 :
this._gesture.action_cmp + 9
);
if (aid) {
this._runAction(aid, 1, 0);
}
// Reset velocity
this._gesture.velocity = this._velocityInit();
this._velocityAppend(this._gesture.velocity, 0);
}
this._velocityAppend(this._gesture.velocity,
this._gesture.progress);
let action_id = this._pinchGetCurrentActionId();
if (action_id) {
this._runAction(action_id, 0,
this._gesture.progress
);
}
}
return Clutter.EVENT_STOP;
}
// Pinch Begin
_pinchBegin(numfingers) {
// Pinch Variables
this._gesture.begin = true;
this._gesture.fingers = numfingers;
this._gesture.progress = 0;
// Action Variables
this._gesture.action = 0;
this._gesture.action_cmp = 0;
this._gesture.action_id = 0;
// Velocity Variables
this._gesture.velocity = this._velocityInit();
this._velocityAppend(this._gesture.velocity, 0);
return Clutter.EVENT_STOP;
}
// End Pinch
_pinchEnd() {
let action_id = this._pinchGetCurrentActionId();
if (action_id) {
if (this._gesture.progress < 1.0) {
// Fling Velocity
let vel = this._velocityCalc(this._gesture.velocity);
if (vel > 0.001) {
let me = this;
this._velocityFling(vel,
this._gesture.progress,
1.0, 30,
function (state, prog) {
me._runAction(action_id, state, prog);
}
);
this._clearVars();
return Clutter.EVENT_STOP;
}
}
this._runAction(action_id, 1, this._gesture.progress);
}
this._clearVars();
return Clutter.EVENT_STOP;
}
// Pinch Handler
_pinchEventHandler(actor, event) {
if (!this._getPinchEnabled()) {
return Clutter.EVENT_PROPAGATE;
}
let numfingers = event.get_touchpad_gesture_finger_count();
if (numfingers != 3 && numfingers != 4) {
return Clutter.EVENT_PROPAGATE;
}
const pinch_scale = event.get_gesture_pinch_scale();
// Process gestures state
switch (event.get_gesture_phase()) {
case Clutter.TouchpadGesturePhase.BEGIN:
return this._pinchBegin(numfingers);
case Clutter.TouchpadGesturePhase.UPDATE:
return this._pinchUpdate(pinch_scale);
}
return this._pinchEnd();
}
_tapHoldGesture(state, numfingers) {
let isWin = (numfingers == this._gestureNumFinger());
if (!isWin || this._getTapHoldMove()) {
if (state) {
let me = this;
this._holdTo = setTimeout(function () {
me._tapHold = 0;
let activeWin = null;
if (!me._getUseActiveWindow()) {
activeWin = me._findPointerWindow();
}
if (!activeWin) {
activeWin = global.display.get_focus_window();
}
if (activeWin && ((activeWin.allows_move() &&
!activeWin.get_maximized()) || !isWin)) {
activeWin.activate(
Meta.CURRENT_TIME
);
me._tapHold = numfingers;
me._tapHoldWin = activeWin;
me._tapHoldTick = 1;
activeWin.get_compositor_private()
.set_pivot_point(0.5, 0.5);
activeWin.get_compositor_private().ease({
duration: 100,
mode: Clutter.AnimationMode.EASE_OUT_QUAD,
scale_y: 1.05,
scale_x: 1.05,
onStopped: () => {
activeWin?.get_compositor_private().ease({
duration: 100,
mode: Clutter.AnimationMode.EASE_OUT_QUAD,
scale_y: 1,
scale_x: 1,
onStopped: () => {
activeWin?.get_compositor_private()
.set_pivot_point(0, 0);
}
});
}
});
}
}, 100);
}
else {
if (this._holdTo) {
clearTimeout(this._holdTo);
}
this._tapHoldTick = this._tick() + 100;
this._holdTo = 0;
}
}
return Clutter.EVENT_PROPAGATE;
}
_tapHoldHandler(actor, event) {
// Process gestures state
let numfingers = event.get_touchpad_gesture_finger_count();
if (numfingers != 3 && numfingers != 4) {
return Clutter.EVENT_PROPAGATE;
}
if (event.get_gesture_phase() == Clutter.TouchpadGesturePhase.BEGIN) {
return this._tapHoldGesture(1, numfingers);
}
return this._tapHoldGesture(0, numfingers);
}
// Touch Event Handler
_touchpadEvent(actor, event) {
// log("_touchpadEvent = " + event.type());
// Process pinch
if (event.type() == Clutter.EventType.TOUCHPAD_PINCH)
return this._pinchEventHandler(actor, event);
// Process swipe
if (event.type() == Clutter.EventType.TOUCHPAD_SWIPE)
return this._swipeEventHandler(actor, event);
// Process tap hold
if (event.type() == Clutter.EventType.TOUCHPAD_HOLD)
return this._tapHoldHandler(actor, event);
return Clutter.EVENT_PROPAGATE;
}
// Get Action Id
_actionIdGet(type) {
let cfg_name = "";
switch (type) {
case 1: cfg_name = "swipe4-left"; break;
case 2: cfg_name = "swipe4-right"; break;
case 3: cfg_name = "swipe4-updown"; break;
case 4: cfg_name = "swipe3-down"; break;
case 5: cfg_name = "swipe3-left"; break;
case 6: cfg_name = "swipe3-right"; break;
case 7: cfg_name = "swipe3-downup"; break;
case 8: cfg_name = "pinch3-in"; break;
case 9: cfg_name = "pinch3-out"; break;
case 10: cfg_name = "pinch4-in"; break;
case 11: cfg_name = "pinch4-out"; break;
default:
if (type >= 50 && type <= 53) {
// Max & Snap
return type;
}
return 0;
}
return this._settings.get_int(cfg_name);
}
// Run Action
_runAction(id, state, progress, oprog) {
const _LCASE = 32;
if (id == 1) {
//
// MINIMIZE ACTION
//
if (this._isOnOverview()) {
return; // Ignore on overview
}
let activeWin = null;
let ui = this._actionWidgets.minimize;
// Init indicator
if (!ui) {
activeWin = global.display.get_focus_window();
if (this._isWindowBlacklist(activeWin)) {
activeWin = null;
}
if (activeWin && activeWin.can_minimize()) {
ui = activeWin.get_compositor_private();
this._actionWidgets.minimize = ui;
if (ui) {
ui.set_pivot_point(0.5, 1);
}
}
else {
this._actionWidgets.minimize = ui = -1;
}
}
// Execute Progress
if (ui && ui != -1) {
if (!state) {
ui.set_pivot_point(0.5, 1);
ui.opacity = 255 - Math.round(100 * progress);
ui.scale_x = 1.0 - (0.2 * progress);
ui.scale_y = 1.0 - (0.2 * progress);
}
else {
// Action is executed
activeWin = null;
if (progress >= 1.0) {
activeWin = global.display.get_focus_window();
if (activeWin) {
if (!activeWin.can_minimize()) {
activeWin = null;
}
}
}
// Restore
ui.ease({
duration: Math.round(250 * progress),
mode: Clutter.AnimationMode.EASE_OUT_QUAD,
opacity: activeWin ? 0 : 255,
scale_x: activeWin ? 0 : 1,
scale_y: activeWin ? 0 : 1,
onStopped: () => {
ui.set_pivot_point(0, 0);
if (activeWin) {
activeWin.minimize();
ui.opacity = 0;
ui.ease({
duration: 800,
opacity: 0,
onStopped: () => {
ui.opacity = 255;
ui.scale_x = 1;
ui.scale_y = 1;
ui = null;
}
});
}
else {
ui = null;
}
}
});
this._actionWidgets.minimize = null;
}
} else if (state) {
this._actionWidgets.minimize = ui = null;
}
}
else if (id == 2) {
//
// CLOSE WINDOW ACTION
//
if (this._isOnOverview()) {
return; // Ignore on overview
}
let activeWin = null;
let ui = this._actionWidgets.close;
// Init indicator
if (!ui) {
activeWin = global.display.get_focus_window();
if (this._isWindowBlacklist(activeWin)) {
activeWin = null;
}
if (activeWin) {
ui = activeWin.get_compositor_private();
this._actionWidgets.close = ui;
if (ui) {
ui.set_pivot_point(0.5, 0.5);
this._createShader('close', ui, 'closeindicator');
ui.fx?.setValue(0);
}
}
else {
this._actionWidgets.close = ui = -1;
}
}
// Execute Progress
if (ui && ui != -1) {
if (!state) {
ui.set_pivot_point(0.5, 0.5);
ui.opacity = 255 - Math.round(40 * progress);
ui.scale_x = 1.0 - (progress * 0.08);
ui.scale_y = 1.0 - (progress * 0.08);
ui.fx?.setValue(progress * 0.99);
}
else {
activeWin = null;
// Action is executed
if (progress >= 1.0) {
activeWin = global.display.get_focus_window();
}
ui.fx?.release();
ui.set_pivot_point(0, 0);
ui.opacity = 255;
ui.scale_x = 1.0;
ui.scale_y = 1.0;
if (activeWin) {
activeWin.delete(
Meta.CURRENT_TIME
);
activeWin = null;
}
ui = null;
this._actionWidgets.close = null;
}
} else if (state) {
this._actionWidgets.close = ui = null;
}
}
else if (id == 3) {
//
// SHOW DESKTOP
//
if (this._isOnOverview()) { // Ignore on overview
return;
}
let ui = this._actionWidgets.show_desktop;
// Init indicator
if (!ui) {
let monitorArea = global.display.list_all_windows();
if (monitorArea.length > 0) {
ui = [];
for (var i = 0; i < monitorArea.length; i++) {
if (!this._isWindowBlacklist(monitorArea[i])) {
let aui = monitorArea[i].get_compositor_private();
if (aui) {
ui.push(aui);
let mrect = monitorArea[i]
.get_work_area_current_monitor();
let wrect = monitorArea[i].get_frame_rect();
// black magic calc ;)
let wl = (mrect.width / 32);
aui._t_targetx = (mrect.width - wl) - wrect.x;
var nx = (0 - (wrect.width - wl)) - wrect.x;
aui.set_pivot_point(1.0, 0.5);
if (Math.abs(nx) < Math.abs(aui._t_targetx)) {
aui._t_targetx = nx;
aui.set_pivot_point(1, (i % 5) * 0.25);
}
else {
aui.set_pivot_point(0, (i % 5) * 0.25);
}
}
}
}
this._actionWidgets.show_desktop = ui;
}
else {
this._actionWidgets.show_desktop = ui = -1;
}
}
// Execute Progress
if (ui && ui != -1) {
if (!state) {
ui.forEach((aui) => {
aui.opacity = 255 - Math.round(180 * progress);
aui.scale_y =
aui.scale_x = 1.0 - (progress * 0.6);
aui.translation_x = (
(progress * progress) * aui._t_targetx
);
});
}
else {
// Action is executed
if (progress >= 1.0) {
// Show Desktop (Super+D) Clutter.KEY_D
this._sendKeyPress(
[Clutter.KEY_Super_L,
Clutter.KEY_D + _LCASE]
);
}
ui.forEach((aui) => {
aui.ease({
duration: Math.round(250 * progress),
mode: Clutter.AnimationMode.EASE_OUT_QUAD,
opacity: 255,
translation_x: 0,
scale_x: 1,
scale_y: 1,
onStopped: () => {
aui.set_pivot_point(0, 0);
delete aui._t_targetx;
delete aui._t_targety;
}
});
});
this._actionWidgets.show_desktop = ui = null;
}
} else if (state) {
this._actionWidgets.show_desktop = ui = null;
}
}
else if ((id == 4) || (id == 5)) {
//
// NEXT & PREVIOUS WINDOW SWITCHING
//
if (this._isOnOverview()) {
return; // Ignore on overview
}
let prv = (id == 5);
let wid = prv ? "switchwin_prev" : "switchwin_next";
let ui = this._actionWidgets[wid];
// Init indicator
if (!ui) {
ui = -1;
let wins = null;
let listActor = null;
// Cancel cache win timeout
if (this._actionWidgets.resetWinlist) {
clearTimeout(this._actionWidgets.resetWinlist);
this._actionWidgets.resetWinlist = 0;
}
// Get cached window list
if (this._actionWidgets.cacheWinTabList) {
wins = this._actionWidgets.cacheWinTabList?.wins;
listActor = this._actionWidgets.cacheWinTabList?.actor;
}
if (!wins) {
// No last cached
wins = this._getWindowTabList();
if (wins.length > 1) {
// Create UI
let gsize = Main.layoutManager.uiGroup.get_size();
let pad = 8;
let posCfg = this._settings.get_int(
'winswitch-position'
);
let lW = (pad * 2) + (wins.length * 48);
let lH = (pad * 2) + 32;
let lX = (gsize[0] - lW) / 2;
let lY = 64; // Top
let pivY = 0;
if (posCfg == 1) {
// Center
lY = (gsize[1] - lH) / 2;
pivY = 0.5;
}
else if (posCfg == 2) {
// Bottom
lY = gsize[1] - (lH + 64);
pivY = 1;
}
listActor = this._createUi(
"wgs-winswitch", lX, lY, lW, lH
);
listActor.set_pivot_point(0.5, pivY);
listActor.opacity = 0;
listActor.scale_x = 0.5;
listActor.scale_y = 0.5;
listActor._data = [];
for (var i = 0; i < wins.length; i++) {
let win = wins[i];
let app = this._winmanWinApp(win);
let ico = app.create_icon_texture(32);
ico.add_style_class_name("wgs-winswitch-ico");
// remove_style_class_name
listActor.add_child(ico);
ico.set_size(32, 32);
ico.set_position((pad * 2) + (48 * i), pad);
ico.set_pivot_point(0.5, 0.5);
listActor._data.push(
{
app: app,
win: win,
ico: ico
}
)
}
listActor.viewShow({
opacity: 255,
scale_x: 1,
scale_y: 1
}, 200);
// Reorder for next (below 1s) calls
this._actionWidgets.cacheWinTabList = {
sel: 0,
first: wins[0],
wins: wins,
actor: listActor
};
}
else {
this._actionWidgets.cacheWinTabList = null;
}
}
if (wins.length > 1) {
ui = { from: wins[0] };
if (prv) {
ui.into = wins[wins.length - 1];
ui.nsel = -1;
}
else {
ui.into = wins[1];
ui.nsel = 1;
}
ui.lstate = 0;
ui.from_actor = ui.from.get_compositor_private();
ui.into_actor = ui.into.get_compositor_private();
ui.from_actor.set_pivot_point(0.5, 1);
ui.into_actor.set_pivot_point(0.5, 1);
// Set selected target
for (var i = 0; i < listActor._data.length; i++) {
let d = listActor._data[i];
if (d.win == ui.from) {
d.ico.add_style_class_name("selected");
ui.from_ico = d.ico;
}
else if (d.win == ui.into) {
d.ico.remove_style_class_name("selected");
ui.into_ico = d.ico;
}
else {
d.ico.remove_style_class_name("selected");
}
}
}
this._actionWidgets[wid] = ui;
}
if (ui && ui != -1) {
if (!state) {
try {
ui.from_actor.opacity = 255 - Math.round(80 * progress);
ui.from_actor.scale_y =
ui.from_actor.scale_x = 1.0 - (0.05 * progress);
ui.into_actor.scale_y =
ui.into_actor.scale_x = 1.0 + (0.05 * progress);
} catch (e) { }
if (progress > 0.8) {
if (!ui.lstate) {
try {
ui.into.raise();
} catch (e) { }
ui.lstate = 1;
ui.from_ico?.remove_style_class_name("selected");
ui.into_ico?.add_style_class_name("selected");
}
}
else if (ui.lstate) {
try {
ui.from.raise();
} catch (e) { }
ui.lstate = 0;
ui.from_ico?.add_style_class_name("selected");
ui.into_ico?.remove_style_class_name("selected");
}
}
else {
if ((progress > 0.8) || ui.lstate) {
try {
try {
this._actionWidgets
.cacheWinTabList.first.activate(
Meta.CURRENT_TIME
);
} catch (e) { }
try {
ui.from.raise();
} catch (e) { }
ui.into.activate(
Meta.CURRENT_TIME
);
} catch (e) { }
if (prv) {
this._actionWidgets.cacheWinTabList.wins.unshift(
this._actionWidgets.cacheWinTabList.wins.pop()
);
}
else {
this._actionWidgets.cacheWinTabList.wins.push(
this._actionWidgets.cacheWinTabList.wins.shift()
);
}
ui.from_ico?.remove_style_class_name("selected");
ui.into_ico?.add_style_class_name("selected");
}
else {
ui.from_ico?.add_style_class_name("selected");
ui.into_ico?.remove_style_class_name("selected");
}
ui.nclose = 0;
// Ease Restore
try {
ui.from_actor.ease({
duration: Math.round(200 * progress),
mode: Clutter.AnimationMode.EASE_OUT_QUAD,
opacity: 255,
scale_x: 1,
scale_y: 1,
onStopped: () => {
ui.from_actor.set_pivot_point(0, 0);
ui.from_actor = null;
ui.from = null;
if (++ui.nclose == 2) {
ui = null;
}
}
});
} catch (e) { }
try {
ui.into_actor.ease({
duration: Math.round(200 * progress),
mode: Clutter.AnimationMode.EASE_OUT_QUAD,
opacity: 255,
scale_x: 1,
scale_y: 1,
onStopped: () => {
ui.into_actor.set_pivot_point(0, 0);
ui.into_actor = null;
ui.into = null;
if (++ui.nclose == 2) {
ui = null;
}
}
});
} catch (e) { }
this._actionWidgets[wid] = null;
// Clear cache after timeout
let me = this;
this._actionWidgets.resetWinlist = setTimeout(
function () {
me._actionWidgets
.cacheWinTabList.actor.aniRelease();
me._actionWidgets.cacheWinTabList = null;
clearTimeout(me._actionWidgets.resetWinlist);
me._actionWidgets.resetWinlist = 0;
}, 500
);
}
} else if (state) {
this._actionWidgets[wid] = ui = null;
}
}
else if ((id == 6) || (id == 7)) {
//
// SEND WINDOW LEFT/RIGHT WORKSPACE
//
if (this._isOnOverview()) {
return; // Ignore on overview
}
let prv = (id == 6);
let wid = prv ? "movewin_left" : "movewin_right";
let ui = this._actionWidgets[wid];
let activeWin = null;
// Init indicator
if (!ui) {
ui = -1;
let isDynamic = this._isDynamicWorkspace();
activeWin = global.display.get_focus_window();
let inserted = 0;
if (activeWin) {
let wsid = activeWin.get_workspace().index();
let tsid = wsid;
if (prv) {
if (isDynamic) {
if (wsid == 0) {
let wpl = activeWin.get_workspace().list_windows();
inserted = wpl.length;
if (inserted > 1) {
// Check for blacklisted windows
for (var i = 0; i < inserted; i++) {
if (this._isWindowBlacklist(wpl[i])) {
inserted--;
}
}
}
wsid = tsid = 1;
}
}
else {
if (wsid == 0) {
inserted = 1;
}
}
tsid--;
}
else {
if (!isDynamic && (wsid >=
global.workspace_manager.get_n_workspaces() - 1)) {
inserted = 1;
}
tsid++;
}
// Make sure move left from left-most workspace only
// triggered if other window is available in
// current workspace
if (inserted !== 1) {
activeWin.stick();
if (inserted) {
this._insertWorkspace(0);
}
ui = {
confirmSwipe: () => { },
wm: Main.wm._workspaceAnimation,
win: activeWin,
wid: tsid,
sid: wsid,
ins: inserted
};
ui.wm._switchWorkspaceBegin(
ui,
global.display.get_primary_monitor()
);
}
}
this._actionWidgets[wid] = ui;
}
if (ui && ui != -1) {
if (!state) {
ui.wm._switchWorkspaceUpdate(
ui,
ui.sid + ((prv) ? 0 - progress : progress)
)
}
else {
ui.win.unstick();
ui.wm._switchWorkspaceEnd(
ui, 350,
ui.sid + ((prv) ? 0 - progress : progress)
);
if (progress > 0.5) {
ui.win.change_workspace_by_index(
ui.wid, true
);
global.workspace_manager.get_workspace_by_index(ui.wid)
.activate(
Meta.CURRENT_TIME
);
}
else if (ui.ins) {
// list_windows
if (ui.ins > 1) {
ui.win.change_workspace_by_index(
ui.sid, true
);
}
else {
ui.win.change_workspace_by_index(
ui.wid, true
);
}
}
ui.win.activate(Meta.CURRENT_TIME);
this._actionWidgets[wid] = ui = null;
}
} else if (state) {
this._actionWidgets[wid] = ui = null;
}
}
else if (id >= 8 && id <= 9) {
//
// BACK / FORWARD
//
if (this._isOnOverview()) {
return; // Ignore on overview
}
const keyList = [
Clutter.KEY_Back,
Clutter.KEY_Forward
];
let kid = id - 8;
let activeWin = null;
let kidw = kid ? 'btnforward' : 'btnback';
let ui = this._actionWidgets[kidw];
// Init indicator
if (!ui) {
activeWin = global.display.get_focus_window();
if (this._isWindowBlacklist(activeWin)) {
activeWin = null;
}
if (activeWin) {
let uipar = activeWin.get_compositor_private();
if (uipar) {
let wrect = activeWin.get_frame_rect();
let uix = ((uipar.get_width() - wrect.width) / 2);
let uiy = (uipar.get_height() / 2) - 32;
if (kid) {
uix += wrect.width - 128;
}
else {
uix += 64;
}
ui = this._createUi(
'wgs-indicator-backforward',
uix,
uiy,
64, 64,
kid ? 'pan-end-symbolic.symbolic' :
'pan-start-symbolic.symbolic',
uipar
);
if (ui) {
ui.translation_x = kid ? -32 : 32;
ui.opacity = 0;
}
else {
ui = -1;
}
this._actionWidgets[kidw] = ui;
}
else {
this._actionWidgets[kidw] = -1;
}
}
else {
this._actionWidgets[kidw] = -1;
}
}
// Execute Progress
if (ui && ui != -1) {
if (!state) {
if (kid) {
ui.translation_x = 32 - (32 * progress);
}
else {
ui.translation_x = (32 * progress) - 32;
}
ui.opacity = Math.round(255 * progress);
}
else {
// Action is executed
if (progress >= 1.0) {
this._sendKeyPress([keyList[kid]]);
}
ui.release();
this._actionWidgets[kidw] = ui = null;
}
} else if (state) {
this._actionWidgets[kidw] = ui = null;
}
}
else if (id >= 10 && id <= 17) {
//
// MEDIA & BRIGHTNESS
//
const keyList = [
Clutter.KEY_MonBrightnessUp,
Clutter.KEY_MonBrightnessDown,
Clutter.KEY_AudioRaiseVolume,
Clutter.KEY_AudioLowerVolume,
Clutter.KEY_AudioMute,
Clutter.KEY_AudioPlay,
Clutter.KEY_AudioNext,
Clutter.KEY_AudioPrev
];
let wid = 'keys_' + id;
let cid = 'keysn_' + id;
let keyId = keyList[id - 10];
let isRepeat = (id < 14);
let kidw = 'keyaction_' + id;
let ui = this._actionWidgets[kidw];
if (isRepeat) {
if (!state && (progress >= 1)) {
if (!this._keyRepeatInterval) {
this._actionWidgets[cid] = 0;
this._sendKeyPress([keyId]);
this._keyRepeatInterval = setInterval(
function (me, cid, keyId) {
if (me._actionWidgets[cid] >= 5) {
me._sendKeyPress([keyId]);
}
else {
me._actionWidgets[cid]++;
}
},
100,
this, cid, keyId
);
}
}
if (state) {
if (this._keyRepeatInterval) {
clearInterval(this._keyRepeatInterval);
}
this._keyRepeatInterval = 0;
this._actionWidgets[cid] = 0;
}
}
else {
if (!ui) {
const iconlist = [
'audio-volume-muted-symbolic',
'media-playback-start-symbolic',
'media-skip-forward-symbolic',
'media-skip-backward-symbolic'
];
let display = global.get_display();
let mrect = display.get_monitor_geometry(
display.get_primary_monitor()
);
ui = this._createUi(
'wgs-indicator-keys',
mrect.x + (mrect.width / 2) - 64,
mrect.y + (mrect.height / 2) - 64,
128, 128,
iconlist[id - 14],
null
);
ui.opacity = 0;
ui.scale_x = ui.scale_y = 0;
this._actionWidgets[kidw] = ui;
}
// Execute Progress
if (ui && ui != -1) {
if (!state) {
ui.scale_x = ui.scale_y = progress;
ui.opacity = Math.round(255 * progress);
}
else {
// Action is executed
if (progress >= 1.0) {
this._sendKeyPress([keyId]);
}
ui.aniRelease(progress);
this._actionWidgets[kidw] = ui = null;
}
}
else if (state) {
this._actionWidgets[kidw] = ui = null;
}
}
}
//
// Non animated actions
//
else if (id == 18) {
if (this._isOnOverview()) {
// Ignore on overview
return;
}
if (!state || progress < 1.0) {
// Ignore if non end
return;
}
// ALT+TAB
this._sendKeyPress([Clutter.KEY_Alt_L, Clutter.KEY_Tab]);
}
else if (id == 19 || id == 20) {
if (!state || progress < 1.0) {
// Ignore if non end
return;
}
if (id == 19) {
Main.overview.show();
}
else {
Main.overview.showApps();
}
}
else if (id == 21) {
if (!state || progress < 1.0) {
// Ignore if non end
return;
}
// Quick Settings (Super+S)
Main.wm._toggleQuickSettings();
// this._sendKeyPress([Clutter.KEY_Super_L, Clutter.KEY_S + _LCASE]);
}
else if (id == 22) {
if (!state || progress < 1.0) {
// Ignore if non end
return;
}
// Notification (Super+V)
Main.wm._toggleCalendar();
// this._sendKeyPress([Clutter.KEY_Super_L, Clutter.KEY_V + _LCASE]);
}
else if (id == 23) {
if (!state || progress < 1.0) {
// Ignore if non end
return;
}
// Run (Alt+F2)
this._sendKeyPress([Clutter.KEY_Alt_L, Clutter.KEY_F2]);
}
else if (id >= 50 && id <= 53) {
//
// Maximized, Fullscreen, Spap Etc.
//
let activeWin = global.display.get_focus_window();
if (!activeWin || this._isOnOverview()) {
return; // Ignore on overview & no active window
}
if (this._isWindowBlacklist(activeWin)) {
return; // Ignore blacklisted
}
let winCanMax = activeWin.allows_move() && activeWin.can_maximize();
let winIsMaximized = activeWin.get_maximized();
let winMaxed = Meta.MaximizeFlags.BOTH == winIsMaximized;
if (activeWin.isTiled) {
winIsMaximized = Meta.MaximizeFlags.VERTICAL;
}
let winIsFullscreen = activeWin.is_fullscreen();
let allowFullscreen = this._getEnableFullscreen();
let ui = 0; // find action id
if (id < 53) {
// Maximize
if (winMaxed) {
if (winIsFullscreen) {
ui = 5; // un-fullscreen
}
else if (allowFullscreen) {
ui = 4; // fullscreen
}
else {
ui = 6; // if not allow fullscreen (restore)
}
}
else if (winCanMax) {
// 1. Max, 2. Snap Left, 3. Snap Right
ui = id - 49;
}
}
else if (winIsFullscreen) {
ui = 5; // un-fullscreen
}
else if (winIsMaximized) {
ui = 6; // restore
}
let wid = "wmax_state" + ui;
if (ui) {
if (!state) {
if (ui == 4) {
// fullsccreen
activeWin?.get_compositor_private()
.set_pivot_point(0.5, 1);
activeWin.get_compositor_private().scale_y =
1.0 + (progress * 0.025);
}
else if (ui >= 5) {
// un-fullsccreen / restore
activeWin?.get_compositor_private()
.set_pivot_point(0.5, 1);
if (ui == 6) {
activeWin.get_compositor_private().scale_y =
activeWin.get_compositor_private().scale_x =
1.0 - (progress * 0.04);
}
else {
activeWin.get_compositor_private().scale_y =
1.0 - (progress * 0.025);
}
}
else if (ui <= 3) {
if (!this._actionWidgets[wid]) {
if (this._actionWidgets.tilerHider) {
clearTimeout(this._actionWidgets.tilerHider);
this._actionWidgets.tilerHider = null;
}
let moarea = activeWin
.get_work_area_current_monitor();
if (ui == 1) {
// max
this._showPreview(
moarea.x,
moarea.y,
moarea.width,
moarea.height
);
}
else if (ui == 2) {
// snap left
this._showPreview(
moarea.x,
moarea.y,
moarea.width / 2,
moarea.height
);
}
else if (ui == 3) {
// snap right
this._showPreview(
moarea.x
+ (moarea.width / 2),
moarea.y,
moarea.width / 2,
moarea.height
);
}
this._actionWidgets[wid] = 1;
}
}
}
else {
if (ui <= 3) {
// max, snap
if (this._actionWidgets.tilerHider) {
clearTimeout(this._actionWidgets.tilerHider);
this._actionWidgets.tilerHider = null;
}
if (this._actionWidgets[wid] && (progress > 0)) {
if (ui == 1) {
activeWin.maximize(Meta.MaximizeFlags.BOTH);
}
else if (ui == 2) {
this._setSnapWindow(0);
}
else if (ui == 3) {
this._setSnapWindow(1);
}
}
this._actionWidgets.tilerHider = setTimeout(
function (me) {
me._hidePreview();
me._actionWidgets.tilerHider = null;
}, 100, this);
this._actionWidgets[wid] = 0;
}
else {
// resize back
activeWin?.get_compositor_private().ease({
duration: Math.round(200 * progress),
mode: Clutter.AnimationMode.EASE_OUT_QUAD,
scale_y: 1,
scale_x: 1,
onStopped: () => {
activeWin?.get_compositor_private()
.set_pivot_point(0, 0);
}
});
if (progress >= 0.5) {
if (ui == 4) {
// fullscreen
activeWin.make_fullscreen();
}
else if (ui == 5) {
// un-fullscreen
activeWin.unmake_fullscreen();
}
else if (ui == 6) {
// restore
activeWin.unmaximize(
Meta.MaximizeFlags.BOTH
);
if (activeWin.isTiled) {
this._sendKeyPress([
Clutter.KEY_Super_L, Clutter.KEY_Down
]);
}
}
}
}
}
}
}
//
// End Of Actions
//
}
}
// Export Extension
export default class WindowGesturesExtension extends Extension {
// Enable Extension
enable() {
this.manager = new Manager(this);
}
// Disable Extension
disable() {
this.manager.destroy();
this.manager = null;
}
}