Skip to content

Commit 033196b

Browse files
authored
Feature: move space to other monitors (#825)
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...);
2 parents c8456b4 + 492dce8 commit 033196b

File tree

7 files changed

+174
-26
lines changed

7 files changed

+174
-26
lines changed

extension.js

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,6 @@ import GLib from 'gi://GLib';
33
import St from 'gi://St';
44

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

98
import {

keybindings.js

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -124,6 +124,19 @@ export function setupActions(settings) {
124124
Tiling.spaces.switchMonitor(Meta.DisplayDirection.DOWN, false);
125125
}, { settings });
126126

127+
registerAction('move-space-monitor-right', () => {
128+
Tiling.spaces.moveToMonitor(Meta.DisplayDirection.RIGHT, Meta.DisplayDirection.LEFT);
129+
}, { settings });
130+
registerAction('move-space-monitor-left', () => {
131+
Tiling.spaces.moveToMonitor(Meta.DisplayDirection.LEFT, Meta.DisplayDirection.RIGHT);
132+
}, { settings });
133+
registerAction('move-space-monitor-above', () => {
134+
Tiling.spaces.moveToMonitor(Meta.DisplayDirection.UP, Meta.DisplayDirection.DOWN);
135+
}, { settings });
136+
registerAction('move-space-monitor-below', () => {
137+
Tiling.spaces.moveToMonitor(Meta.DisplayDirection.DOWN, Meta.DisplayDirection.UP);
138+
}, { settings });
139+
127140
registerAction('swap-monitor-right', () => {
128141
Tiling.spaces.swapMonitor(Meta.DisplayDirection.RIGHT, Meta.DisplayDirection.LEFT);
129142
}, { settings });

