Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Drag and drop windows while navigating #259

Closed
wants to merge 60 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
60 commits
Select commit Hold shift + click to select a range
e5764df
wip: do DnD outside mutter grab
olejorgenb Jan 18, 2020
dd64416
Use clones global coordinates to insert
hedning Jan 18, 2020
b8f5733
Ensure all windows in the space belong to the workspace before comple…
olejorgenb Jan 19, 2020
b4c023e
Do DnD outside mutter grab
olejorgenb Jan 19, 2020
510a70d
utils: less verbose trace functions
olejorgenb Jan 19, 2020
99744ad
DnD mouse cursor
olejorgenb Jan 21, 2020
772b698
wip: always use fake grab
hedning Jan 24, 2020
818d90a
wip: grab everywhere
hedning Jan 24, 2020
4ad4899
better pointeroffset
hedning Jan 24, 2020
fa75a40
fix grab y position
hedning Jan 25, 2020
245d7aa
fix some coordinate transforms
hedning Jan 25, 2020
c4808c3
guard
hedning Jan 25, 2020
9dc3880
navigator: start dnd if in grab
hedning Jan 25, 2020
08f902a
isWindowAtPoint: use correct clone coordinate
hedning Jan 25, 2020
9bff4dc
get_coords doesn't take scaling into account
hedning Jan 25, 2020
f42cee9
position clone correctly when popped out of scaled space
hedning Jan 25, 2020
f43de26
navigator: always start dnd if grab is active
hedning Jan 25, 2020
9fb8b0e
space button-press: navigate to space if clicking on background
hedning Jan 25, 2020
32c6ad3
grab: remove positionChanged
hedning Jan 25, 2020
613abd2
grab: remove scroll-event
hedning Jan 25, 2020
69ec12c
space: only scroll if navigating or in a grab
hedning Jan 25, 2020
7e25f9a
reposition clone using pointer after insertion
hedning Jan 25, 2020
2f58d79
navigator: rewrite finish/destroy code
hedning Jan 25, 2020
4c1bff9
navigate to correct space on grab.end()
hedning Jan 26, 2020
17855a6
cleanup
hedning Jan 26, 2020
eb1e3f5
non dnd motion: coords were already monitor relative
hedning Jan 26, 2020
470182c
navigator: never move a visible space
hedning Jan 26, 2020
f7a2b45
begin dnd when pointer moves to another monitor
hedning Jan 26, 2020
2582970
Ensure grab window remains focused when ending navigation before dnd
olejorgenb Jan 27, 2020
e6397c7
space: fix scroll direction
hedning Jan 27, 2020
2f04b87
moveDone: guard against all grabbing
hedning Jan 28, 2020
58d1cd8
grab: make re-grabbing a window smoother
hedning Jan 28, 2020
dfa9994
Always run a complete switch workspace handler on navigation end (inP…
olejorgenb Jan 29, 2020
d4e24b1
Only react to background clicks when in preview mode
olejorgenb Jan 29, 2020
950efe0
utils:trace: don't require a metawindow
olejorgenb Jan 29, 2020
12cd597
wip: implemnt keyboard grab
hedning Feb 13, 2020
280a874
refactor to globalToScroll and globalToViewport
hedning Feb 15, 2020
ffb51ad
utils: crude actor tree printer
olejorgenb Feb 8, 2020
d594dc8
dnd: create zones and actors on the fly
olejorgenb Feb 16, 2020
610f654
dnd: handle empty spaces (on demand zones)
olejorgenb Feb 16, 2020
ddd6460
dnd: rip out the window immediatedly when ctrl is held down
olejorgenb Feb 16, 2020
3fa4c7b
navigator: Start DnD if grab is active on finish
hedning Feb 20, 2020
78d458a
scratch: Add animateWindows and showWindows
hedning Feb 20, 2020
67ebf1b
Substitute scratch windows with clones
hedning Feb 20, 2020
23224df
navigator: pseudo focus when hitting escape in grab
hedning Feb 20, 2020
4198b10
dnd: hide minimap
hedning Feb 20, 2020
c732e9c
begin: fix scratchish detection
hedning Feb 21, 2020
816b1d8
grab: fix for scratch windows
hedning Feb 21, 2020
c3e412c
fix clone jumping when dragging in a scaled space
hedning Feb 21, 2020
61628e1
dnd: rip out the window immediatedly when ctrl is held down (scratch …
olejorgenb Feb 22, 2020
3daf35e
ctrl grab: support gnome 3.36
hedning Feb 22, 2020
c7327b9
dnd: restore wider zone on empty workspaces (regression)
olejorgenb Feb 23, 2020
954bb42
dnd: bugfix zone selection
olejorgenb Feb 23, 2020
f64e804
navigator: do action before forcing dnd
hedning Feb 23, 2020
b9a3f9d
navigator finish: ensure moveDone is run
hedning Mar 1, 2020
3bfcb46
grab: pass the correct focus window to navigator
hedning Mar 8, 2020
b33f2d8
getModiferState: always return 0 on X11
olejorgenb Mar 14, 2020
6e71b07
Bugfix: (X11 only?) fix occationally initial jump when moving window
olejorgenb May 29, 2020
82abd2b
Hack to make clone marks survive grabs
olejorgenb May 29, 2020
ec7fa6e
get_pointer seems to be a reliable way to get modifiers on both X11 a…
olejorgenb May 29, 2020
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
558 changes: 313 additions & 245 deletions grab.js

Large diffs are not rendered by default.

5 changes: 5 additions & 0 deletions kludges.js
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,11 @@ if (!global.display.get_monitor_neighbor_index) {
}
}


if (!global.display.set_cursor) {
global.display.constructor.prototype.set_cursor = global.screen.set_cursor.bind(global.screen);
}

// polyfill for 3.28
if (!Meta.DisplayDirection && Meta.ScreenDirection) {
Meta.DisplayDirection = Meta.ScreenDirection;
Expand Down
77 changes: 52 additions & 25 deletions navigator.js
Original file line number Diff line number Diff line change
Expand Up @@ -153,14 +153,18 @@ var ActionDispatcher = class {
let metaWindow = space.selectedWindow;

if (action && action.options.activeInNavigator) {
if (action.options.opensMinimap) {
if (!Tiling.inGrab && action.options.opensMinimap) {
this.navigator._showMinimap(space);
}
action.handler(metaWindow, space, {navigator: this.navigator});
if (space !== Tiling.spaces.selectedSpace) {
this.navigator.minimaps.forEach(m => typeof(m) === 'number' ?
Mainloop.source_remove(m) : m.hide());
}
if (Tiling.inGrab && !Tiling.inGrab.dnd && Tiling.inGrab.window) {
Tiling.inGrab.beginDnD();
}

return true;
} else if (mutterActionId == Meta.KeyBindingAction.MINIMIZE) {
metaWindow.minimize();
Expand Down Expand Up @@ -194,7 +198,6 @@ var Navigator = class Navigator {
this._block = Main.wm._blockAnimations;
Main.wm._blockAnimations = true;
// Meta.disable_unredirect_for_screen(screen);

this.space = Tiling.spaces.spaceOf(workspaceManager.get_active_workspace());

this._startWindow = this.space.selectedWindow;
Expand All @@ -205,6 +208,7 @@ var Navigator = class Navigator {

TopBar.fixTopBar();

Scratch.animateWindows();
this.space.startAnimate();
}

Expand All @@ -227,21 +231,25 @@ var Navigator = class Navigator {
this.was_accepted = true;
}

finish() {
finish(space, focus) {
if (grab)
return;
this.accept();
this.destroy();
this.destroy(space, focus);
}

destroy() {
destroy(space, focus) {
this.minimaps.forEach(m => {
if (typeof(m) === 'number')
Mainloop.source_remove(m);
else
m.destroy();
});

if (Tiling.inGrab && !Tiling.inGrab.dnd) {
Tiling.inGrab.beginDnD()
}

if (Main.panel.statusArea.appMenu)
Main.panel.statusArea.appMenu.container.show();

Expand All @@ -250,9 +258,10 @@ var Navigator = class Navigator {

if (force) {
this.space.monitor.clickOverlay.hide();
this.space = Tiling.spaces.selectedSpace;
}

this.space = space || Tiling.spaces.selectedSpace;

let from = this.from;
let selected = this.space.selectedWindow;
if(!this.was_accepted) {
Expand All @@ -264,46 +273,64 @@ var Navigator = class Navigator {
selected = display.focus_window;
}

if (this.monitor !== this.space.monitor) {
this.space.setMonitor(this.monitor, true);
}

let visible = [];
for (let monitor of Main.layoutManager.monitors) {
if (monitor === this.monitor || !monitor.clickOverlay)
visible.push( Tiling.spaces.monitors.get(monitor));
if (monitor === this.monitor)
continue;
monitor.clickOverlay.activate();
}

if (this.space === from && force) {
if (!visible.includes(space) && this.monitor !== this.space.monitor) {
this.space.setMonitor(this.monitor, true);
}

if (this.space === from) {
// Animate the selected space into full view - normally this
// happens on workspace switch, but activating the same workspace
// again doesn't trigger a switch signal
Tiling.spaces.animateToSpace(this.space);
if (force) {
const workspaceId = this.space.workspace.index();
Tiling.spaces.switchWorkspace(null, workspaceId, workspaceId);
}
} else {
if (Tiling.inGrab && Tiling.inGrab.window) {
this.space.workspace.activate_with_focus(Tiling.inGrab.window, global.get_current_time());
} else {
this.space.workspace.activate(global.get_current_time());
}
}

selected = this.space.indexOf(selected) !== -1 ? selected :
this.space.selectedWindow;
if (selected &&
(!force ||
!(display.focus_window && display.focus_window.is_on_all_workspaces())) ) {

let hasFocus = selected.has_focus();
let curFocus = display.focus_window;
if (force && curFocus && curFocus.is_on_all_workspaces())
selected = curFocus;

if (focus)
selected = focus;

if (selected && !Tiling.inGrab) {
let hasFocus = selected && selected.has_focus();
selected.foreach_transient(mw => hasFocus = mw.has_focus() || hasFocus);
if (!hasFocus) {
Main.activateWindow(selected);
if (hasFocus) {
Tiling.focus_handler(selected)
} else {
// Typically on cancel - the `focus` signal won't run
// automatically, so we run it manually
Tiling.focus_handler(selected);
Main.activateWindow(selected);
}
debug('#preview', 'Finish', selected.title);
} else {
this.space.workspace.activate(global.get_current_time());
}
if (selected && Tiling.inGrab && !this.was_accepted) {
Tiling.focus_handler(selected)
}

if (!Tiling.inGrab)
Scratch.showWindows();

TopBar.fixTopBar();

Main.wm._blockAnimations = this._block;
this.space.moveDone();

this.emit('destroy', this.was_accepted);
navigator = false;
Expand Down
6 changes: 6 additions & 0 deletions notes.org
Original file line number Diff line number Diff line change
Expand Up @@ -290,3 +290,9 @@ The absolute path of the an extension: `Extension.dir.get_path()`
Clutter prints the FPS at regular intervals if ~CLUTTER_SHOW_FPS~ is set when gnome-shell starts. Where the output ends up depends on how gnome-shell was started. On my system it ends up in the system journal (journalctl)

To turn on off without disrupting flow too much use ~GLib.setenv("CLUTTER_SHOW_FPS", "1", true)~ and restart gnome-shell.
* Invariants
** Focus and active workspace
It's not possible the have a focused window which doesn't belong to the active workspace
~global.display.focus_window.workspace === workspaceManger.get_active_workspace()~
* Clutter animation
~time: 0~ does not result in an instant animation. A default duration seems to be selected instead.
18 changes: 18 additions & 0 deletions scratch.js
Original file line number Diff line number Diff line change
Expand Up @@ -199,6 +199,24 @@ function hide() {
});
}

function animateWindows() {
let ws = getScratchWindows().filter(w => !w.minimized);
ws = global.display.sort_windows_by_stacking(ws);
for (let w of ws) {
let parent = w.clone.get_parent()
parent && parent.remove_child(w.clone);
Main.uiGroup.insert_child_below(w.clone, Main.layoutManager.panelBox)
let f = w.get_frame_rect();
w.clone.set_position(f.x, f.y);
Tiling.animateWindow(w);
}
}

function showWindows() {
let ws = getScratchWindows().filter(w => !w.minimized);
ws.forEach(Tiling.showWindow)
}

// Monkey patch the alt-space menu
var PopupMenu = imports.ui.popupMenu;
var WindowMenu = imports.ui.windowMenu;
Expand Down
2 changes: 2 additions & 0 deletions stackoverlay.js
Original file line number Diff line number Diff line change
Expand Up @@ -373,6 +373,8 @@ var StackOverlay = class StackOverlay {
let column = space[index];
this.target = mru.filter(w => column.includes(w))[0];
let metaWindow = this.target;
if (!metaWindow)
return;

let overlay = this.overlay;
let actor = metaWindow.get_compositor_private();
Expand Down
102 changes: 85 additions & 17 deletions tiling.js
Original file line number Diff line number Diff line change
Expand Up @@ -540,7 +540,7 @@ class Space extends Array {

isWindowAtPoint(metaWindow, x, y) {
let clone = metaWindow.clone;
let wX = clone.targetX + this.cloneContainer.x;
let wX = clone.x + this.cloneContainer.x;
return x >= wX && x <= wX + clone.width &&
y >= clone.y && y <= clone.y + clone.height;
}
Expand Down Expand Up @@ -622,6 +622,8 @@ class Space extends Array {
this.visible.splice(this.visible.indexOf(metaWindow), 1);

let clone = metaWindow.clone;
if (clone.get_parent() !== this.cloneContainer)
utils.trace('wrong parent', metaWindow);
this.cloneContainer.remove_actor(clone);
// Don't destroy the selection highlight widget
if (clone.first_child.name === 'selection')
Expand Down Expand Up @@ -785,12 +787,23 @@ class Space extends Array {
return column.indexOf(metaWindow);
}

globalToViewport(gx, gy) {
let [ok, vx, vy] = this.actor.transform_stage_point(gx, gy);
return [Math.round(vx), Math.round(vy)];
}

/** Transform global coordinates to scroll cooridinates (cloneContainer relative) */
globalToScroll(gx, gy, useTarget=false) {
// NB: must use this.cloneContainer.transform_stage_point(gx, gy) if stuff is not simply translated
let x = gx - this.monitor.x - (useTarget ? this.targetX : this.cloneContainer.x);
let y = gy - this.monitor.y - this.cloneContainer.y;
return [x, y];
globalToScroll(gx, gy, {useTarget = false} = {}) {
// Use the smart transform on the actor, as that's the one we scale etc.
// We can then use straight translation on the scroll which makes it possible to use target instead if wanted.
let [vx, vy] = this.globalToViewport(gx, gy);
let sx = vx - (useTarget ? this.targetX : this.cloneContainer.x);
let sy = vy - this.cloneContainer.y;
return [Math.round(sx), Math.round(sy)];
}

viewportToScroll(vx, vy=0) {
return [vx - this.cloneContainer.x, vy - this.cloneContainer.y];
}

moveDone() {
Expand All @@ -799,7 +812,7 @@ class Space extends Array {
Navigator.navigating || inPreview ||
Main.overview.visible ||
// Block when we're carrying a window in dnd
(inGrab && inGrab.dnd && inGrab.window)
(inGrab && inGrab.window)
) {
return;
}
Expand Down Expand Up @@ -1057,16 +1070,55 @@ box-shadow: 0px 0px 8px 0px rgba(0, 0, 0, .7);
this.signals.connect(
this.background, 'button-press-event',
(actor, event) => {
let [aX, aY, mask] = global.get_pointer();
let [ok, x, y] =
this.actor.transform_stage_point(aX, aY);
if (inGrab) {
return;
}
let [gx, gy, $] = global.get_pointer();
let [ok, x, y] = this.actor.transform_stage_point(gx, gy);
let windowAtPoint = !Gestures.gliding && this.getWindowAtPoint(x, y);
let nav = Navigator.getNavigator();
if (windowAtPoint) {
ensureViewport(windowAtPoint, this);
inGrab = new Extension.imports.grab.MoveGrab(windowAtPoint, Meta.GrabOp.MOVING, this);
inGrab.begin();
} else if (inPreview) {
spaces.selectedSpace = this;
Navigator.getNavigator().finish();
}
});

this.signals.connect(
this.background, 'scroll-event',
(actor, event) => {
if (!inGrab && !Navigator.navigating)
return;
let dir = event.get_scroll_direction();
if (dir === Clutter.ScrollDirection.SMOOTH)
return;
// print(dir, Clutter.ScrollDirection.SMOOTH, Clutter.ScrollDirection.UP, Clutter.ScrollDirection.DOWN)
let dx
log(utils.ppEnumValue(dir, Clutter.ScrollDirection))
// let dx = dir === Clutter.ScrollDirection.DOWN ? -1 : 1
// let [dx, dy] = event.get_scroll_delta()

let [gx, gy] = event.get_coords();
if (!gx) {
print("Noooo");
return;
}
spaces.selectedSpace = this;
nav.finish();
print(dx, gx, gy);

switch (dir) {
case Clutter.ScrollDirection.LEFT:
case Clutter.ScrollDirection.UP:
this.switchLeft();
break;
case Clutter.ScrollDirection.RIGHT:
case Clutter.ScrollDirection.DOWN:
this.switchRight();
break;
}
// spaces.selectedSpace = this;
// nav.finish();
});

this.signals.connect(
Expand Down Expand Up @@ -1565,6 +1617,19 @@ class Spaces extends Map {
let toSpace = this.spaceOf(to);
let fromSpace = this.spaceOf(from);

if (inGrab && inGrab.window) {
inGrab.window.change_workspace(toSpace.workspace);
}

for (let metaWindow of toSpace.getWindows()) {
// Make sure all windows belong to the correct workspace.
// Note: The 'switch-workspace' signal (this method) runs before mutter decides on focus window.
// This simplifies other code moving windows between workspaces.
// Eg.: The DnD-window defer changing its workspace until the workspace actually is activated.
// This ensures the DnD window keep focus the whole time.
metaWindow.change_workspace(toSpace.workspace);
}

if (inPreview === PreviewMode.NONE && toSpace.monitor === fromSpace.monitor) {
// Only start an animation if we're moving between workspaces on the
// same monitor
Expand Down Expand Up @@ -2641,10 +2706,13 @@ function grabBegin(metaWindow, type) {
case Meta.GrabOp.MOVING:
inGrab = new Extension.imports.grab.MoveGrab(metaWindow, type);

if (!inGrab.initialSpace || inGrab.initialSpace.indexOf(metaWindow) === -1)
return;
if (utils.getModiferState() & Clutter.ModifierType.CONTROL_MASK) {
inGrab.begin();
inGrab.beginDnD();
} else if (inGrab.initialSpace && inGrab.initialSpace.indexOf(metaWindow) > -1) {
inGrab.begin();
}

inGrab.begin();
break;
case Meta.GrabOp.RESIZING_NW:
case Meta.GrabOp.RESIZING_N:
Expand All @@ -2669,7 +2737,7 @@ function grabBegin(metaWindow, type) {
}

function grabEnd(metaWindow, type) {
if (!inGrab)
if (!inGrab || inGrab.dnd || inGrab.grabbed)
return;

inGrab.end();
Expand Down
Loading