From 33dd65545d04fdb66909c3fba9c715d9d636e023 Mon Sep 17 00:00:00 2001 From: Harshad Gavali Date: Sun, 21 Nov 2021 09:57:36 +0530 Subject: [PATCH] Revert "cleanup: Remove residual schema code" This reverts commit 99d3517adc462c632ae7c1e2f38a98f3aa371f73. Revert "cleanup: Move custom clutter type to gnome-shell" This reverts commit 3b8803cb303146e1cabd9a9ee48f02be27a39630. Revert "cleanup: Remove hold gesture to make seperate PR" This reverts commit e8e7b492f0e3575feea899ecd608b4abad3051ac. --- extension/common/prefs.ts | 2 + extension/common/settings.ts | 16 +- extension/common/utils/clutter.ts | 11 + extension/common/utils/gobject.ts | 92 +++---- extension/constants.ts | 5 + extension/extension.ts | 8 +- ...extensions.gestureImprovements.gschema.xml | 13 + extension/src/altTab.ts | 2 +- extension/src/gestures.ts | 230 +++++++++++++++++- extension/src/holdGestures/animatePanel.ts | 112 +++++++++ extension/src/swipeTracker.ts | 54 +++- extension/src/trackers/pinchTracker.ts | 3 +- extension/src/utils/dbus.ts | 35 ++- extension/ui/prefs.ui | 99 ++++++++ gnome-shell/index.d.ts | 32 ++- 15 files changed, 632 insertions(+), 82 deletions(-) create mode 100644 extension/common/utils/clutter.ts create mode 100644 extension/src/holdGestures/animatePanel.ts diff --git a/extension/common/prefs.ts b/extension/common/prefs.ts index 4c52623..17f8a5f 100644 --- a/extension/common/prefs.ts +++ b/extension/common/prefs.ts @@ -100,6 +100,8 @@ export function getPrefsWidget(settings: Gio.Settin showEnableMinimizeButton('allow-minimize-window', 'allow-minimize-window_box-row', settings, builder); + bind_boolean_value('enable-move-window-to-workspace', settings, builder, { sensitiveRowKeys: ['animate-panel_box-row'] }); + bind_combo_box('animate-panel', settings, builder); bind_combo_box('pinch-3-finger-gesture', settings, builder); bind_combo_box('pinch-4-finger-gesture', settings, builder); diff --git a/extension/common/settings.ts b/extension/common/settings.ts index b893f89..7ab800e 100644 --- a/extension/common/settings.ts +++ b/extension/common/settings.ts @@ -1,5 +1,13 @@ import Gio from '@gi-types/gio2'; +// define enum +export enum AnimatePanel { + NONE = 0, + SWITCH_WORKSPACE = 1, + MOVE_WINDOW = 2, + SWITCH_WORKSPACE_AND_MOVE_WINDOW = 3, +} + // define enum export enum PinchGestureType { NONE = 0, @@ -12,7 +20,8 @@ export type BooleanSettingsKeys = 'allow-minimize-window' | 'follow-natural-scroll' | 'enable-alttab-gesture' | - 'enable-window-manipulation-gesture' + 'enable-window-manipulation-gesture' | + 'enable-move-window-to-workspace' ; export type IntegerSettingsKeys = @@ -24,6 +33,7 @@ export type DoubleSettingsKeys = ; export type EnumSettingsKeys = + 'animate-panel' | 'pinch-3-finger-gesture' | 'pinch-4-finger-gesture' ; @@ -41,7 +51,8 @@ export type AllUIObjectKeys = 'touchpad-speed_scale_display-value' | 'touchpad-pinch-speed_display-value' | 'allow-minimize-window_box-row' | - 'alttab-delay_box-row' + 'alttab-delay_box-row' | + 'animate-panel_box-row' ; type Enum_Functions = { @@ -50,6 +61,7 @@ type Enum_Functions = { } type SettingsEnumFunctions = + Enum_Functions<'animate-panel', AnimatePanel> & Enum_Functions<'pinch-3-finger-gesture' | 'pinch-4-finger-gesture', PinchGestureType> ; diff --git a/extension/common/utils/clutter.ts b/extension/common/utils/clutter.ts new file mode 100644 index 0000000..99a77fe --- /dev/null +++ b/extension/common/utils/clutter.ts @@ -0,0 +1,11 @@ +import { EventType, Event } from '@gi-types/clutter8'; + +export const ClutterEventType = { TOUCHPAD_HOLD: 1234, ...EventType }; + +export type CustomEventType = Pick< + Event, + 'type' | 'get_gesture_phase' | + 'get_touchpad_gesture_finger_count' | 'get_time' | + 'get_coords' | 'get_gesture_motion_delta_unaccelerated' | + 'get_gesture_pinch_scale' | 'get_gesture_pinch_angle_delta' +>; \ No newline at end of file diff --git a/extension/common/utils/gobject.ts b/extension/common/utils/gobject.ts index e288a4a..f7343fc 100644 --- a/extension/common/utils/gobject.ts +++ b/extension/common/utils/gobject.ts @@ -15,7 +15,7 @@ const OGRegisterClass = GObject.registerClass; type ConstructorType = new (...args: any[]) => any; type IFaces }[]> = { - [key in keyof Interfaces]: Interfaces[key] extends { $gtype: GObject.GType } ? I : never; + [key in keyof Interfaces]: Interfaces[key] extends { $gtype: GObject.GType } ? I : never; }; type _TupleOf = R['length'] extends N ? R : _TupleOf; @@ -23,63 +23,63 @@ type _TupleOf = R['length'] extends N ? R : type Tuple = _TupleOf; type GObjectSignalDefinition = { - param_types?: Partial>; - [key: string]: any; + param_types?: Partial>; + [key: string]: any; }; /** Get typescript type for {@link GObjectSignalDefinition.param_types} */ type CallBackTypeTuple = T extends any[] ? { [P in keyof T]: T[P] extends GObject.GType ? R : never } : []; export type RegisteredPrototype< - P extends {}, - Props extends { [key: string]: GObject.ParamSpec }, - Interfaces extends any[], - Sigs extends { [key: string]: GObjectSignalDefinition } - > = { - /// This is one of modification done by this file - connect( - key: K, - callback: ( - _source: RegisteredPrototype, - ...args: CallBackTypeTuple - ) => void, - ): number, - } & GObject.RegisteredPrototype; + P extends {}, + Props extends { [key: string]: GObject.ParamSpec }, + Interfaces extends any[], + Sigs extends { [key: string]: GObjectSignalDefinition } + > = { + /// This is one of modification done by this file + connect( + key: K, + callback: ( + _source: RegisteredPrototype, + ...args: CallBackTypeTuple + ) => void, + ): number, + } & GObject.RegisteredPrototype; export type RegisteredClass< - T extends ConstructorType, - Props extends { [key: string]: GObject.ParamSpec }, - Interfaces extends { $gtype: GObject.GType }[], - Sigs extends { [key: string]: GObjectSignalDefinition } - > = T extends { prototype: infer P } - ? { - $gtype: GObject.GType, Sigs>>; - prototype: RegisteredPrototype, Sigs>; - /// use constructor parameter instead of '_init' parameters - new(...args: ConstructorParameters): RegisteredPrototype, Sigs>; - } - : never; + T extends ConstructorType, + Props extends { [key: string]: GObject.ParamSpec }, + Interfaces extends { $gtype: GObject.GType }[], + Sigs extends { [key: string]: GObjectSignalDefinition } + > = T extends { prototype: infer P } + ? { + $gtype: GObject.GType, Sigs>>; + prototype: RegisteredPrototype, Sigs>; + /// use constructor parameter instead of '_init' parameters + new(...args: ConstructorParameters): RegisteredPrototype, Sigs>; + } + : never; export function registerClass(klass: T): RegisteredClass; export function registerClass< - T extends ConstructorType, - Props extends { [key: string]: GObject.ParamSpec }, - Interfaces extends { $gtype: GObject.GType }[], - Sigs extends { [key: string]: GObjectSignalDefinition } + T extends ConstructorType, + Props extends { [key: string]: GObject.ParamSpec }, + Interfaces extends { $gtype: GObject.GType }[], + Sigs extends { [key: string]: GObjectSignalDefinition } >( - options: { - GTypeName?: string; - GTypeFlags?: GObject.TypeFlags; - /// Make properties mandatory - Properties: Props; - Signals?: Sigs; - Implements?: Interfaces; - CssName?: string; - Template?: string; - Children?: string[]; - InternalChildren?: string[]; - }, - klass: T + options: { + GTypeName?: string; + GTypeFlags?: GObject.TypeFlags; + /// Make properties mandatory + Properties: Props; + Signals?: Sigs; + Implements?: Interfaces; + CssName?: string; + Template?: string; + Children?: string[]; + InternalChildren?: string[]; + }, + klass: T ): RegisteredClass; export function registerClass(...args: any[]): any { diff --git a/extension/constants.ts b/extension/constants.ts index f93a9d5..bae3e03 100644 --- a/extension/constants.ts +++ b/extension/constants.ts @@ -1,3 +1,6 @@ + +import { AnimatePanel } from './common/settings'; + // FIXME: ideally these values matches physical touchpad size. We can get the // correct values for gnome-shell specifically, since mutter uses libinput // directly, but GTK apps cannot get it, so use an arbitrary value so that @@ -33,6 +36,8 @@ export const ExtSettings = { DEFAULT_OVERVIEW_GESTURE: false, ALLOW_MINIMIZE_WINDOW: false, FOLLOW_NATURAL_SCROLL: true, + ENABLE_MOVE_WINDOW_TO_WORKSPACE: true, + ANIMATE_PANEL: AnimatePanel.MOVE_WINDOW, }; export const RELOAD_DELAY = 150; // reload extension delay in ms \ No newline at end of file diff --git a/extension/extension.ts b/extension/extension.ts index 88e7a13..547ccc8 100644 --- a/extension/extension.ts +++ b/extension/extension.ts @@ -7,7 +7,7 @@ import { OverviewRoundTripGestureExtension } from './src/overviewRoundTrip'; import { SnapWindowExtension } from './src/snapWindow'; import * as DBusUtils from './src/utils/dbus'; import { imports } from 'gnome-shell'; -import { AllSettingsKeys, GioSettings, PinchGestureType } from './common/settings'; +import { AllSettingsKeys, AnimatePanel, GioSettings, PinchGestureType } from './common/settings'; import { AltTabConstants, ExtSettings, TouchpadConstants } from './constants'; import { ShowDesktopExtension } from './src/pinchGestures/showDesktop'; @@ -108,6 +108,12 @@ class Extension { ExtSettings.DEFAULT_OVERVIEW_GESTURE = this.settings.get_boolean('default-overview'); ExtSettings.ALLOW_MINIMIZE_WINDOW = this.settings.get_boolean('allow-minimize-window'); ExtSettings.FOLLOW_NATURAL_SCROLL = this.settings.get_boolean('follow-natural-scroll'); + ExtSettings.ENABLE_MOVE_WINDOW_TO_WORKSPACE = this.settings.get_boolean('enable-move-window-to-workspace'); + + if (ExtSettings.ENABLE_MOVE_WINDOW_TO_WORKSPACE) + ExtSettings.ANIMATE_PANEL = this.settings.get_enum('animate-panel'); + else + ExtSettings.ANIMATE_PANEL = AnimatePanel.NONE; TouchpadConstants.SWIPE_MULTIPLIER = Constants.TouchpadConstants.DEFAULT_SWIPE_MULTIPLIER * this.settings.get_double('touchpad-speed-scale'); TouchpadConstants.PINCH_MULTIPLIER = Constants.TouchpadConstants.DEFAULT_PINCH_MULTIPLIER * this.settings.get_double('touchpad-pinch-speed'); diff --git a/extension/schemas/org.gnome.shell.extensions.gestureImprovements.gschema.xml b/extension/schemas/org.gnome.shell.extensions.gestureImprovements.gschema.xml index 623f79e..358afde 100644 --- a/extension/schemas/org.gnome.shell.extensions.gestureImprovements.gschema.xml +++ b/extension/schemas/org.gnome.shell.extensions.gestureImprovements.gschema.xml @@ -1,5 +1,10 @@ + + + + + @@ -39,6 +44,14 @@ true Enable Window manipulation gesture + + true + Enable move window to workspace gesture + + + 'MOVE_WINDOW' + When to animate panel + 'SHOW_DESKTOP' Gesture for 3 finger pinch diff --git a/extension/src/altTab.ts b/extension/src/altTab.ts index b41858c..6ce8369 100644 --- a/extension/src/altTab.ts +++ b/extension/src/altTab.ts @@ -106,7 +106,7 @@ export class AltTabGestureExtension implements ISubExtension { if (this._extState === AltTabExtState.DEFAULT) { this._switcher = new WindowSwitcherPopup(); // remove timeout entirely - this._switcher._resetNoModsTimeout = function () { + this._switcher._resetNoModsTimeout = function() { if (this._noModsTimeoutId) { GLib.source_remove(this._noModsTimeoutId); this._noModsTimeoutId = 0; diff --git a/extension/src/gestures.ts b/extension/src/gestures.ts index 481902d..82f2fc3 100644 --- a/extension/src/gestures.ts +++ b/extension/src/gestures.ts @@ -1,12 +1,18 @@ import GObject from '@gi-types/gobject2'; import Shell from '@gi-types/shell0'; +import Meta from '@gi-types/meta8'; import Clutter from '@gi-types/clutter8'; -import { imports, global, __shell_private_types, CustomEventType } from 'gnome-shell'; +import St from '@gi-types/st1'; +import { imports, global, __shell_private_types } from 'gnome-shell'; const Main = imports.ui.main; import { createSwipeTracker, TouchpadSwipeGesture } from './swipeTracker'; import { OverviewControlsState, ExtSettings } from '../constants'; +import { CustomEventType } from '../common/utils/clutter'; +import { easeActor } from './utils/environment'; +import { DummyCyclicPanel } from './holdGestures/animatePanel'; +import { AnimatePanel } from '../common/settings'; declare interface ShallowSwipeTrackerT { orientation: Clutter.Orientation, @@ -61,9 +67,27 @@ abstract class SwipeTrackerEndPointsModifer { } } +// declare enum +enum ExtensionState { + DEFAULT = 0, + SWITCH_WORKSPACE = AnimatePanel.SWITCH_WORKSPACE, + MOVE_WINDOW = AnimatePanel.MOVE_WINDOW, + /** flag: whether to animate panel */ + ANIMATE_PANEL = 4, + /** flag: whether update/end signal was received from tracker */ + UPDATE_RECEIVED = 8, +} + class WorkspaceAnimationModifier extends SwipeTrackerEndPointsModifer { private _workspaceAnimation: imports.ui.workspaceAnimation.WorkspaceAnimationController; protected _swipeTracker: SwipeTrackerT; + private _window?: Meta.Window; + private _highlight?: St.Widget; + + private GESTURE_DELAY = 75; + private _workspaceChangedId = 0; + private _extensionState = ExtensionState.DEFAULT; + private _dummyCyclicPanel?: typeof DummyCyclicPanel.prototype; constructor(wm: typeof imports.ui.main.wm) { super(); @@ -77,6 +101,9 @@ class WorkspaceAnimationModifier extends SwipeTrackerEndPointsModifer { 1, { allowTouch: false }, ); + + if (ExtSettings.ANIMATE_PANEL !== AnimatePanel.NONE) + this._dummyCyclicPanel = new DummyCyclicPanel(); } apply(): void { @@ -87,14 +114,66 @@ class WorkspaceAnimationModifier extends SwipeTrackerEndPointsModifer { super.apply(); } + private _getWindowToMove(monitor: number) { + const types = [Meta.WindowType.MODAL_DIALOG, Meta.WindowType.NORMAL, Meta.WindowType.DIALOG]; + + const window = global.display.get_focus_window() as Meta.Window | null; + if (ExtSettings.ENABLE_MOVE_WINDOW_TO_WORKSPACE && + this._swipeTracker._touchpadGesture?.hadHoldGesture && + window && + !window.is_fullscreen() && + types.includes(window.get_window_type()) && + // ignore window is it's skipbar and type is normal + (!window.skip_taskbar || window.get_window_type() !== Meta.WindowType.NORMAL) && + window.get_monitor() === monitor && + !window.is_always_on_all_workspaces() && + (!Meta.prefs_get_workspaces_only_on_primary() || monitor === Main.layoutManager.primaryMonitor.index) + ) + return window; + return undefined; + } + protected _gestureBegin(tracker: SwipeTrackerT, monitor: number): void { - super._modifySnapPoints(tracker, (shallowTracker) => { - this._workspaceAnimation._switchWorkspaceBegin(shallowTracker, monitor); - tracker.orientation = shallowTracker.orientation; - }); + this.reset(); + this._highlight?.destroy(); + this._extensionState = ExtensionState.DEFAULT; + + this._window = this._getWindowToMove(monitor); + this._workspaceAnimation.movingWindow = this._window; + if (this._window) { + this._extensionState = ExtensionState.MOVE_WINDOW; + this._highlight = this._getWindowHighlight(); + this._animateHighLight(() => { + if (this._swipeTracker._touchpadGesture?.followNaturalScroll !== undefined) + this._swipeTracker._touchpadGesture.followNaturalScroll = false; + + super._modifySnapPoints(tracker, (shallowTracker) => { + this._workspaceAnimation._switchWorkspaceBegin(shallowTracker, monitor as never); + tracker.orientation = shallowTracker.orientation; + }); + }); + } + else { + this._extensionState = ExtensionState.SWITCH_WORKSPACE; + if (this._swipeTracker._touchpadGesture?.followNaturalScroll !== undefined) + this._swipeTracker._touchpadGesture.followNaturalScroll = ExtSettings.FOLLOW_NATURAL_SCROLL; + + super._modifySnapPoints(tracker, (shallowTracker) => { + this._workspaceAnimation._switchWorkspaceBegin(shallowTracker, monitor as never); + tracker.orientation = shallowTracker.orientation; + }); + } + + if (ExtSettings.ANIMATE_PANEL & this._extensionState) { + this._extensionState |= ExtensionState.ANIMATE_PANEL; + this._dummyCyclicPanel?.beginGesture(); + } } protected _gestureUpdate(tracker: SwipeTrackerT, progress: number): void { + if (this._extensionState === ExtensionState.DEFAULT) + return; + this._extensionState |= ExtensionState.UPDATE_RECEIVED; if (progress < this._firstVal) { progress = this._firstVal - (this._firstVal - progress) * 0.05; } @@ -102,15 +181,150 @@ class WorkspaceAnimationModifier extends SwipeTrackerEndPointsModifer { progress = this._lastVal + (progress - this._lastVal) * 0.05; } this._workspaceAnimation._switchWorkspaceUpdate(tracker, progress); + + if (this._extensionState & ExtensionState.ANIMATE_PANEL) + this._dummyCyclicPanel?.updateGesture(progress); } - protected _gestureEnd(tracker: SwipeTrackerT, duration: number, progress: number): void { - progress = Math.clamp(progress, this._firstVal, this._lastVal); - this._workspaceAnimation._switchWorkspaceEnd(tracker, duration, progress); + protected _gestureEnd(tracker: SwipeTrackerT, duration: number, endProgress: number): void { + if (this._extensionState === ExtensionState.DEFAULT) + return; + this._extensionState |= ExtensionState.UPDATE_RECEIVED; + endProgress = Math.clamp(endProgress, this._firstVal, this._lastVal); + + this._workspaceAnimation._switchWorkspaceEnd(tracker, duration, endProgress); + + if (this._highlight) { + easeActor(this._highlight, { + opacity: 0, + mode: Clutter.AnimationMode.EASE_OUT_QUAD, + duration: duration, + onStopped: () => { + this._highlight?.destroy(); + this._highlight = undefined; + }, + }); + } + + const workspace = global.workspaceManager.get_workspace_by_index(endProgress) as Meta.Workspace; + if (!workspace.active && this._window) { + this._workspaceChangedId = global.workspaceManager.connect('active-workspace-changed', () => { + if (workspace.active && this._window) { + this._window.change_workspace(workspace); + workspace.activate_with_focus(this._window, global.get_current_time()); + this._window = undefined; + } + + global.workspaceManager.disconnect(this._workspaceChangedId); + this._workspaceChangedId = 0; + }); + } + + if (this._extensionState & ExtensionState.ANIMATE_PANEL) + this._dummyCyclicPanel?.endGesture(endProgress, duration); + + this._extensionState = ExtensionState.DEFAULT; + } + + private _getWindowHighlight() { + if (this._window === undefined) + return undefined; + + const rect = this._window.get_frame_rect(); + + const highlight = new St.Widget({ + style_class: 'cycler-highlight', + x: rect.x, + y: rect.y, + width: rect.width, + height: rect.height, + style: 'border-radius: 6px;', + opacity: 0, + visible: true, + }); + Main.layoutManager.uiGroup.add_child(highlight); + return highlight; + } + + private _easeActor(actor: T, props: { [P in KeysOfType]?: number }): void { + easeActor(actor, { + ...props, + duration: this.GESTURE_DELAY, + mode: Clutter.AnimationMode.EASE_OUT_QUAD, + autoReverse: true, + repeatCount: 1, + }); + } + + private _animateHighLight(callback: () => void) { + if (this._highlight === undefined || this._window === undefined) { + callback(); + return; + } + + const windowActor = this._window.get_compositor_private() as Meta.WindowActor; + + [windowActor, this._highlight].forEach(actor => { + actor.set_pivot_point(0.5, 0.5); + this._easeActor(actor, { scale_x: 0.95 }); + this._easeActor(actor, { scale_y: 0.95 }); + }); + + easeActor(this._highlight, { + opacity: 200, + mode: Clutter.AnimationMode.EASE_OUT_QUAD, + duration: 2 * this.GESTURE_DELAY, + onStopped: () => { + callback(); + this._animateHighLightWaitForGestureUpdate(windowActor); + }, + }); + } + + private _animateHighLightWaitForGestureUpdate(actor: Meta.WindowActor) { + if (!this._highlight) { + actor.set_pivot_point(0, 0); + return; + } + easeActor(this._highlight, { + opacity: 255, + mode: Clutter.AnimationMode.EASE_OUT_QUAD, + duration: 2 * this.GESTURE_DELAY, + onStopped: () => { + log('animate highlight complete'); + actor.set_pivot_point(0, 0); + this._highlight?.set_pivot_point(0, 0); + // no update received on highlight showing + if ((this._extensionState & ExtensionState.UPDATE_RECEIVED) === 0) + this.reset(); + }, + }); + } + + private reset() { + this._highlight?.destroy(); + this._highlight = undefined; + + const active_workspace = global.workspace_manager.get_active_workspace_index(); + + if ((this._extensionState & ExtensionState.SWITCH_WORKSPACE) || (this._extensionState & ExtensionState.MOVE_WINDOW)) + this._workspaceAnimation._switchWorkspaceEnd(this._swipeTracker, 0, active_workspace); + + if (this._extensionState & ExtensionState.ANIMATE_PANEL) + this._dummyCyclicPanel?.endGesture(global.workspace_manager.get_active_workspace_index(), 0); + + this._extensionState = ExtensionState.DEFAULT; + this._window = undefined; + this._workspaceAnimation.movingWindow = undefined; } destroy(): void { + this._dummyCyclicPanel?.destroy(); this._swipeTracker.destroy(); + + if (this._workspaceChangedId) + global.workspaceManager.disconnect(this._workspaceChangedId); + const swipeTracker = this._workspaceAnimation._swipeTracker; if (swipeTracker._touchpadGesture) { swipeTracker._touchpadGesture._stageCaptureEvent = global.stage.connect( diff --git a/extension/src/holdGestures/animatePanel.ts b/extension/src/holdGestures/animatePanel.ts new file mode 100644 index 0000000..ab127e8 --- /dev/null +++ b/extension/src/holdGestures/animatePanel.ts @@ -0,0 +1,112 @@ +import { registerClass } from '../../common/utils/gobject'; +import Clutter from '@gi-types/clutter8'; +import St from '@gi-types/st1'; + +import { global, imports } from 'gnome-shell'; +import { easeActor } from '../utils/environment'; + +const Main = imports.ui.main; +const { lerp } = imports.misc.util; + +/** + * GObject Class to animate top panel in circular animation + * Without displaying it on any other monitors + */ +export const DummyCyclicPanel = registerClass( + class extends Clutter.Actor { + panelBox: St.BoxLayout>; + private PADDING_WIDTH; + private _container: Clutter.Actor; + + constructor() { + super({ visible: false }); + + this.PADDING_WIDTH = 100 * Main.layoutManager.primaryMonitor.geometry_scale; + + this.panelBox = Main.layoutManager.panelBox; + + this._container = new Clutter.Actor({ layoutManager: new Clutter.BoxLayout({ orientation: Clutter.Orientation.HORIZONTAL, spacing: this.PADDING_WIDTH }) }); + this.add_child(this._container); + + this._container.add_child(new Clutter.Clone({ source: this.panelBox })); + this._container.add_child(new Clutter.Clone({ source: this.panelBox })); + + // this.add_constraint(new MonitorConstraint({ primary: true })); + this.set_clip_to_allocation(true); + Main.layoutManager.uiGroup.add_child(this); + } + + vfunc_get_preferred_height(for_width: number) { + return this.panelBox.get_preferred_height(for_width); + } + + vfunc_get_preferred_width(for_height: number) { + return this.panelBox.get_preferred_width(for_height); + } + + beginGesture() { + // hide main panel + Main.layoutManager.panelBox.opacity = 0; + + Main.layoutManager.panelBox.set_style_class_name('no-panel-corner'); + + let x, y; + + // dash-to-panel + if (this.panelBox.get_parent() !== Main.layoutManager.uiGroup) + [x, y] = this.panelBox.get_parent().get_position(); + else + [x, y] = Main.layoutManager.panelBox.get_position(); + + if (x === null || y === null) { + const { x, y } = Main.layoutManager.primaryMonitor; + this.set_position(x, y); + } + else + this.set_position(x, y); + + this.visible = true; + Main.layoutManager.uiGroup.set_child_above_sibling(this, null); + } + + updateGesture(progress: number) { + // log('setting position to: ' + this.get_position()); + this._container.translation_x = this._getTranslationFor(progress); + } + + endGesture(endProgress: number, duration: number) { + // gesture returns accelerated end value, hence need to do this + const current_workspace = global.workspace_manager.get_active_workspace_index(); + const translation_x = ( + endProgress > current_workspace || + (endProgress === current_workspace && this._container.translation_x <= this.min_cyclic_translation / 2) + ) ? this.min_cyclic_translation : 0; + + easeActor(this._container, { + translation_x, + duration, + mode: Clutter.AnimationMode.EASE_OUT_CUBIC, + onStopped: () => { + this.visible = false; + + // add corners + Main.layoutManager.panelBox.remove_style_class_name('no-panel-corner'); + Main.layoutManager.panelBox.opacity = 255; + }, + }); + } + + private _getTranslationFor(progress: number) { + const begin = Math.floor(progress); + const end = Math.ceil(progress); + progress = begin === end ? 0 : (progress - begin) / (end - begin); + + return lerp(0, this.min_cyclic_translation, progress); + } + + /** returns maximium negative value because translation is always negative */ + get min_cyclic_translation(): number { + return -(this.width + this.PADDING_WIDTH); + } + }, +); \ No newline at end of file diff --git a/extension/src/swipeTracker.ts b/extension/src/swipeTracker.ts index 09955cf..724e636 100644 --- a/extension/src/swipeTracker.ts +++ b/extension/src/swipeTracker.ts @@ -2,7 +2,7 @@ import Clutter from '@gi-types/clutter8'; import GObject from '@gi-types/gobject2'; import Shell from '@gi-types/shell0'; import Meta from '@gi-types/meta8'; -import { imports, global, CustomEventType } from 'gnome-shell'; +import { imports, global } from 'gnome-shell'; const Main = imports.ui.main; const { SwipeTracker } = imports.ui.swipeTracker; @@ -10,6 +10,7 @@ const { SwipeTracker } = imports.ui.swipeTracker; import * as DBusUtils from './utils/dbus'; import { TouchpadConstants } from '../constants'; import { registerClass } from '../common/utils/gobject'; +import { ClutterEventType, CustomEventType } from '../common/utils/clutter'; // define enum enum TouchpadState { @@ -59,6 +60,14 @@ export const TouchpadSwipeGesture = registerClass({ DRAG_THRESHOLD_DISTANCE = TouchpadConstants.DRAG_THRESHOLD_DISTANCE; enabled = true; + private DELAY_BETWEEN_HOLD = 150; // ms + private HOLD_TIME = 100; // ms + private _lastHoldBeginTime = - this.DELAY_BETWEEN_HOLD; + private _lastHoldCancelledTime = 0; + private _beginTime = this.HOLD_TIME; + // private _hadHoldGesture = false; + private _time = 0; + constructor( nfingers: number[], allowedModes: Shell.ActionMode, @@ -83,7 +92,27 @@ export const TouchpadSwipeGesture = registerClass({ this.SWIPE_MULTIPLIER = TouchpadConstants.SWIPE_MULTIPLIER * (typeof (gestureSpeed) !== 'number' ? 1.0 : gestureSpeed); } + _handleHold(event: CustomEventType): void { + switch (event.get_gesture_phase()) { + case Clutter.TouchpadGesturePhase.BEGIN: + this._lastHoldBeginTime = event.get_time(); + break; + case Clutter.TouchpadGesturePhase.CANCEL: + this._lastHoldCancelledTime = event.get_time(); + break; + default: + this._lastHoldBeginTime = - this.DELAY_BETWEEN_HOLD; + this._lastHoldCancelledTime = 0; + } + + } + _handleEvent(_actor: undefined | Clutter.Actor, event: CustomEventType): boolean { + if (event.type() === ClutterEventType.TOUCHPAD_HOLD) { + this._handleHold(event); + return Clutter.EVENT_PROPAGATE; + } + if (event.type() !== Clutter.EventType.TOUCHPAD_SWIPE) return Clutter.EVENT_PROPAGATE; @@ -91,6 +120,8 @@ export const TouchpadSwipeGesture = registerClass({ if (gesturePhase === Clutter.TouchpadGesturePhase.BEGIN) { this._state = TouchpadState.NONE; this._toggledDirection = false; + + this._beginTime = event.get_time(); } if (this._state === TouchpadState.IGNORED) @@ -127,6 +158,7 @@ export const TouchpadSwipeGesture = registerClass({ const [x, y] = event.get_coords(); const [dx, dy] = event.get_gesture_motion_delta_unaccelerated() as [number, number]; + this._time = time; if (this._state === TouchpadState.NONE) { if (dx === 0 && dy === 0) return Clutter.EVENT_PROPAGATE; @@ -182,6 +214,9 @@ export const TouchpadSwipeGesture = registerClass({ this.emit('end', time, distance); this._state = TouchpadState.NONE; this._toggledDirection = false; + this._lastHoldCancelledTime = 0; + this._lastHoldBeginTime = - this.DELAY_BETWEEN_HOLD; + this._beginTime = this.HOLD_TIME; break; } @@ -204,6 +239,23 @@ export const TouchpadSwipeGesture = registerClass({ this._stageCaptureEvent = 0; } } + + get hadHoldGesture(): boolean { + return (this._beginTime - this._lastHoldCancelledTime) < this.HOLD_TIME && + (this._lastHoldCancelledTime - this._lastHoldBeginTime) > this.DELAY_BETWEEN_HOLD; + } + + get time(): number { + return this._time; + } + + get followNaturalScroll(): boolean { + return this._followNaturalScroll; + } + + set followNaturalScroll(follow: boolean) { + this._followNaturalScroll = follow; + } }); declare type _SwipeTrackerOptionalParams = { diff --git a/extension/src/trackers/pinchTracker.ts b/extension/src/trackers/pinchTracker.ts index db0a10a..e947d5f 100644 --- a/extension/src/trackers/pinchTracker.ts +++ b/extension/src/trackers/pinchTracker.ts @@ -2,12 +2,13 @@ import Clutter from '@gi-types/clutter8'; import GObject from '@gi-types/gobject2'; import Shell from '@gi-types/shell0'; import Meta from '@gi-types/meta8'; -import { CustomEventType, global, imports } from 'gnome-shell'; +import { global, imports } from 'gnome-shell'; const Main = imports.ui.main; import * as DBusUtils from '../utils/dbus'; import { registerClass } from '../../common/utils/gobject'; +import { CustomEventType } from '../../common/utils/clutter'; import { TouchpadConstants } from '../../constants'; const MIN_ANIMATION_DURATION = 100; diff --git a/extension/src/utils/dbus.ts b/extension/src/utils/dbus.ts index 7241446..a257320 100644 --- a/extension/src/utils/dbus.ts +++ b/extension/src/utils/dbus.ts @@ -1,9 +1,10 @@ -import { imports, global, CustomEventType } from 'gnome-shell'; +import { imports, global } from 'gnome-shell'; import Clutter from '@gi-types/clutter8'; import Gio from '@gi-types/gio2'; import GObject from '@gi-types/gobject2'; import { registerClass } from '../../common/utils/gobject'; +import { ClutterEventType, CustomEventType } from '../../common/utils/clutter'; import { printStack } from '../../common/utils/logging'; const Util = imports.misc.util; @@ -35,6 +36,17 @@ const DBusWrapperGIExtension = registerClass({ accumulator: GObject.AccumulatorType.TRUE_HANDLED, return_type: GObject.TYPE_BOOLEAN, }, + 'TouchpadHold': { + param_types: [ + GObject.TYPE_STRING, // phase + GObject.TYPE_INT, // fingers + GObject.TYPE_UINT, // time + GObject.TYPE_BOOLEAN, // is_cancelled + ], + flags: GObject.SignalFlags.RUN_LAST, + accumulator: GObject.AccumulatorType.TRUE_HANDLED, + return_type: GObject.TYPE_BOOLEAN, + }, 'TouchpadPinch': { param_types: [ GObject.TYPE_STRING, // phase @@ -63,6 +75,7 @@ const DBusWrapperGIExtension = registerClass({ ); this._proxyConnectSignalIds.push(this._proxy.connectSignal('TouchpadSwipe', this._handleDbusSwipeSignal.bind(this))); + this._proxyConnectSignalIds.push(this._proxy.connectSignal('TouchpadHold', this._handleDbusHoldSignal.bind(this))); this._proxyConnectSignalIds.push(this._proxy.connectSignal('TouchpadPinch', this._handleDbusPinchSignal.bind(this))); } @@ -81,6 +94,13 @@ const DBusWrapperGIExtension = registerClass({ this.emit('TouchpadSwipe', sphase, fingers, dx, dy, time); } + // eslint-disable-next-line @typescript-eslint/no-explicit-any + _handleDbusHoldSignal(_proxy: never, _sender: never, params: [any]): void { + // (siub) + const [sphase, fingers, time, is_cancelled] = params[0]; + this.emit('TouchpadHold', sphase, fingers, time, is_cancelled); + } + // eslint-disable-next-line @typescript-eslint/no-explicit-any _handleDbusPinchSignal(_proxy: never, _sender: never, params: [any]): void { // (siddu) @@ -97,7 +117,7 @@ type EventOptionalParams = Partial<{ is_cancelled: boolean, }>; -function GenerateEvent(type: Clutter.EventType, sphase: string, fingers: number, time: number, params: EventOptionalParams): CustomEventType { +function GenerateEvent(type: number, sphase: string, fingers: number, time: number, params: EventOptionalParams): CustomEventType { return { type: () => type, get_gesture_phase: () => { @@ -132,14 +152,21 @@ export function subscribe(callback: (actor: never | undefined, event: CustomEven connectedSignalIds.push( proxy.connect('TouchpadSwipe', (_source, sphase, fingers, dx, dy, time) => { - const event = GenerateEvent(Clutter.EventType.TOUCHPAD_SWIPE, sphase, fingers, time, { dx, dy }); + const event = GenerateEvent(ClutterEventType.TOUCHPAD_SWIPE, sphase, fingers, time, { dx, dy }); + return callback(undefined, event); + }), + ); + + connectedSignalIds.push( + proxy.connect('TouchpadHold', (_source, sphase, fingers, time, is_cancelled) => { + const event = GenerateEvent(ClutterEventType.TOUCHPAD_HOLD, sphase, fingers, time, { is_cancelled }); return callback(undefined, event); }), ); connectedSignalIds.push( proxy.connect('TouchpadPinch', (_source, sphase, fingers, pinch_angle_delta, pinch_scale, time) => { - const event = GenerateEvent(Clutter.EventType.TOUCHPAD_PINCH, sphase, fingers, time, { pinch_angle_delta, pinch_scale }); + const event = GenerateEvent(ClutterEventType.TOUCHPAD_PINCH, sphase, fingers, time, { pinch_angle_delta, pinch_scale }); return callback(undefined, event); }), ); diff --git a/extension/ui/prefs.ui b/extension/ui/prefs.ui index 4bd5dd5..db96601 100644 --- a/extension/ui/prefs.ui +++ b/extension/ui/prefs.ui @@ -436,6 +436,69 @@ other gesture, if enabled, will be activated using 4-finger gesture. + + + + 12 + + + start + center + Hold & Swipe Gestures + + + + + + + + + + + 36 + + + none + + + + + + + 12 + 12 + 12 + 12 + 32 + + + + start + center + 1 + 0 + Move window to previous/next workspace + + + + + + end + True + center + + + + + + + + + + + + + @@ -577,6 +640,42 @@ other gesture, if enabled, will be activated using 4-finger gesture. + + + + + + 12 + 12 + 12 + 12 + 32 + + + + start + center + 1 + 0 + Animate panel + + + + + + + No animation + When moving a window + When moving a window or switching workspace + + + + + + + + + diff --git a/gnome-shell/index.d.ts b/gnome-shell/index.d.ts index 50d23b5..86f1383 100644 --- a/gnome-shell/index.d.ts +++ b/gnome-shell/index.d.ts @@ -22,6 +22,11 @@ declare namespace __shell_private_types { _stageCaptureEvent: number; destroy(): void; _handleEvent(actor: Clutter.Actor | undefined, event: CustomEventType): boolean; + + /** This values are provided by Modified TouchpadGesture */ + hadHoldGesture?: boolean; + time?: number; + followNaturalScroll?: boolean; } declare interface IMonitorState { @@ -80,7 +85,7 @@ declare namespace imports { const layoutManager: { uiGroup: Clutter.Actor, panelBox: St.BoxLayout, - monitors: __shell_private_types.IMonitorState[], + monitors: __shell_private_types.IMonitorState[] primaryMonitor: __shell_private_types.IMonitorState, currentMonitor: __shell_private_types.IMonitorState, getWorkAreaForMonitor: (index: number) => Meta.Rectangle, @@ -116,21 +121,21 @@ declare namespace imports { _stateAdjustment: OverviewAdjustment; layoutManager: Clutter.BoxLayout & { _searchEntry: St.Bin - }; + } _toggleAppsPage(): void _workspacesDisplay: { _swipeTracker: swipeTracker.SwipeTracker - }; + } _appDisplay: { _swipeTracker: swipeTracker.SwipeTracker - }; + } _searchController: { searchActive: boolean - }; + } } } @@ -155,7 +160,7 @@ declare namespace imports { _endTouchpadGesture(): void; _history: { reset(): void; - }; + } } } @@ -180,7 +185,7 @@ declare namespace imports { _switchWorkspaceBegin(tracker: { orientation: Clutter.Orientation, confirmSwipe: typeof swipeTracker.SwipeTracker.prototype.confirmSwipe - }, monitor: number); + }, monitor: never); _switchWorkspaceUpdate(tracker: swipeTracker.SwipeTracker, progress: number); _switchWorkspaceEnd(tracker: swipeTracker.SwipeTracker, duration: number, progress: number); @@ -219,7 +224,7 @@ declare namespace imports { adjustment: St.Adjustment } } - }; + } _resetNoModsTimeout(): void; _noModsTimeoutId: number; @@ -230,13 +235,4 @@ declare namespace imports { } } } -} - -// types -export type CustomEventType = Pick< - import('@gi-types/clutter8').Event, - 'type' | 'get_gesture_phase' | - 'get_touchpad_gesture_finger_count' | 'get_time' | - 'get_coords' | 'get_gesture_motion_delta_unaccelerated' | - 'get_gesture_pinch_scale' | 'get_gesture_pinch_angle_delta' ->; \ No newline at end of file +} \ No newline at end of file