wip 3 finger touchpad scrolling

This commit is contained in:
Tor Hedin Brønner
2018-08-27 20:33:34 +02:00
parent 88bac15134
commit 2e8fe499b3
4 changed files with 329 additions and 2 deletions
+5 -1
View File
@@ -14,7 +14,7 @@ It will link the repo to `~/.local/share/gnome-shell-extensions/` where gnome-sh
## Usage ##
Most functionality is available using a mouse, eg. by clicking on a window at the edge of a monitor. But the primary focus is making an environment which works well with a keyboard.
Most functionality is available using a mouse, eg. activating a window at the edge of the monitor by clicking on it. In wayland its possible to navigate with 3-finger swipes on the trackpad. But the primary focus is making an environment which works well with a keyboard.
All keybindings start with the <kbd>Super</kbd> modifier. On most keyboards it's the Windows key, on mac keyboards it's the Command key. It's possible and recommended to modify the keyboard layout so that <kbd>Super</kbd> is switched with <kbd>Alt</kbd> making all the keybindings easier to reach. This can be done through Gnome Tweaks under `Keybard & Mouse` ⟶ `Additional Layout Options` ⟶ `Alt/Win key behavior` ⟶ `Left Alt is swapped with Left Win`.
@@ -34,6 +34,8 @@ Activating a window will ensure it's fully visible, scrolling the tiling if nece
Pressing <kbd>Super</kbd><kbd>I</kbd> will move the window to the right below the active window, tiling them vertically in a column. <kbd>Super</kbd><kbd>O</kbd> will do the opposite, pushing the bottom window out of the current column.
Swiping the trackpad horizontally with three fingers will scroll the tiling (only available in Wayland).
<kbd>Alt</kbd><kbd>Tab</kbd> is of course also available.
| Keybindings | |
@@ -67,6 +69,8 @@ The workspace name is shown in the top left corner replacing the `Activities` bu
![The workspace menu](https://github.com/paperwm/media/blob/master/menu.png)
Swiping the trackpad vertically with three fingers lets you navigate the workspace stack (only available in Wayland).
There's a single scrollable tiling per workspace. Adding another monitor simply makes it possible to have another workspace visible. The workspace stack is shared among all the monitors, windows being resized vertically as necessary when workspace is displayed on another monitor.
| Keybindings | |
+1 -1
View File
@@ -12,7 +12,7 @@ var modules = [
Extension.imports.stackoverlay, Extension.imports.app,
Extension.imports.kludges, Extension.imports.topbar,
Extension.imports.navigator, Extension.imports.settings,
Extension.imports.keybindings,
Extension.imports.keybindings, Extension.imports.gestures
];
var [ Tiling, Scratch, LiveAltTab,
utils, StackOverlay,
+314
View File
@@ -0,0 +1,314 @@
var Extension = imports.misc.extensionUtils.extensions['paperwm@hedning:matrix.org'];
var Meta = imports.gi.Meta;
var St = imports.gi.St;
var Gio = imports.gi.Gio;
var PanelMenu = imports.ui.panelMenu;
var PopupMenu = imports.ui.popupMenu;
var Clutter = imports.gi.Clutter;
var Main = imports.ui.main;
var Tweener = imports.ui.tweener;
var Gdk = imports.gi.Gdk;
var Utils = Extension.imports.utils;
var Tiling = Extension.imports.tiling;
var Navigator = Extension.imports.navigator;
var prefs = Extension.imports.settings.prefs;
const stage = global.stage;
var signals;
function init() {
signals = new Utils.Signals();
}
const DIRECTIONS = {
Horizontal: true,
Vertical: false,
}
var vy;
var time;
var hState, vState;
var navigator;
var direction = undefined;
function enable() {
// Touchpad swipes only works in Wayland
if (!Meta.is_wayland_compositor())
return;
signals.destroy();
/**
In order for the space.background actors to get any input we need to hide
all the window actors from the stage.
The stage takes care of scrolling vertically through the workspace mru.
Delegating the horizontal scrolling to each space. This way vertical
scrolling works anywhere, while horizontal scrolling is done on the space
under the mouse cursor.
*/
signals.connect(stage, 'captured-event', (actor, event) => {
if (event.type() !== Clutter.EventType.TOUCHPAD_SWIPE ||
event.get_touchpad_gesture_finger_count() > 3) {
return Clutter.EVENT_PROPAGATE;
}
const phase = event.get_gesture_phase();
switch (phase) {
case Clutter.TouchpadGesturePhase.UPDATE:
if (direction === DIRECTIONS.Horizontal) {
return Clutter.EVENT_PROPAGATE;
}
let [dx, dy] = event.get_gesture_motion_delta();
if (direction === undefined) {
log(`set direction ${dx} ${dy}`);
if (Math.abs(dx) < Math.abs(dy)) {
vy = 0;
vState = phase;
direction = DIRECTIONS.Vertical;
}
}
if (direction === DIRECTIONS.Vertical) {
updateVertical(-dy*2, event.get_time());
return Clutter.EVENT_STOP;
}
return Clutter.EVENT_PROPAGATE;
case Clutter.TouchpadGesturePhase.BEGIN:
time = event.get_time();
direction = undefined;
navigator = Navigator.getNavigator();
navigator.connect('destroy', () => {
vState = -1;
});
return Clutter.EVENT_STOP;
case Clutter.TouchpadGesturePhase.CANCEL:
case Clutter.TouchpadGesturePhase.END:
if (direction === DIRECTIONS.Vertical) {
vState = phase;
endVertical();
return Clutter.EVENT_STOP;
}
};
return Clutter.EVENT_PROPAGATE;
});
}
function disable() {
signals.destroy();
}
/**
Handle scrolling horizontally in a space. The handler is meant to be
connected from each space.background. See Tiling.space.constructor.
*/
function horizontalScroll(actor, event) {
if (event.type() !== Clutter.EventType.TOUCHPAD_SWIPE ||
event.get_touchpad_gesture_finger_count() > 3) {
return Clutter.EVENT_PROPAGATE;
}
const phase = event.get_gesture_phase();
switch (phase) {
case Clutter.TouchpadGesturePhase.UPDATE:
let [dx, dy] = event.get_gesture_motion_delta();
if (direction === undefined) {
actor.vx = 0;
hState = phase;
direction = DIRECTIONS.Horizontal;
}
return update(actor, -dx, event.get_time());
case Clutter.TouchpadGesturePhase.CANCEL:
case Clutter.TouchpadGesturePhase.END:
hState = phase;
return done(actor);
}
}
function update(actor, dx, t) {
let space = actor.space;
let v = dx/(t - time);
if (Number.isFinite(v)) {
actor.vx = (actor.vx + v)/2;
}
time = t;
space.targetX -= dx;
space.cloneContainer.x = space.targetX;
return Clutter.EVENT_STOP;
}
function done(actor) {
if (!Number.isFinite(actor.vx)) {
log(`${actor.vx} is not finite`)
navigator.finish();
hState = -1;
return Clutter.EVENT_STOP;
}
let test = actor.vx > 0 ?
() => actor.vx < 0 :
() => actor.vx > 0;
let space = actor.space;
log(`space ${space.actor}`);
let glide = () => {
if (hState < Clutter.TouchpadGesturePhase.END)
return false;
if (test() ||
space.targetX > 0 ||
space.targetX + space.cloneContainer.width < space.width) {
log(`end: ${space.targetX + space.cloneContainer.width} ${space.targetX} ${space.cloneContainer.width}`)
focusWindowAtPointer(actor);
space.cloneContainer.set_scale(1, 1);
return false;
}
let dx = actor.vx*16;
space.targetX -= dx;
space.cloneContainer.x = space.targetX;
actor.vx = actor.vx + (actor.vx > 0 ? -0.2 : 0.2);
return true;
};
imports.mainloop.timeout_add(16, glide, 0);
return Clutter.EVENT_STOP;
}
function focusWindowAtPointer(actor) {
log(`focus at pointer`)
let display = Gdk.Display.get_default();
let deviceManager = display.get_device_manager();
let pointer = deviceManager.get_client_pointer();
let [$, x, y] = pointer.get_position();
let space = actor.space;
x -= space.monitor.x;
y -= space.monitor.y;
space.targetX = Math.round(space.targetX);
space.cloneContainer.x = space.targetX;
let gap = prefs.window_gap/2;
let target;
let selected = space.selectedWindow.clone;
if (selected.x + space.targetX >= 0 &&
selected.x + selected.width + space.targetX <= space.width) {
log(`selected fully visible`);
target = space.selectedWindow;
}
if (!target) {
for (let w of space.getWindows()) {
let clone = w.clone;
if (clone.x + space.targetX - gap <= x
&& x <= clone.x + space.targetX + clone.width + gap) {
target = w;
log(`cursor over ${w.title}`);
break;
}
}
}
target && Tiling.ensureViewport(target, space);
if (space.cloneContainer.width < space.width) {
target = target || space.selectedWindow;
space.targetX = Math.round((space.width - (space.cloneContainer.width - gap))/2);
Tweener.addTween(space.cloneContainer,
{ x: space.targetX,
time: 0.25,
transition: 'easeInOutQuad',
onComplete: space.moveDone.bind(space)
});
} else if (0 <= space.cloneContainer.x) {
log(`last`)
let first = space[0][0];
target = target || first;
// Tiling.ensureViewport(target, space);
Tiling.move_to(space, first, {x: 0});
} else if (space.targetX + space.cloneContainer.width <= space.width) {
log(`last`)
let last = space[space.length-1][0];
target = target || last;
Tiling.move_to(space, last, {x: space.width - last.clone.width});
}
if (!Tiling.inPreview)
Navigator.getNavigator().finish();
}
var transition = 'easeOutQuad';
function updateVertical(dy, t) {
if (!Tiling.inPreview) {
Tiling.spaces._initWorkspaceStack();
}
let selected = Tiling.spaces.selectedSpace;
let monitor = navigator.monitor;
let v = dy/(t - time);
if (dy > 0
&& selected !== navigator.from
&& (selected.actor.y - dy < 0.035*monitor.height)
) {
dy = 0;
vy = 1;
selected.actor.y = 0.035*selected.height;
Tiling.spaces.selectSpace(Meta.MotionDirection.UP, false, transition);
selected = Tiling.spaces.selectedSpace;
Tweener.removeTweens(selected.actor);
Tweener.addTween(selected.actor,
{scale_x: 0.9, scale_y: 0.9, time: 0.25, transition});
} else if (dy < 0
&& (selected.actor.y - dy > 0.95*monitor.height)) {
dy = 0;
vy = -1;
selected.actor.y = 0.95*selected.height;
Tiling.spaces.selectSpace(Meta.MotionDirection.DOWN, false, transition);
selected = Tiling.spaces.selectedSpace;
Tweener.removeTweens(selected.actor);
Tweener.addTween(selected.actor,
{scale_x: 0.9, scale_y: 0.9, time: 0.25, transition});
} else if (Number.isFinite(v)) {
vy = (v + vy)/2;
}
selected.actor.y -= dy;
if (selected === navigator.from) {
let scale = 0.90;
let s = 1 - (1 - scale)*(selected.actor.y/(0.1*monitor.height));
s = Math.max(s, scale);
Tweener.removeTweens(selected.actor);
selected.actor.set_scale(s, s);
}
}
function endVertical() {
let test = vy > 0 ?
() => vy < 0 :
() => vy > 0;
let glide = () => {
if (vState < Clutter.TouchpadGesturePhase.END)
return false;
if (!Number.isFinite(vy)) {
log(`vertical velocity isn't finite`);
return false;
}
let selected = Tiling.spaces.selectedSpace;
let y = selected.actor.y;
if (selected === navigator.from && y <= 0.1*selected.height) {
log(`finish ${y}`);
navigator.finish();
return false;
}
if (test()) {
return false;
}
let friction = 0.05;
let dy = vy*16;
let v = vy;
updateVertical(dy, time + 16);
vy = vy + (v > 0 ? -0.1 : 0.1);
return true; // repeat
};
imports.mainloop.timeout_add(16, glide, 0);
}
+9
View File
@@ -153,6 +153,7 @@ class Space extends Array {
reactive: true // Disable the background menu
}, meta_display)
);
background.space = this;
// This kills all input on X11, but works on wayland...
if (Meta.is_wayland_compositor()) {
@@ -164,6 +165,11 @@ class Space extends Array {
spaces.selectedSpace = this;
nav.finish();
});
this.signals.connect(background, 'captured-event',
(actor, event) => {
Extension.imports.gestures.horizontalScroll(actor, event);
});
}
this.background = background;
this.shadow = new St.Widget();;
@@ -280,6 +286,7 @@ class Space extends Array {
}
}
this._inLayout = false;
this.cloneContainer.width = x - gap;
let width = x - gap;
this.cloneContainer.width = width;
@@ -1052,6 +1059,8 @@ class Spaces extends Map {
}
_initWorkspaceStack() {
if (inPreview)
return;
const scale = 0.9;
let space = this.spaceOf(workspaceManager.get_active_workspace());
let mru = [space, ...this.stack];