Skip to content

Commit

Permalink
Feature: move space to other monitors (#825)
Browse files Browse the repository at this point in the history
This PR resolves #824.

It adds the following:
- keybinds (`Ctrl`+`Alt`+`left`/`right`/`up`/`down`) which moves the
current workspace to a different monitor in that direction;
- if a workspace is the last on a monitor, we fallback to swapping that
space with the target monitor space;
- informs user with notification if can't move / swap a space;
- FIXES #816 by creating spaces to meet a minimum (i.e. PaperWM breaks
if have 2 monitors and only one space...);
  • Loading branch information
jtaala authored Apr 15, 2024
2 parents c8456b4 + 492dce8 commit 033196b
Show file tree
Hide file tree
Showing 7 changed files with 174 additions and 26 deletions.
1 change: 0 additions & 1 deletion extension.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@ import GLib from 'gi://GLib';
import St from 'gi://St';

import * as Main from 'resource:///org/gnome/shell/ui/main.js';
import * as MessageTray from 'resource:///org/gnome/shell/ui/messageTray.js';
import * as Util from 'resource:///org/gnome/shell/misc/util.js';

import {
Expand Down
13 changes: 13 additions & 0 deletions keybindings.js
Original file line number Diff line number Diff line change
Expand Up @@ -124,6 +124,19 @@ export function setupActions(settings) {
Tiling.spaces.switchMonitor(Meta.DisplayDirection.DOWN, false);
}, { settings });

registerAction('move-space-monitor-right', () => {
Tiling.spaces.moveToMonitor(Meta.DisplayDirection.RIGHT, Meta.DisplayDirection.LEFT);
}, { settings });
registerAction('move-space-monitor-left', () => {
Tiling.spaces.moveToMonitor(Meta.DisplayDirection.LEFT, Meta.DisplayDirection.RIGHT);
}, { settings });
registerAction('move-space-monitor-above', () => {
Tiling.spaces.moveToMonitor(Meta.DisplayDirection.UP, Meta.DisplayDirection.DOWN);
}, { settings });
registerAction('move-space-monitor-below', () => {
Tiling.spaces.moveToMonitor(Meta.DisplayDirection.DOWN, Meta.DisplayDirection.UP);
}, { settings });