patches.js

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -576,8 +576,23 @@ export function _checkWorkspaces() {
576576
let workspaceManager = global.workspace_manager;
577577
let i;
578578
let emptyWorkspaces = [];
579+
let minimum = Main.layoutManager.monitors.length + 1;
579580

580581
if (!Meta.prefs_get_dynamic_workspaces()) {
582+
// if less spaces than minimum, create!
583+
let created = 0;
584+
while (workspaceManager.nWorkspaces < minimum) {
585+
workspaceManager.append_new_workspace(false, global.get_current_time());
586+
created++;
587+
}
588+
589+
if (created > 0) {
590+
Main.notify(
591+
`PaperWM (created ${created} workspaces)`,
592+
`PaperWM requires a minimum of ${minimum} workspaces for you monitor configuration.`
593+
);
594+
}
595+
581596
this._checkWorkspacesId = 0;
582597
return false;
583598
}
@@ -621,11 +636,11 @@ export function _checkWorkspaces() {
621636
* Set minimum workspaces to be max of num_monitors+1.
622637
* This ensures that we have at least one workspace at the end.
623638
*/
624-
let minimum = Main.layoutManager.monitors.length + 1;
625639
// Make sure we have a minimum number of spaces
626640
for (i = 0; i < minimum; i++) {
627641
if (i >= emptyWorkspaces.length) {
628642
workspaceManager.append_new_workspace(false, global.get_current_time());
643+
console.log(`created workspace`);
629644
emptyWorkspaces.push(true);
630645
}
631646
}

prefsKeybinding.js

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -81,6 +81,10 @@ const actions = {
8181
'switch-monitor-left',
8282
'switch-monitor-above',
8383
'switch-monitor-below',
84+
'move-space-monitor-right',
85+
'move-space-monitor-left',
86+
'move-space-monitor-above',
87+
'move-space-monitor-below',
8488
'swap-monitor-right',
8589
'swap-monitor-left',
8690
'swap-monitor-above',

schemas/gschemas.compiled

336 Bytes
Binary file not shown.

schemas/org.gnome.shell.extensions.paperwm.gschema.xml

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -108,6 +108,23 @@
108108
<summary>Switch to the below monitor</summary>
109109
</key>
110110

111+
<key type="as" name="move-space-monitor-right">
112+
<default><![CDATA[['<Ctrl><Alt><Shift>Right']]]></default>
113+
<summary>Move workspace to monitor on the right</summary>
114+
</key>
115+
<key type="as" name="move-space-monitor-left">
116+
<default><![CDATA[['<Ctrl><Alt><Shift>Left']]]></default>
117+
<summary>Move workspace to monitor on the left</summary>
118+
</key>
119+
<key type="as" name="move-space-monitor-above">
120+
<default><![CDATA[['<Ctrl><Alt><Shift>Up']]]></default>
121+
<summary>Move workspace to monitor above</summary>
122+
</key>
123+
<key type="as" name="move-space-monitor-below">
124+
<default><![CDATA[['<Ctrl><Alt><Shift>Down']]]></default>
125+
<summary>Move workspace to monitor below</summary>
126+
</key>
127+
111128
<key type="as" name="swap-monitor-right">
112129
<default><![CDATA[['<Super><Alt>Right']]]></default>
113130
<summary>Swap workspace with monitor to the right</summary>

tiling.js

Lines changed: 124 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -2051,14 +2051,14 @@ export const Spaces = class Spaces extends Map {
20512051
});
20522052

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

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

20592059

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

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

22882288
// remove spaces
2289-
for (let [workspace, space] of this) {
2289+
for (let [, space] of this) {
22902290
this.removeSpace(space);
22912291
}
22922292

@@ -2312,7 +2312,7 @@ export const Spaces = class Spaces extends Map {
23122312
}
23132313

23142314
let nextUnusedWorkspaceIndex = nWorkspaces;
2315-
for (let [workspace, space] of this) {
2315+
for (let [, space] of this) {
23162316
if (workspaces[space.workspace] !== true) {
23172317
this.removeSpace(space);
23182318

@@ -2331,6 +2331,20 @@ export const Spaces = class Spaces extends Map {
23312331
}
23322332
}
23332333

2334+
/**
2335+
* Return true if there are less-than-or-equal-to spaces than monitors.
2336+
*/
2337+
lteSpacesThanMonitors(onFalseCallback) {
2338+
const cb = onFalseCallback ?? function(_nSpaces, _nMonitors) {};
2339+
const nSpaces = [...this].length;
2340+
const nMonitors = Main.layoutManager.monitors.length;
2341+
2342+
if (nSpaces <= nMonitors) {
2343+
cb(nSpaces, nMonitors);
2344+
}
2345+
return nSpaces <= nMonitors;
2346+
}
2347+
23342348
switchMonitor(direction, move, warp = true) {
23352349
let focus = display.focus_window;
23362350
let monitor = focusMonitor();
@@ -2372,12 +2386,91 @@ export const Spaces = class Spaces extends Map {
23722386
}
23732387
}
23742388

2375-
swapMonitor(direction, backDirection) {
2389+
moveToMonitor(direction, backDirection) {
2390+
const monitor = focusMonitor();
2391+
const i = display.get_monitor_neighbor_index(monitor.index, direction);
2392+
if (i === -1)
2393+
return;
2394+
2395+
if (this.lteSpacesThanMonitors(
2396+
(s, m) => Main.notify(
2397+
`PaperWM (cannot move workspace)`,
2398+
`You need at least ${m + 1} workspaces to complete this operation.`
2399+
)
2400+
)) {
2401+
return;
2402+
}
2403+
2404+
// check how many spaces are on this monitor
2405+
const numSpaces = [...this].filter(([_monitor, space]) => space?.monitor === monitor).length;
2406+
// if last space on this monitor, then swap
2407+
if (numSpaces <= 1) {
2408+
this.swapMonitor(direction, backDirection);
2409+
return;
2410+
}
2411+
2412+
let navFinish = () => Navigator.getNavigator().finish();
2413+
// action on current monitor
2414+
this.selectStackSpace(Meta.MotionDirection.DOWN);
2415+
navFinish();
2416+
// switch to target monitor and action mru
2417+
this.switchMonitor(direction, false, true);
2418+
this.selectStackSpace(Meta.MotionDirection.DOWN);
2419+
navFinish();
2420+
2421+
// /**
2422+
// * Fullscreen monitor workaround.
2423+
// * see https://github.com/paperwm/PaperWM/issues/638
2424+
// */
2425+
// this.forEach(space => {
2426+
// space.getWindows().filter(w => w.fullscreen).forEach(w => {
2427+
// animateWindow(w);
2428+
// w.unmake_fullscreen();
2429+
// w.make_fullscreen();
2430+
// showWindow(w);
2431+
// });
2432+
// });
2433+
2434+
// ensure after swapping that the space elements are shown correctly
2435+
this.setSpaceTopbarElementsVisible(true, { force: true });
2436+
}
2437+
2438+
swapMonitor(direction, backDirection, options = {}) {
2439+
const checkIfLast = options.checkIfLast ?? true;
2440+
const warpIfLast = options.warpIfLast ?? true;
2441+
23762442
const monitor = focusMonitor();
23772443
const i = display.get_monitor_neighbor_index(monitor.index, direction);
23782444
if (i === -1)
23792445
return;
23802446

2447+
if (this.lteSpacesThanMonitors(
2448+
(s, m) => Main.notify(
2449+
`PaperWM (cannot swap workspaces)`,
2450+
`You need at least ${m + 1} workspaces to complete this operation.`
2451+
)
2452+
)) {
2453+
return;
2454+
}
2455+
2456+
if (checkIfLast) {
2457+
// check how many spaces are on this monitor
2458+
const numSpaces = [...this].filter(([_monitor, space]) => space?.monitor === monitor).length;
2459+
// if last space on this monitor, then swap
2460+
if (numSpaces <= 1) {
2461+
// focus other monitor for a switchback
2462+
this.switchMonitor(direction, false, false);
2463+
this.swapMonitor(backDirection, direction, {
2464+
checkIfLast: false,
2465+
warpIfLast: false,
2466+
});
2467+
2468+
// now switch monitor with warp since we back-flipped
2469+
this.switchMonitor(direction, false, true);
2470+
return;
2471+
}
2472+
}
2473+
23812474
let navFinish = () => Navigator.getNavigator().finish();
23822475
// action on current monitor
23832476
this.selectStackSpace(Meta.MotionDirection.DOWN);
@@ -2391,20 +2484,20 @@ export const Spaces = class Spaces extends Map {
23912484
this.selectStackSpace(Meta.MotionDirection.DOWN);
23922485
navFinish();
23932486
// final switch with warp
2394-
this.switchMonitor(direction);
2395-
2396-
/**
2397-
* Fullscreen monitor workaround.
2398-
* see https://github.com/paperwm/PaperWM/issues/638
2399-
*/
2400-
this.forEach(space => {
2401-
space.getWindows().filter(w => w.fullscreen).forEach(w => {
2402-
animateWindow(w);
2403-
w.unmake_fullscreen();
2404-
w.make_fullscreen();
2405-
showWindow(w);
2406-
});
2407-
});
2487+
this.switchMonitor(direction, false, warpIfLast);
2488+
2489+
// /**
2490+
// * Fullscreen monitor workaround.
2491+
// * see https://github.com/paperwm/PaperWM/issues/638
2492+
// */
2493+
// this.forEach(space => {
2494+
// space.getWindows().filter(w => w.fullscreen).forEach(w => {
2495+
// animateWindow(w);
2496+
// w.unmake_fullscreen();
2497+
// w.make_fullscreen();
2498+
// showWindow(w);
2499+
// });
2500+
// });
24082501

24092502
// ensure after swapping that the space elements are shown correctly
24102503
this.setSpaceTopbarElementsVisible(true, { force: true });
@@ -2484,11 +2577,18 @@ export const Spaces = class Spaces extends Map {
24842577
let out = [];
24852578
for (let i = 0; i < nWorkspaces; i++) {
24862579
let space = this.spaceOf(workspaceManager.get_workspace_by_index(i));
2487-
if (space.monitor === monitor ||
2488-
(space.length === 0 && this.monitors.get(space.monitor) !== space)) {
2489-
// include workspace if it is the current one
2490-
// or if it is empty and not active on another monitor
2580+
2581+
if (space.monitor === monitor) {
24912582
out.push(space);
2583+
continue;
2584+
}
2585+
2586+
// include workspace if it is the current one
2587+
// or if it is empty and not active on another monitor
2588+
if (space.length === 0 &&
2589+
this.monitors.get(space.monitor) !== space) {
2590+
out.push(space);
2591+
continue;
24922592
}
24932593
}
24942594
return out;
@@ -2852,7 +2952,7 @@ export const Spaces = class Spaces extends Map {
28522952
}
28532953

28542954
let visible = new Map();
2855-
for (let [monitor, space] of this.monitors) {
2955+
for (let [, space] of this.monitors) {
28562956
visible.set(space, true);
28572957
}
28582958

0 commit comments

Comments
 (0)