diff --git a/keybindings.js b/keybindings.js
index 2899781fa..c1d2b6dca 100644
--- a/keybindings.js
+++ b/keybindings.js
@@ -264,6 +264,15 @@ export function setupActions(settings) {
registerMinimapAction("move-down",
(_mw, space) => space.swap(Meta.MotionDirection.DOWN));
+ registerMinimapAction("move-global-left",
+ (_mw, space) => space.swapGlobal(Meta.MotionDirection.LEFT));
+ registerMinimapAction("move-global-right",
+ (_mw, space) => space.swapGlobal(Meta.MotionDirection.RIGHT));
+ registerMinimapAction("move-global-up",
+ (_mw, space) => space.swapGlobal(Meta.MotionDirection.UP));
+ registerMinimapAction("move-global-down",
+ (_mw, space) => space.swapGlobal(Meta.MotionDirection.DOWN));
+
registerPaperAction("toggle-scratch-window",
Scratch.toggleScratchWindow);
diff --git a/prefsKeybinding.js b/prefsKeybinding.js
index 67ff8bb81..3c509727e 100644
--- a/prefsKeybinding.js
+++ b/prefsKeybinding.js
@@ -69,6 +69,10 @@ const actions = {
'move-right',
'move-up',
'move-down',
+ 'move-global-left',
+ 'move-global-right',
+ 'move-global-up',
+ 'move-global-down',
'slurp-in',
'barf-out',
'barf-out-active',
diff --git a/schemas/gschemas.compiled b/schemas/gschemas.compiled
index 484bc3286..89758b2fb 100644
Binary files a/schemas/gschemas.compiled and b/schemas/gschemas.compiled differ
diff --git a/schemas/org.gnome.shell.extensions.paperwm.gschema.xml b/schemas/org.gnome.shell.extensions.paperwm.gschema.xml
index 1819e96b0..029b9c1a5 100644
--- a/schemas/org.gnome.shell.extensions.paperwm.gschema.xml
+++ b/schemas/org.gnome.shell.extensions.paperwm.gschema.xml
@@ -347,11 +347,11 @@
period', 'period', 'Right']]]>
- Move the active window to the right
+ Move the active window to the right, staying on the same monitor
comma', 'comma', 'Left']]]>
- Move the active window to the left
+ Move the active window to the left, staying on the same monitor
Up']]]>
@@ -362,6 +362,23 @@
Move the active window down
+
+
+ Move the active window to the right
+
+
+
+ Move the active window to the left
+
+
+
+ Move the active window up or to the workspace above
+
+
+
+ Move the active window down or to the workspace below
+
+
i']]]>
Consume window into the active column
diff --git a/tiling.js b/tiling.js
index b6fd65925..0790f5237 100644
--- a/tiling.js
+++ b/tiling.js
@@ -1095,6 +1095,46 @@ export class Space extends Array {
ensureViewport(this.selectedWindow, this, { force: true });
}
+ swapGlobal(direction, metaWindow) {
+ metaWindow = metaWindow || this.selectedWindow;
+
+ let [index, row] = this.positionOf(metaWindow);
+ let targetIndex = index;
+ let targetRow = row;
+ const dir = Utils.motionToDisplayDirection[direction];
+ switch (direction) {
+ case Meta.MotionDirection.LEFT:
+ targetIndex--;
+ break;
+ case Meta.MotionDirection.RIGHT:
+ targetIndex++;
+ break;
+ case Meta.MotionDirection.DOWN:
+ targetRow++;
+ break;
+ case Meta.MotionDirection.UP:
+ targetRow--;
+ break;
+ }
+ if (targetIndex < 0 || targetIndex >= this.length) {
+ spaces.switchMonitor(dir, true, true, metaWindow);
+ return;
+ }
+ let column = this[index];
+ if (targetRow < 0 || targetRow >= column.length) {
+ spaces.selectSequenceSpace(direction, true);
+ Navigator.finishNavigation();
+ return;
+ }
+
+ Lib.swap(this[index], row, targetRow);
+ Lib.swap(this, index, targetIndex);
+
+ this.layout();
+ this.emit('swapped', index, targetIndex, row, targetRow);
+ ensureViewport(this.selectedWindow, this, { force: true });
+ }
+
switchLinear(dir, loop) {
let index = this.selectedIndex();
let column = this[index];
@@ -1190,13 +1230,8 @@ export class Space extends Array {
switchGlobalUp() { this.switchGlobal(Meta.MotionDirection.UP); }
switchGlobalDown() { this.switchGlobal(Meta.MotionDirection.DOWN); }
switchGlobal(direction) {
- const motionToDisplayDirection = {
- [Meta.MotionDirection.LEFT]: Meta.DisplayDirection.LEFT,
- [Meta.MotionDirection.RIGHT]: Meta.DisplayDirection.RIGHT,
- [Meta.MotionDirection.UP]: Meta.DisplayDirection.UP,
- [Meta.MotionDirection.DOWN]: Meta.DisplayDirection.DOWN,
- };
- const dir = motionToDisplayDirection[direction]
+ const dir = Utils.motionToDisplayDirection[direction];
+
let space = this;
const switchMonitor = () => {
@@ -2566,8 +2601,10 @@ export const Spaces = class Spaces extends Map {
return nSpaces <= nMonitors;
}
- switchMonitor(direction, move, warp = true) {
- let focus = display.focus_window;
+ switchMonitor(direction, move, warp = true, focus = null) {
+ // For unkown reasons, display.focus_window is null if you are in the
+ // middle of moving a winodw, aka if navigation is open.
+ focus = focus ?? display.focus_window;
let monitor = focusMonitor();
let currentSpace = this.monitors.get(monitor);
let i = display.get_monitor_neighbor_index(monitor.index, direction);
@@ -2580,6 +2617,12 @@ export const Spaces = class Spaces extends Map {
let space = this.monitors.get(newMonitor);
if (move && focus) {
+ const customIndex = getMoveWindowPositionIndex(space, direction);
+ if (customIndex !== null) {
+ // namespaced prop to avoid possible future collisions
+ focus.paperwm_openAtIndex = customIndex;
+ }
+
let metaWindow = focus.get_transient_for() || focus;
if (currentSpace && currentSpace.indexOf(metaWindow) !== -1) {
@@ -4164,7 +4207,13 @@ Opening "${metaWindow?.title}" on current space.`);
}
ok && clone.set_position(x, y);
- if (!space.addWindow(metaWindow, getOpenWindowPositionIndex(space)))
+ // When moving a window from another monitor, we may request a certain index
+ const openAtIndex = metaWindow.paperwm_openAtIndex ?? getOpenWindowPositionIndex(space);
+ if (metaWindow.paperwm_openAtIndex) {
+ delete metaWindow.paperwm_openAtIndex;
+ }
+
+ if (!space.addWindow(metaWindow, openAtIndex))
return;
metaWindow.unmake_above();
@@ -4267,6 +4316,43 @@ Opening "${metaWindow?.title}" on current space.`);
}
}
+/**
+ * When we're moving a window from an existing monitor, we want to insert with
+ * minimal disruption. E.g. if we're moving window E to the left,
+ *
+ * ([a b] c d) ([E f])
+ * (a [b E] c d) ([f])
+ *
+ * so that visually it looks like this:
+ *
+ * [a b] [E f]
+ * [b E] [f g]
+ *
+ * regardless of the setting for inserting new windows.
+ */
+function getMoveWindowPositionIndex(space, direction) {
+ const visibleColumns = space.filter(([mw]) => space.isVisible(mw));
+
+ if (visibleColumns.length === 0) {
+ return null;
+ }
+
+ switch (direction) {
+ case Meta.DisplayDirection.LEFT: {
+ const windowAtTarget = visibleColumns[visibleColumns.length - 1][0];
+ return space.indexOf(windowAtTarget) + 1;
+ break;
+ }
+ case Meta.DisplayDirection.RIGHT: {
+ const windowAtTarget = visibleColumns[0][0];
+ return space.indexOf(windowAtTarget);
+ }
+ default:
+ // No special handling yet for moving up/down
+ return null;
+ }
+}
+
/**
* Gets the window index to add a new window in the space:
* { RIGHT: 0, LEFT: 1, START: 2, END: 3 };
diff --git a/utils.js b/utils.js
index 15bb86af8..8c523907b 100644
--- a/utils.js
+++ b/utils.js
@@ -105,6 +105,13 @@ export function setBackgroundImage(actor, resource_path) {
actor.content_repeat = Clutter.ContentRepeat.BOTH;
}
+export const motionToDisplayDirection = {
+ [Meta.MotionDirection.LEFT]: Meta.DisplayDirection.LEFT,
+ [Meta.MotionDirection.RIGHT]: Meta.DisplayDirection.RIGHT,
+ [Meta.MotionDirection.UP]: Meta.DisplayDirection.UP,
+ [Meta.MotionDirection.DOWN]: Meta.DisplayDirection.DOWN,
+};
+
/**
* Backwards compatible function. Attempts to use Cogl.Color with a fallback
* to Clutter.Color.