registerAction('swap-monitor-right', () => {
Tiling.spaces.swapMonitor(Meta.DisplayDirection.RIGHT, Meta.DisplayDirection.LEFT);
}, { settings });
Expand Down
17 changes: 16 additions & 1 deletion patches.js
Original file line number Diff line number Diff line change
Expand Up @@ -576,8 +576,23 @@ export function _checkWorkspaces() {
let workspaceManager = global.workspace_manager;
let i;
let emptyWorkspaces = [];
let minimum = Main.layoutManager.monitors.length + 1;

if (!Meta.prefs_get_dynamic_workspaces()) {
// if less spaces than minimum, create!
let created = 0;
while (workspaceManager.nWorkspaces < minimum) {
workspaceManager.append_new_workspace(false, global.get_current_time());
created++;
}

if (created > 0) {
Main.notify(
`PaperWM (created ${created} workspaces)`,
`PaperWM requires a minimum of ${minimum} workspaces for you monitor configuration.`
);
}

this._checkWorkspacesId = 0;
return false;
}
Expand Down Expand Up @@ -621,11 +636,11 @@ export function _checkWorkspaces() {
* Set minimum workspaces to be max of num_monitors+1.
* This ensures that we have at least one workspace at the end.
*/
let minimum = Main.layoutManager.monitors.length + 1;
// Make sure we have a minimum number of spaces
for (i = 0; i < minimum; i++) {
if (i >= emptyWorkspaces.length) {
workspaceManager.append_new_workspace(false, global.get_current_time());
console.log(`created workspace`);
emptyWorkspaces.push(true);
}
}
Expand Down
4 changes: 4 additions & 0 deletions prefsKeybinding.js
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,10 @@ const actions = {
'switch-monitor-left',
'switch-monitor-above',
'switch-monitor-below',
'move-space-monitor-right',
'move-space-monitor-left',
'move-space-monitor-above',
'move-space-monitor-below',
'swap-monitor-right',
'swap-monitor-left',
'swap-monitor-above',
Expand Down
Binary file modified schemas/gschemas.compiled
Binary file not shown.
17 changes: 17 additions & 0 deletions schemas/org.gnome.shell.extensions.paperwm.gschema.xml
Original file line number Diff line number Diff line change
Expand Up @@ -108,6 +108,23 @@
<summary>Switch to the below monitor</summary>
</key>

<key type="as" name="move-space-monitor-right">
<default><![CDATA[['<Ctrl><Alt><Shift>Right']]]></default>
<summary>Move workspace to monitor on the right</summary>
</key>
<key type="as" name="move-space-monitor-left">
<default><![CDATA[['<Ctrl><Alt><Shift>Left']]]></default>
<summary>Move workspace to monitor on the left</summary>
</key>
<key type="as" name="move-space-monitor-above">
<default><![CDATA[['<Ctrl><Alt><Shift>Up']]]></default>
<summary>Move workspace to monitor above</summary>
</key>
<key type="as" name="move-space-monitor-below">
<default><![CDATA[['<Ctrl><Alt><Shift>Down']]]></default>
<summary>Move workspace to monitor below</summary>
</key>

<key type="as" name="swap-monitor-right">
<default><![CDATA[['<Super><Alt>Right']]]></default>
<summary>Swap workspace with monitor to the right</summary>
Expand Down
148 changes: 124 additions & 24 deletions tiling.js
Original file line number Diff line number Diff line change
Expand Up @@ -2051,14 +2051,14 @@ export const Spaces = class Spaces extends Map {
});

this.signals.connect(display, 'window-created',
(display, metaWindow, user_data) => this.window_created(metaWindow));
(display, metaWindow, _user_data) => this.window_created(metaWindow));

this.signals.connect(display, 'grab-op-begin', (display, mw, type) => grabBegin(mw, type));
this.signals.connect(display, 'grab-op-end', (display, mw, type) => grabEnd(mw, type));


this.signals.connect(global.window_manager, 'switch-workspace',
(wm, from, to, direction) => this.switchWorkspace(wm, from, to));
(wm, from, to, _direction) => this.switchWorkspace(wm, from, to));

this.signals.connect(this.overrideSettings, 'changed::workspaces-only-on-primary', () => {
displayConfig.upgradeGnomeMonitors(() => this.monitorsChanged());
Expand Down Expand Up @@ -2286,7 +2286,7 @@ export const Spaces = class Spaces extends Map {
this.signals = null;

// remove spaces
for (let [workspace, space] of this) {
for (let [, space] of this) {
this.removeSpace(space);
}

Expand All @@ -2312,7 +2312,7 @@ export const Spaces = class Spaces extends Map {
}

let nextUnusedWorkspaceIndex = nWorkspaces;
for (let [workspace, space] of this) {
for (let [, space] of this) {
if (workspaces[space.workspace] !== true) {
this.removeSpace(space);

Expand All @@ -2331,6 +2331,20 @@ export const Spaces = class Spaces extends Map {
}
}

/**
* Return true if there are less-than-or-equal-to spaces than monitors.
*/
lteSpacesThanMonitors(onFalseCallback) {
const cb = onFalseCallback ?? function(_nSpaces, _nMonitors) {};
const nSpaces = [...this].length;
const nMonitors = Main.layoutManager.monitors.length;

if (nSpaces <= nMonitors) {
cb(nSpaces, nMonitors);
}
return nSpaces <= nMonitors;
}

switchMonitor(direction, move, warp = true) {
let focus = display.focus_window;
let monitor = focusMonitor();
Expand Down Expand Up @@ -2372,12 +2386,91 @@ export const Spaces = class Spaces extends Map {
}
}

swapMonitor(direction, backDirection) {
moveToMonitor(direction, backDirection) {
const monitor = focusMonitor();
const i = display.get_monitor_neighbor_index(monitor.index, direction);
if (i === -1)
return;

if (this.lteSpacesThanMonitors(
(s, m) => Main.notify(
`PaperWM (cannot move workspace)`,
`You need at least ${m + 1} workspaces to complete this operation.`
)
)) {
return;
}

// check how many spaces are on this monitor
const numSpaces = [...this].filter(([_monitor, space]) => space?.monitor === monitor).length;
// if last space on this monitor, then swap
if (numSpaces <= 1) {
this.swapMonitor(direction, backDirection);
return;
}

let navFinish = () => Navigator.getNavigator().finish();
// action on current monitor
this.selectStackSpace(Meta.MotionDirection.DOWN);
navFinish();
// switch to target monitor and action mru
this.switchMonitor(direction, false, true);
this.selectStackSpace(Meta.MotionDirection.DOWN);
navFinish();

// /**
// * Fullscreen monitor workaround.
// * see https://github.com/paperwm/PaperWM/issues/638
// */
// this.forEach(space => {
// space.getWindows().filter(w => w.fullscreen).forEach(w => {
// animateWindow(w);
// w.unmake_fullscreen();
// w.make_fullscreen();
// showWindow(w);
// });
// });

// ensure after swapping that the space elements are shown correctly
this.setSpaceTopbarElementsVisible(true, { force: true });
}

swapMonitor(direction, backDirection, options = {}) {
const checkIfLast = options.checkIfLast ?? true;
const warpIfLast = options.warpIfLast ?? true;

const monitor = focusMonitor();
const i = display.get_monitor_neighbor_index(monitor.index, direction);
if (i === -1)
return;

if (this.lteSpacesThanMonitors(
(s, m) => Main.notify(
`PaperWM (cannot swap workspaces)`,
`You need at least ${m + 1} workspaces to complete this operation.`
)
)) {
return;
}

if (checkIfLast) {
// check how many spaces are on this monitor
const numSpaces = [...this].filter(([_monitor, space]) => space?.monitor === monitor).length;
// if last space on this monitor, then swap
if (numSpaces <= 1) {
// focus other monitor for a switchback
this.switchMonitor(direction, false, false);
this.swapMonitor(backDirection, direction, {
checkIfLast: false,
warpIfLast: false,
});

// now switch monitor with warp since we back-flipped
this.switchMonitor(direction, false, true);
return;
}
}

let navFinish = () => Navigator.getNavigator().finish();
// action on current monitor
this.selectStackSpace(Meta.MotionDirection.DOWN);
Expand All @@ -2391,20 +2484,20 @@ export const Spaces = class Spaces extends Map {
this.selectStackSpace(Meta.MotionDirection.DOWN);
navFinish();
// final switch with warp
this.switchMonitor(direction);

/**
* Fullscreen monitor workaround.
* see https://github.com/paperwm/PaperWM/issues/638
*/
this.forEach(space => {
space.getWindows().filter(w => w.fullscreen).forEach(w => {
animateWindow(w);
w.unmake_fullscreen();
w.make_fullscreen();
showWindow(w);
});
});
this.switchMonitor(direction, false, warpIfLast);

// /**
// * Fullscreen monitor workaround.
// * see https://github.com/paperwm/PaperWM/issues/638
// */
// this.forEach(space => {
// space.getWindows().filter(w => w.fullscreen).forEach(w => {
// animateWindow(w);
// w.unmake_fullscreen();
// w.make_fullscreen();
// showWindow(w);
// });
// });

// ensure after swapping that the space elements are shown correctly
this.setSpaceTopbarElementsVisible(true, { force: true });
Expand Down Expand Up @@ -2484,11 +2577,18 @@ export const Spaces = class Spaces extends Map {
let out = [];
for (let i = 0; i < nWorkspaces; i++) {
let space = this.spaceOf(workspaceManager.get_workspace_by_index(i));
if (space.monitor === monitor ||
(space.length === 0 && this.monitors.get(space.monitor) !== space)) {
// include workspace if it is the current one
// or if it is empty and not active on another monitor

if (space.monitor === monitor) {
out.push(space);
continue;
}

// include workspace if it is the current one
// or if it is empty and not active on another monitor
if (space.length === 0 &&
this.monitors.get(space.monitor) !== space) {
out.push(space);
continue;
}
}
return out;
Expand Down Expand Up @@ -2852,7 +2952,7 @@ export const Spaces = class Spaces extends Map {
}

let visible = new Map();
for (let [monitor, space] of this.monitors) {
for (let [, space] of this.monitors) {
visible.set(space, true);
}

Expand Down

0 comments on commit 033196b

Please sign in to comment.