From 77e78d7d942026b1006edc82c1eb724857b352e8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ole=20J=C3=B8rgen=20Br=C3=B8nner?= Date: Sat, 18 Jan 2020 21:32:24 +0100 Subject: [PATCH 001/104] wip: do DnD outside mutter grab This is much more flexible: we can drag windows across workspaces in the carousel, react on keybindings, scroll, etc. --- grab.js | 132 ++++++++++++++++++++++++++++++++++++++------------- navigator.js | 2 +- tiling.js | 9 +++- 3 files changed, 107 insertions(+), 36 deletions(-) diff --git a/grab.js b/grab.js index 90c74223..68864e58 100644 --- a/grab.js +++ b/grab.js @@ -6,6 +6,8 @@ if (imports.misc.extensionUtils.extensions) { Extension = imports.ui.main.extensionManager.lookup("paperwm@hedning:matrix.org"); } +var Meta = imports.gi.Meta; +var Clutter = imports.gi.Clutter; var St = imports.gi.St; var Main = imports.ui.main; @@ -193,7 +195,7 @@ var MoveGrab = class MoveGrab { this.beginDnD.bind(this) ); - this.scrollAnchor = metaWindow.clone.targetX + space.monitor.x; + this.scrollAnchor = metaWindow.clone.targetX + space.monitor.x; this.signals.connect( metaWindow, 'position-changed', this.positionChanged.bind(this) ); @@ -207,19 +209,40 @@ var MoveGrab = class MoveGrab { if (this.dnd) return; this.dnd = true; + global.display.end_grab_op(global.get_current_time()); let metaWindow = this.window; + let frame = metaWindow.get_frame_rect(); let actor = metaWindow.get_compositor_private(); + let clone = metaWindow.clone; let space = this.initialSpace; let i = space.indexOf(metaWindow); let single = i !== -1 && space[i].length === 1; space.removeWindow(metaWindow); - Tweener.addTween(actor, {time: prefs.animation_time, scale_x: 0.5, scale_y: 0.5}); - this.monitorToZones = createDndZonesForMonitors(); + clone.reparent(Main.uiGroup); + clone.x = frame.x; + clone.y = frame.y; + Tiling.animateWindow(metaWindow); + + Tweener.addTween(clone, {time: prefs.animation_time, scale_x: 0.5, scale_y: 0.5}); let [gx, gy, $] = global.get_pointer(); + + this.pointerOffset = [gx - frame.x, gy - frame.y] + this.signals.connect(global.stage, "motion-event", this.motion.bind(this)); + this.spaceToDndZones = new Map(); + for (let [workspace, space] of Tiling.spaces) { + this.signals.connect(space.background, "motion-event", this.spaceMotion.bind(this, space)); + this.spaceToDndZones.set(space, createDnDZones(space)); + } + this.signals.connect(global.stage, "button-release-event", this.end.bind(this)); + this.signals.connect(global.stage, "button-press-event", this.end.bind(this)); + this.signals.connect(global.stage, "scroll-event", this.scroll.bind(this)); + + this.monitorToZones = createDndZonesForMonitors(); + let monitor = monitorAtPoint(gx, gy); let onSame = space === Tiling.spaces.monitors.get(monitor); @@ -234,21 +257,18 @@ var MoveGrab = class MoveGrab { space.cloneContainer.x = x; } - this.selectDndZone(single && onSame) + let [sx, sy] = space.globalToScroll(gx, gy, true); + this.selectDndZone(space, sx, sy, single && onSame); } - selectDndZone(initial=false) { - let [gx, gy, $] = global.get_pointer(); - - let monitor = monitorAtPoint(gx, gy); - let space = Tiling.spaces.monitors.get(monitor); - let dndZones = this.monitorToZones.get(monitor); - - let [x, y] = space.globalToScroll(gx, gy, true); + spaceMotion(space, background, event) { + let [bx, by] = event.get_coords(); + this.selectDndZone(space, bx - space.targetX, by); + } - let frame = this.window.get_frame_rect(); - let clone = this.window.clone; - [clone.x, clone.y] = space.globalToScroll(frame.x, frame.y); + /** x,y in scroll cooridinates */ + selectDndZone(space, x, y, initial=false) { + let dndZones = this.spaceToDndZones.get(space); let newDndTarget = null; for (let zone of dndZones) { @@ -269,15 +289,15 @@ var MoveGrab = class MoveGrab { this.activateDndTarget(newDndTarget, initial); } } - - positionChanged(metaWindow) { - Utils.assert(metaWindow === this.window); - let [gx, gy, $] = global.get_pointer(); + positionChanged() { + let metaWindow = this.window; + let [gx, gy, $] = global.get_pointer(); if (this.dnd) { - this.selectDndZone(); + print("SHOULD NOT HAPPEND") + // this.selectDndZone(gx, gy); // TODO: dead/obsolete? } else { // Move the window and scroll the space let space = this.initialSpace; let clone = metaWindow.clone; @@ -299,6 +319,49 @@ var MoveGrab = class MoveGrab { } } + scroll(actor, event) { + 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 + if ([Clutter.ScrollDirection.DOWN, Clutter.ScrollDirection.LEFT].includes(dir)) { + dx = -1; + } else { + dx = 1; + } + // 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; + } + print(dx, gx, gy); + let monitor = monitorAtPoint(gx, gy); + let space = Tiling.spaces.monitors.get(monitor); + + if (dx === 1) + space.switchLeft(); + else + space.switchRight(); + + // let speed = 30 + // space.targetX += dx * speed + // space.cloneContainer.x += dx * speed + } + + motion(actor, event) { + let [gx, gy, $] = global.get_pointer(); + let [dx, dy] = this.pointerOffset; + let clone = this.window.clone; + clone.x = gx - dx; + clone.y = gy - dy; + // this.selectDndZone(...event.get_coords()) + // this.selectDndZone(gx, gy) + } + end() { this.signals.destroy(); @@ -311,7 +374,7 @@ var MoveGrab = class MoveGrab { if (this.dnd) { let dndTarget = this.dndTarget; - for (let [monitor, zones] of this.monitorToZones) { + for (let [space, zones] of this.spaceToDndZones) { zones.forEach(zone => zone.actor.destroy()); } @@ -324,14 +387,12 @@ var MoveGrab = class MoveGrab { space.addWindow(metaWindow, ...dndTarget.position); - [clone.x, clone.y] = space.globalToScroll(frame.x, frame.y); - - let [x, y] = space.globalToScroll(gx, gy); - let px = (x - clone.x) / clone.width; - let py = (y - clone.y) / clone.height; - clone.set_pivot_point(px, py); - clone.set_scale(actor.scale_x, actor.scale_y); + // Make sure the window is on the correct workspace. + // If the window is transient this will take care of its parent too. + // metaWindow.change_workspace(space.workspace); + // space.workspace.activate(global.get_current_time()); + [clone.x, clone.y] = space.globalToScroll(clone.x, clone.y); actor.set_scale(1, 1); actor.set_pivot_point(0, 0); @@ -359,6 +420,7 @@ var MoveGrab = class MoveGrab { clone.raise_top() } else { + // metaWindow.move_frame(true, ) Scratch.makeScratch(metaWindow); this.initialSpace.moveDone(); @@ -405,13 +467,14 @@ var MoveGrab = class MoveGrab { this.initialSpace.layout(); - let monitor = monitorAtPoint(gx, gy); - let space = Tiling.spaces.monitors.get(monitor); + // let monitor = monitorAtPoint(gx, gy); + // let space = Tiling.spaces.monitors.get(monitor); - // Make sure the window is on the correct workspace. - // If the window is transient this will take care of its parent too. - metaWindow.change_workspace(space.workspace) - space.workspace.activate(global.get_current_time()); + // // Make sure the window is on the correct workspace. + // // If the window is transient this will take care of its parent too. + // metaWindow.change_workspace(space.workspace) + // space.workspace.activate(global.get_current_time()); + Tiling.inGrab = false; } activateDndTarget(zone, first) { @@ -459,6 +522,7 @@ var MoveGrab = class MoveGrab { var ResizeGrab = class ResizeGrab { constructor(metaWindow, type) { + print("Resize grab begin", metaWindow.title) this.window = metaWindow; this.signals = new Utils.Signals(); diff --git a/navigator.js b/navigator.js index efa9646b..e7d8bb5a 100644 --- a/navigator.js +++ b/navigator.js @@ -297,9 +297,9 @@ var Navigator = class Navigator { } else { // Typically on cancel - the `focus` signal won't run // automatically, so we run it manually + this.space.workspace.activate(global.get_current_time()); Tiling.focus_handler(selected); } - debug('#preview', 'Finish', selected.title); } else { this.space.workspace.activate(global.get_current_time()); } diff --git a/tiling.js b/tiling.js index 093f38a1..fdcf34c7 100644 --- a/tiling.js +++ b/tiling.js @@ -1070,6 +1070,8 @@ box-shadow: 0px 0px 8px 0px rgba(0, 0, 0, .7); this.signals.connect( this.background, 'button-press-event', (actor, event) => { + if (inGrab) + return; let [aX, aY, mask] = global.get_pointer(); let [ok, x, y] = this.actor.transform_stage_point(aX, aY); @@ -1584,6 +1586,11 @@ class Spaces extends Map { let toSpace = this.spaceOf(to); let fromSpace = this.spaceOf(from); + for (let metaWindow of toSpace.getWindows()) { + print("Enforcing workspace memebership", toSpace.name, metaWindow.title) + 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 @@ -2688,7 +2695,7 @@ function grabBegin(metaWindow, type) { } function grabEnd(metaWindow, type) { - if (!inGrab) + if (!inGrab || inGrab.dnd) return; inGrab.end(); From d1e392d313000f50b65f93b5c13eb32b8d3c280f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tor=20Hedin=20Br=C3=B8nner?= Date: Sat, 18 Jan 2020 23:36:41 +0100 Subject: [PATCH 002/104] Use clones global coordinates to insert --- grab.js | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/grab.js b/grab.js index 68864e58..32125648 100644 --- a/grab.js +++ b/grab.js @@ -385,6 +385,8 @@ var MoveGrab = class MoveGrab { if (Scratch.isScratchWindow(metaWindow)) Scratch.unmakeScratch(metaWindow); + // Remember the global coordinates of the clone + let [x, y] = clone.get_position(); space.addWindow(metaWindow, ...dndTarget.position); // Make sure the window is on the correct workspace. @@ -392,7 +394,7 @@ var MoveGrab = class MoveGrab { // metaWindow.change_workspace(space.workspace); // space.workspace.activate(global.get_current_time()); - [clone.x, clone.y] = space.globalToScroll(clone.x, clone.y); + [clone.x, clone.y] = space.globalToScroll(x, y); actor.set_scale(1, 1); actor.set_pivot_point(0, 0); @@ -415,7 +417,7 @@ var MoveGrab = class MoveGrab { } else { space.layout(); } - Tiling.move_to(space, metaWindow, {x: frame.x - space.monitor.x}) + Tiling.move_to(space, metaWindow, {x: x - space.monitor.x}) Tiling.ensureViewport(metaWindow, space); clone.raise_top() From 03682716316ced16e5f3af0f533275353b1a3189 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ole=20J=C3=B8rgen=20Br=C3=B8nner?= Date: Sun, 19 Jan 2020 13:28:05 +0100 Subject: [PATCH 003/104] Ensure all windows in the space belong to the workspace before completing workspace switch This simplifies code moving windows between workspaces without messing with the focus. (since mutter enforce focus_window.workspace == active_workspace) Mainly preparation for DnD fixes in the next commit. --- notes.org | 4 ++++ tiling.js | 6 +++++- 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/notes.org b/notes.org index e83152e1..bd57cbc3 100644 --- a/notes.org +++ b/notes.org @@ -290,3 +290,7 @@ 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()~ diff --git a/tiling.js b/tiling.js index fdcf34c7..aaab0d03 100644 --- a/tiling.js +++ b/tiling.js @@ -1587,7 +1587,11 @@ class Spaces extends Map { let fromSpace = this.spaceOf(from); for (let metaWindow of toSpace.getWindows()) { - print("Enforcing workspace memebership", toSpace.name, metaWindow.title) + // 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); } From 60d1ad7e731f54a32314b762958190ead1ab79e7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ole=20J=C3=B8rgen=20Br=C3=B8nner?= Date: Sun, 19 Jan 2020 12:22:55 +0100 Subject: [PATCH 004/104] Do DnD outside mutter grab - delay workspace change until DnD end (partial Navigator destroy) - sync scratch window frame with clone on grab end Bug in the wip commit fixed: (focus was not always preserved on workspace dnd) - DnD a window - switch to a different workspace using the workspace carousel - drop the window -> the DnD-window loose focus on workspace change (in step 2) Note: Mutter enforce focus_window.workspace === active_workspace, changing focus window if necessary. --- grab.js | 11 ++++------- navigator.js | 35 +++++++++++++++++++++-------------- 2 files changed, 25 insertions(+), 21 deletions(-) diff --git a/grab.js b/grab.js index 32125648..82978696 100644 --- a/grab.js +++ b/grab.js @@ -16,6 +16,7 @@ var Scratch = Extension.imports.scratch; var prefs = Extension.imports.settings.prefs; var Utils = Extension.imports.utils; var Tweener = Utils.tweener; +var Navigator = Extension.imports.navigator; function isInRect(x, y, r) { @@ -389,11 +390,6 @@ var MoveGrab = class MoveGrab { let [x, y] = clone.get_position(); space.addWindow(metaWindow, ...dndTarget.position); - // Make sure the window is on the correct workspace. - // If the window is transient this will take care of its parent too. - // metaWindow.change_workspace(space.workspace); - // space.workspace.activate(global.get_current_time()); - [clone.x, clone.y] = space.globalToScroll(x, y); actor.set_scale(1, 1); @@ -411,7 +407,7 @@ var MoveGrab = class MoveGrab { }); space.targetX = space.cloneContainer.x; - space.selectedWindow = metaWindow + space.selectedWindow = metaWindow; if (dndTarget.position) { space.layout(true, {customAllocators: {[dndTarget.position[0]]: Tiling.allocateEqualHeight}}); } else { @@ -422,7 +418,7 @@ var MoveGrab = class MoveGrab { clone.raise_top() } else { - // metaWindow.move_frame(true, ) + metaWindow.move_frame(true, clone.x, clone.y); Scratch.makeScratch(metaWindow); this.initialSpace.moveDone(); @@ -477,6 +473,7 @@ var MoveGrab = class MoveGrab { // metaWindow.change_workspace(space.workspace) // space.workspace.activate(global.get_current_time()); Tiling.inGrab = false; + Navigator.getNavigator().finish(); } activateDndTarget(zone, first) { diff --git a/navigator.js b/navigator.js index e7d8bb5a..5d7b478a 100644 --- a/navigator.js +++ b/navigator.js @@ -253,9 +253,10 @@ var Navigator = class Navigator { if (force) { this.space.monitor.clickOverlay.hide(); - this.space = Tiling.spaces.selectedSpace; } + this.space = Tiling.spaces.selectedSpace; + let from = this.from; let selected = this.space.selectedWindow; if(!this.was_accepted) { @@ -286,22 +287,28 @@ var Navigator = class Navigator { 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(); - selected.foreach_transient(mw => hasFocus = mw.has_focus() || hasFocus); - if (!hasFocus) { - Main.activateWindow(selected); + + if (Tiling.inGrab && Tiling.inGrab.dnd) { + Tiling.spaces.monitors.set(this.monitor, this.space); + Tiling.spaces.animateToSpace(this.space); + } else { + if (selected && + (!force || + !(display.focus_window && display.focus_window.is_on_all_workspaces())) ) { + + let hasFocus = selected.has_focus(); + selected.foreach_transient(mw => hasFocus = mw.has_focus() || hasFocus); + if (!hasFocus) { + Main.activateWindow(selected); + } else { + // Typically on cancel - the `focus` signal won't run + // automatically, so we run it manually + this.space.workspace.activate(global.get_current_time()); + Tiling.focus_handler(selected); + } } else { - // Typically on cancel - the `focus` signal won't run - // automatically, so we run it manually this.space.workspace.activate(global.get_current_time()); - Tiling.focus_handler(selected); } - } else { - this.space.workspace.activate(global.get_current_time()); } TopBar.fixTopBar(); From 86a91c232e789e949a910e20a24638f6195e87d7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ole=20J=C3=B8rgen=20Br=C3=B8nner?= Date: Sun, 19 Jan 2020 13:51:41 +0100 Subject: [PATCH 005/104] utils: less verbose trace functions --- utils.js | 61 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 61 insertions(+) diff --git a/utils.js b/utils.js index ae4c88a7..a9ce90a2 100644 --- a/utils.js +++ b/utils.js @@ -346,3 +346,64 @@ var tweener = { return actor.get_transition('x') || actor.get_transition('y') || actor.get_transition('scale-x') || actor.get_transition('scale-x'); } }; + + +function trace(topic, ...args) { + windowTrace(topic, ...args); +} + +let existingWindows = new Set(); + +function windowTrace(topic, metaWindow, ...rest) { + if (existingWindows.has(metaWindow)) { + return; + } + + if (!topic.match(/.*/)) { + return; + } + + log(topic, infoMetaWindow(metaWindow).join("\n"), ...rest.join("\n")); +} + +function infoMetaWindow(metaWindow) { + let id = metaWindow.toString().split(" ")[4]; + let trace = shortTrace(3).join(" < "); + let info = [ + `(win: ${id}) ${trace}`, + `Title: ${metaWindow.title}`, + ]; + if (!metaWindow.window_type === Meta.WindowType.NORMAL) { + info.push(`Type: ${ppEnumValue(metaWindow.window_type, Meta.WindowType)}`); + } + if (!metaWindow.get_compositor_private()) { + info.push(`- no actor`); + } + if (metaWindow.is_on_all_workspaces()) { + info.push(`- is_on_all_workspaces`); + } + if (metaWindow.above) { + info.push(`- above`); + } + if (Extension.imports.scratch.isScratchWindow(metaWindow)) { + info.push(`- scratch`); + } + return info; +} + +function shortTrace(skip=0) { + let trace = new Error().stack.split("\n").map(s => { + let words = s.split(/[@/]/) + let cols = s.split(":") + let ln = parseInt(cols[2]) + if (ln === null) + ln = "?" + + return [words[0], ln] + }) + trace = trace.filter(([f, ln]) => f !== "dynamic_function_ref").map(([f, ln]) => f === "" ? "?" : f+":"+ln); + return trace.slice(skip+1, skip+5); +} + + +// Meta.remove_verbose_topic(Meta.DebugTopic.FOCUS) From 851157de15a9f1da2315cbd719915e94627c6e70 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ole=20J=C3=B8rgen=20Br=C3=B8nner?= Date: Tue, 21 Jan 2020 19:24:34 +0100 Subject: [PATCH 006/104] DnD mouse cursor --- grab.js | 2 ++ kludges.js | 5 +++++ 2 files changed, 7 insertions(+) diff --git a/grab.js b/grab.js index 82978696..a320d669 100644 --- a/grab.js +++ b/grab.js @@ -211,6 +211,7 @@ var MoveGrab = class MoveGrab { return; this.dnd = true; global.display.end_grab_op(global.get_current_time()); + global.display.set_cursor(Meta.Cursor.MOVE_OR_RESIZE_WINDOW); let metaWindow = this.window; let frame = metaWindow.get_frame_rect(); @@ -474,6 +475,7 @@ var MoveGrab = class MoveGrab { // space.workspace.activate(global.get_current_time()); Tiling.inGrab = false; Navigator.getNavigator().finish(); + global.display.set_cursor(Meta.Cursor.DEFAULT); } activateDndTarget(zone, first) { diff --git a/kludges.js b/kludges.js index da2c92c3..737be313 100644 --- a/kludges.js +++ b/kludges.js @@ -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; From 3752114f62b5166ad418f5c888faab6a50a6430b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tor=20Hedin=20Br=C3=B8nner?= Date: Fri, 24 Jan 2020 17:45:49 +0100 Subject: [PATCH 007/104] wip: always use fake grab --- grab.js | 169 ++++++++++++++++++++++++++++++++---------------------- tiling.js | 2 +- 2 files changed, 102 insertions(+), 69 deletions(-) diff --git a/grab.js b/grab.js index a320d669..e6754ccd 100644 --- a/grab.js +++ b/grab.js @@ -168,79 +168,84 @@ var MoveGrab = class MoveGrab { this.window = metaWindow; this.type = type; this.signals = new Utils.Signals(); + this.grabbed = false; this.initialSpace = Tiling.spaces.spaceOfWindow(metaWindow); } begin() { + log(`begin`) + if (this.grabbed) + return; + this.grabbed = true + global.display.end_grab_op(global.get_current_time()); + global.display.set_cursor(Meta.Cursor.MOVE_OR_RESIZE_WINDOW); + let metaWindow = this.window; + let actor = metaWindow.get_compositor_private(); + let clone = metaWindow.clone; + let space = this.initialSpace; let frame = metaWindow.get_frame_rect(); - let space = Tiling.spaces.spaceOfWindow(metaWindow); - - this.initialY = frame.y; - let actor = metaWindow.get_compositor_private(); + this.initialY = clone.y; let [gx, gy, $] = global.get_pointer(); + this.pointerOffset = [gx - frame.x, gy - frame.y] + let px = (gx - actor.x) / actor.width; let py = (gy - actor.y) / actor.height; actor.set_pivot_point(px, py); - let clone = metaWindow.clone; let [x, y] = space.globalToScroll(gx, gy); px = (x - clone.x) / clone.width; py = (y - clone.y) / clone.height; clone.set_pivot_point(px, py); + this.signals.connect(global.stage, "button-release-event", this.end.bind(this)); + this.signals.connect(global.stage, "button-press-event", this.end.bind(this)); + this.signals.connect(global.stage, "motion-event", this.motion.bind(this)); this.signals.connect( global.screen || global.display, "window-entered-monitor", this.beginDnD.bind(this) ); - this.scrollAnchor = metaWindow.clone.targetX + space.monitor.x; - this.signals.connect( - metaWindow, 'position-changed', this.positionChanged.bind(this) - ); + this.scrollAnchor = x; space.startAnimate(); // Make sure the window actor is visible - Tiling.showWindow(metaWindow); + Tiling.animateWindow(metaWindow); Tweener.removeTweens(space.cloneContainer); } beginDnD() { if (this.dnd) return; + log(`beginDND`) this.dnd = true; - global.display.end_grab_op(global.get_current_time()); - global.display.set_cursor(Meta.Cursor.MOVE_OR_RESIZE_WINDOW); - let metaWindow = this.window; - let frame = metaWindow.get_frame_rect(); + // let frame = metaWindow.get_frame_rect(); let actor = metaWindow.get_compositor_private(); let clone = metaWindow.clone; let space = this.initialSpace; + // FIXME + let frame = { + x: clone.x + space.cloneContainer.x + space.monitor.x, + y: clone.y + space.cloneContainer.y + space.monitor.y, + width: clone.width, + height: clone.height + } + let i = space.indexOf(metaWindow); let single = i !== -1 && space[i].length === 1; - space.removeWindow(metaWindow); - - clone.reparent(Main.uiGroup); clone.x = frame.x; clone.y = frame.y; - Tiling.animateWindow(metaWindow); + space.removeWindow(metaWindow); + clone.reparent(Main.uiGroup); Tweener.addTween(clone, {time: prefs.animation_time, scale_x: 0.5, scale_y: 0.5}); let [gx, gy, $] = global.get_pointer(); - this.pointerOffset = [gx - frame.x, gy - frame.y] - this.signals.connect(global.stage, "motion-event", this.motion.bind(this)); this.spaceToDndZones = new Map(); - for (let [workspace, space] of Tiling.spaces) { - this.signals.connect(space.background, "motion-event", this.spaceMotion.bind(this, space)); - this.spaceToDndZones.set(space, createDnDZones(space)); - } - this.signals.connect(global.stage, "button-release-event", this.end.bind(this)); - this.signals.connect(global.stage, "button-press-event", this.end.bind(this)); this.signals.connect(global.stage, "scroll-event", this.scroll.bind(this)); this.monitorToZones = createDndZonesForMonitors(); @@ -260,6 +265,11 @@ var MoveGrab = class MoveGrab { } let [sx, sy] = space.globalToScroll(gx, gy, true); + + for (let [workspace, space] of Tiling.spaces) { + this.signals.connect(space.background, "motion-event", this.spaceMotion.bind(this, space)); + this.spaceToDndZones.set(space, createDnDZones(space)); + } this.selectDndZone(space, sx, sy, single && onSame); } @@ -292,34 +302,37 @@ var MoveGrab = class MoveGrab { } } - positionChanged() { - let metaWindow = this.window; - - let [gx, gy, $] = global.get_pointer(); - - if (this.dnd) { - print("SHOULD NOT HAPPEND") - // this.selectDndZone(gx, gy); // TODO: dead/obsolete? - } else { // Move the window and scroll the space - let space = this.initialSpace; - let clone = metaWindow.clone; - let frame = metaWindow.get_frame_rect(); - space.targetX = frame.x - this.scrollAnchor; - space.cloneContainer.x = space.targetX; - - const threshold = 300; - const dy = Math.min(threshold, Math.abs(frame.y - this.initialY)); - let s = 1 - Math.pow(dy / 500, 3); - let actor = metaWindow.get_compositor_private(); - actor.set_scale(s, s); - clone.set_scale(s, s); - [clone.x, clone.y] = space.globalToScroll(frame.x, frame.y); - - if (dy >= threshold) { - this.beginDnD(); - } - } - } + // positionChanged() { + // let metaWindow = this.window; + + // let [gx, gy, $] = global.get_pointer(); + + // if (this.dnd) { + // print("SHOULD NOT HAPPEND") + // // this.selectDndZone(gx, gy); // TODO: dead/obsolete? + // } else { // Move the window and scroll the space + // let space = this.initialSpace; + // let clone = metaWindow.clone; + // let frame = metaWindow.get_frame_rect(); + // // scrollAnchhor = gx - space.monitor.x - space.cloneContainer.x + // // scrollAnchhor - gx + space.monitor.x = - space.cloneContainer.x + + // space.targetX = gx - space.monitor.x - this.scrollAnchhor; + // space.cloneContainer.x = space.targetX; + + // const threshold = 300; + // const dy = Math.min(threshold, Math.abs(frame.y - this.initialY)); + // let s = 1 - Math.pow(dy / 500, 3); + // let actor = metaWindow.get_compositor_private(); + // actor.set_scale(s, s); + // clone.set_scale(s, s); + // [clone.x, clone.y] = space.globalToScroll(frame.x, frame.y); + + // if (dy >= threshold) { + // this.beginDnD(); + // } + // } + // } scroll(actor, event) { let dir = event.get_scroll_direction(); @@ -355,16 +368,37 @@ var MoveGrab = class MoveGrab { } motion(actor, event) { - let [gx, gy, $] = global.get_pointer(); + let metaWindow = this.window; + let [gx, gy] = event.get_coords(); let [dx, dy] = this.pointerOffset; - let clone = this.window.clone; - clone.x = gx - dx; - clone.y = gy - dy; - // this.selectDndZone(...event.get_coords()) - // this.selectDndZone(gx, gy) + let clone = metaWindow.clone; + + if (this.dnd) { + clone.x = gx - dx; + clone.y = gy - dy; + } else { + let space = this.initialSpace; + let clone = metaWindow.clone; + space.targetX = gx - space.monitor.x - this.scrollAnchor; + space.cloneContainer.x = space.targetX; + + clone.y = gy - dy + space.monitor.y; + + const threshold = 300; + dy = Math.min(threshold, Math.abs(clone.y - this.initialY)); + let s = 1 - Math.pow(dy / 500, 3); + let actor = metaWindow.get_compositor_private(); + actor.set_scale(s, s); + clone.set_scale(s, s); + + if (dy >= threshold) { + this.beginDnD(); + } + } } end() { + log(`end`) this.signals.destroy(); let metaWindow = this.window; @@ -396,7 +430,7 @@ var MoveGrab = class MoveGrab { actor.set_scale(1, 1); actor.set_pivot_point(0, 0); - Tiling.animateWindow(metaWindow); + // Tiling.animateWindow(metaWindow); Tweener.addTween(clone, { time: prefs.animation_time, scale_x: 1, @@ -438,10 +472,6 @@ var MoveGrab = class MoveGrab { } else if (this.initialSpace.indexOf(metaWindow) !== -1){ let space = this.initialSpace; space.targetX = space.cloneContainer.x; - clone.targetX = frame.x - space.monitor.x - space.targetX; - clone.targetY = frame.y - space.monitor.y; - clone.set_position(clone.targetX, - clone.targetY); actor.set_scale(1, 1); actor.set_pivot_point(0, 0); @@ -492,10 +522,13 @@ var MoveGrab = class MoveGrab { params.height = zone.actor.height params.y = zone.actor.y - let actor = this.window.get_compositor_private(); + let clone = this.window.clone; let space = zone.space; - zone.actor.set_position(...space.globalToScroll(...actor.get_transformed_position())) - zone.actor.set_size(...actor.get_transformed_size()) + let [x, y] = clone.get_transformed_position() + log(...clone.get_transformed_position(), clone.get_parent(), clone.x, clone.y, space.targetX) + log(clone.get_transformed_size()) + zone.actor.set_position(...space.globalToScroll(x, y)) + zone.actor.set_size(...clone.get_transformed_size()) } else { zone.actor[zone.sizeProp] = 0; zone.actor[zone.originProp] = zone.center; diff --git a/tiling.js b/tiling.js index aaab0d03..45360e4f 100644 --- a/tiling.js +++ b/tiling.js @@ -2699,7 +2699,7 @@ function grabBegin(metaWindow, type) { } function grabEnd(metaWindow, type) { - if (!inGrab || inGrab.dnd) + if (!inGrab || inGrab.dnd || inGrab.grabbed) return; inGrab.end(); From c6ad376764635d3b1022434aefe388a81d35c113 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tor=20Hedin=20Br=C3=B8nner?= Date: Fri, 24 Jan 2020 18:24:28 +0100 Subject: [PATCH 008/104] wip: grab everywhere --- grab.js | 14 ++++++++------ tiling.js | 10 ++++++---- 2 files changed, 14 insertions(+), 10 deletions(-) diff --git a/grab.js b/grab.js index e6754ccd..b3c079d2 100644 --- a/grab.js +++ b/grab.js @@ -201,7 +201,6 @@ var MoveGrab = class MoveGrab { clone.set_pivot_point(px, py); this.signals.connect(global.stage, "button-release-event", this.end.bind(this)); - this.signals.connect(global.stage, "button-press-event", this.end.bind(this)); this.signals.connect(global.stage, "motion-event", this.motion.bind(this)); this.signals.connect( global.screen || global.display, "window-entered-monitor", @@ -245,9 +244,10 @@ var MoveGrab = class MoveGrab { let [gx, gy, $] = global.get_pointer(); - this.spaceToDndZones = new Map(); + this.signals.connect(global.stage, "button-press-event", this.end.bind(this)); this.signals.connect(global.stage, "scroll-event", this.scroll.bind(this)); + this.spaceToDndZones = new Map(); this.monitorToZones = createDndZonesForMonitors(); let monitor = monitorAtPoint(gx, gy); @@ -425,6 +425,8 @@ var MoveGrab = class MoveGrab { let [x, y] = clone.get_position(); space.addWindow(metaWindow, ...dndTarget.position); + this.window = null; + [clone.x, clone.y] = space.globalToScroll(x, y); actor.set_scale(1, 1); @@ -524,10 +526,10 @@ var MoveGrab = class MoveGrab { let clone = this.window.clone; let space = zone.space; - let [x, y] = clone.get_transformed_position() - log(...clone.get_transformed_position(), clone.get_parent(), clone.x, clone.y, space.targetX) - log(clone.get_transformed_size()) - zone.actor.set_position(...space.globalToScroll(x, y)) + // let [x, y] = clone.get_transformed_position() + // log(...clone.get_transformed_position(), clone.get_parent(), clone.x, clone.y, space.targetX) + // log(clone.get_transformed_size()) + zone.actor.set_position(...clone.get_transformed_position()) zone.actor.set_size(...clone.get_transformed_size()) } else { zone.actor[zone.sizeProp] = 0; diff --git a/tiling.js b/tiling.js index 45360e4f..9b56c92a 100644 --- a/tiling.js +++ b/tiling.js @@ -1070,18 +1070,20 @@ box-shadow: 0px 0px 8px 0px rgba(0, 0, 0, .7); this.signals.connect( this.background, 'button-press-event', (actor, event) => { - if (inGrab) + if (inGrab) { return; + } let [aX, aY, mask] = global.get_pointer(); let [ok, x, y] = this.actor.transform_stage_point(aX, aY); 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); + inGrab.begin(); } - spaces.selectedSpace = this; - nav.finish(); + // spaces.selectedSpace = this; + // nav.finish(); }); this.signals.connect( From 095cc72327ac709747b6fe3821ca5a86cca47abf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tor=20Hedin=20Br=C3=B8nner?= Date: Fri, 24 Jan 2020 20:27:55 +0100 Subject: [PATCH 009/104] better pointeroffset --- grab.js | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/grab.js b/grab.js index b3c079d2..2aff8515 100644 --- a/grab.js +++ b/grab.js @@ -189,16 +189,20 @@ var MoveGrab = class MoveGrab { this.initialY = clone.y; let [gx, gy, $] = global.get_pointer(); - this.pointerOffset = [gx - frame.x, gy - frame.y] let px = (gx - actor.x) / actor.width; let py = (gy - actor.y) / actor.height; actor.set_pivot_point(px, py); - let [x, y] = space.globalToScroll(gx, gy); + let [ok, x, y] = space.cloneContainer.transform_stage_point(gx, gy); px = (x - clone.x) / clone.width; py = (y - clone.y) / clone.height; clone.set_pivot_point(px, py); + if (clone.get_parent()) { + this.pointerOffset = [x - clone.x, y - clone.y]; + } else { + this.pointerOffset = [gx - frame.x, gy - frame.y]; + } this.signals.connect(global.stage, "button-release-event", this.end.bind(this)); this.signals.connect(global.stage, "motion-event", this.motion.bind(this)); From 371453cec03cd25a6daae764f5ad3387ac0a4080 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tor=20Hedin=20Br=C3=B8nner?= Date: Sat, 25 Jan 2020 01:12:48 +0100 Subject: [PATCH 010/104] fix grab y position --- grab.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/grab.js b/grab.js index 2aff8515..f5732041 100644 --- a/grab.js +++ b/grab.js @@ -386,7 +386,8 @@ var MoveGrab = class MoveGrab { space.targetX = gx - space.monitor.x - this.scrollAnchor; space.cloneContainer.x = space.targetX; - clone.y = gy - dy + space.monitor.y; + let [ok, x, y] = space.cloneContainer.transform_stage_point(gx, gy); + clone.y = y - dy; const threshold = 300; dy = Math.min(threshold, Math.abs(clone.y - this.initialY)); From 85c0c9d48fa28aacac62a5aeb3039c654e15b115 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tor=20Hedin=20Br=C3=B8nner?= Date: Sat, 25 Jan 2020 01:26:29 +0100 Subject: [PATCH 011/104] fix some coordinate transforms --- grab.js | 111 +++++++++++++++++++++++++++++------------------------- tiling.js | 41 ++++++++++++++++++-- 2 files changed, 96 insertions(+), 56 deletions(-) diff --git a/grab.js b/grab.js index f5732041..28b75197 100644 --- a/grab.js +++ b/grab.js @@ -164,13 +164,13 @@ function createDnDZones(space) { var MoveGrab = class MoveGrab { - constructor(metaWindow, type) { + constructor(metaWindow, type, space) { this.window = metaWindow; this.type = type; this.signals = new Utils.Signals(); this.grabbed = false; - this.initialSpace = Tiling.spaces.spaceOfWindow(metaWindow); + this.initialSpace = space || Tiling.spaces.spaceOfWindow(metaWindow); } begin() { @@ -222,42 +222,46 @@ var MoveGrab = class MoveGrab { if (this.dnd) return; log(`beginDND`) + global.display.set_cursor(Meta.Cursor.MOVE_OR_RESIZE_WINDOW); this.dnd = true; let metaWindow = this.window; - // let frame = metaWindow.get_frame_rect(); let actor = metaWindow.get_compositor_private(); let clone = metaWindow.clone; let space = this.initialSpace; - - // FIXME - let frame = { - x: clone.x + space.cloneContainer.x + space.monitor.x, - y: clone.y + space.cloneContainer.y + space.monitor.y, - width: clone.width, - height: clone.height + let frame; + if (clone.get_parent()) { + // let point = clone.apply_transform_to_point(new Clutter.Vertex({x: 0, y: 0})); + let point = space.cloneContainer.apply_transform_to_point(new Clutter.Vertex({x: clone.x, y: clone.y})); + frame = {x: point.x, y: point.y, + width: clone.width, + height: clone.height + } + } else { + frame = metaWindow.get_frame_rect(); } let i = space.indexOf(metaWindow); let single = i !== -1 && space[i].length === 1; - clone.x = frame.x; - clone.y = frame.y; space.removeWindow(metaWindow); clone.reparent(Main.uiGroup); + clone.x = frame.x; + clone.y = frame.y; + let newScale = clone.scale_x*space.actor.scale_x; + clone.set_scale(newScale, newScale); Tweener.addTween(clone, {time: prefs.animation_time, scale_x: 0.5, scale_y: 0.5}); - let [gx, gy, $] = global.get_pointer(); - this.signals.connect(global.stage, "button-press-event", this.end.bind(this)); - this.signals.connect(global.stage, "scroll-event", this.scroll.bind(this)); this.spaceToDndZones = new Map(); this.monitorToZones = createDndZonesForMonitors(); + let [gx, gy, $] = global.get_pointer(); let monitor = monitorAtPoint(gx, gy); - let onSame = space === Tiling.spaces.monitors.get(monitor); - let x = gx - space.monitor.x; + let onSame = monitor === space.monitor; + + let [ok, x, y] = space.actor.transform_stage_point(gx, gy); if (onSame && single && space[i]) { Tiling.move_to(space, space[i][0], { x: x + prefs.window_gap/2 }); } else if (onSame && single && space[i-1]) { @@ -338,38 +342,38 @@ var MoveGrab = class MoveGrab { // } // } - scroll(actor, event) { - 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 - if ([Clutter.ScrollDirection.DOWN, Clutter.ScrollDirection.LEFT].includes(dir)) { - dx = -1; - } else { - dx = 1; - } - // 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; - } - print(dx, gx, gy); - let monitor = monitorAtPoint(gx, gy); - let space = Tiling.spaces.monitors.get(monitor); - - if (dx === 1) - space.switchLeft(); - else - space.switchRight(); + // scroll(space, actor, event) { + // 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; + // } + // print(dx, gx, gy); + + // switch (dir) { + // case Clutter.ScrollDirection.LEFT: + // case Clutter.ScrollDirection.DOWN: + // space.switchLeft(); + // break; + // case Clutter.ScrollDirection.RIGHT: + // case Clutter.ScrollDirection.UP: + // space.switchRight(); + // break; + // } - // let speed = 30 - // space.targetX += dx * speed - // space.cloneContainer.x += dx * speed - } + // // let speed = 30 + // // space.targetX += dx * speed + // // space.cloneContainer.x += dx * speed + // } motion(actor, event) { let metaWindow = this.window; @@ -430,9 +434,11 @@ var MoveGrab = class MoveGrab { let [x, y] = clone.get_position(); space.addWindow(metaWindow, ...dndTarget.position); - this.window = null; - [clone.x, clone.y] = space.globalToScroll(x, y); + let [ok, sx, sy] = space.cloneContainer.transform_stage_point(x, y); + [clone.x, clone.y] = [sx, sy]; + let newScale = clone.scale_x/space.actor.scale_x; + clone.set_scale(newScale, newScale); actor.set_scale(1, 1); actor.set_pivot_point(0, 0); @@ -532,9 +538,10 @@ var MoveGrab = class MoveGrab { let clone = this.window.clone; let space = zone.space; // let [x, y] = clone.get_transformed_position() - // log(...clone.get_transformed_position(), clone.get_parent(), clone.x, clone.y, space.targetX) + log(...clone.get_transformed_position(), clone.get_parent(), clone.x, clone.y, space.targetX) // log(clone.get_transformed_size()) - zone.actor.set_position(...clone.get_transformed_position()) + let [ok, x, y] = space.cloneContainer.transform_stage_point(...clone.get_transformed_position()) + zone.actor.set_position(x, y) zone.actor.set_size(...clone.get_transformed_size()) } else { zone.actor[zone.sizeProp] = 0; diff --git a/tiling.js b/tiling.js index 9b56c92a..4a72dd0d 100644 --- a/tiling.js +++ b/tiling.js @@ -623,6 +623,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') @@ -1073,19 +1075,50 @@ box-shadow: 0px 0px 8px 0px rgba(0, 0, 0, .7); if (inGrab) { return; } - let [aX, aY, mask] = global.get_pointer(); - let [ok, x, y] = - this.actor.transform_stage_point(aX, aY); + let [x, y] = event.get_coords(); let windowAtPoint = !Gestures.gliding && this.getWindowAtPoint(x, y); if (windowAtPoint) { ensureViewport(windowAtPoint, this); - inGrab = new Extension.imports.grab.MoveGrab(windowAtPoint, Meta.GrabOp.MOVING); + inGrab = new Extension.imports.grab.MoveGrab(windowAtPoint, Meta.GrabOp.MOVING, this); inGrab.begin(); } // spaces.selectedSpace = this; // nav.finish(); }); + this.signals.connect( + this.background, 'scroll-event', + (actor, event) => { + 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; + } + print(dx, gx, gy); + + switch (dir) { + case Clutter.ScrollDirection.LEFT: + case Clutter.ScrollDirection.DOWN: + this.switchLeft(); + break; + case Clutter.ScrollDirection.RIGHT: + case Clutter.ScrollDirection.UP: + this.switchRight(); + break; + } + // spaces.selectedSpace = this; + // nav.finish(); + }); + this.signals.connect( this.background, 'captured-event', Gestures.horizontalScroll.bind(this)); From 685c1ea20fad71382a40dddd9ebe52ea2cd9183e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tor=20Hedin=20Br=C3=B8nner?= Date: Sat, 25 Jan 2020 01:26:45 +0100 Subject: [PATCH 012/104] guard --- stackoverlay.js | 2 ++ 1 file changed, 2 insertions(+) diff --git a/stackoverlay.js b/stackoverlay.js index 883ad595..508e8d9e 100644 --- a/stackoverlay.js +++ b/stackoverlay.js @@ -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(); From 01d9ce435de9944a3fca584d6584f7be625787cd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tor=20Hedin=20Br=C3=B8nner?= Date: Sat, 25 Jan 2020 01:27:08 +0100 Subject: [PATCH 013/104] navigator: start dnd if in grab --- navigator.js | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/navigator.js b/navigator.js index 5d7b478a..c7f0e440 100644 --- a/navigator.js +++ b/navigator.js @@ -197,6 +197,10 @@ var Navigator = class Navigator { this._block = Main.wm._blockAnimations; Main.wm._blockAnimations = true; // Meta.disable_unredirect_for_screen(screen); + // + if (Tiling.inGrab && Tiling.inGrab.window) { + Tiling.inGrab.beginDnD(); + } this.space = Tiling.spaces.spaceOf(workspaceManager.get_active_workspace()); From 049c1331fbbb2859284d40a03625a78b490c5b1a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tor=20Hedin=20Br=C3=B8nner?= Date: Sat, 25 Jan 2020 15:08:27 +0100 Subject: [PATCH 014/104] isWindowAtPoint: use correct clone coordinate --- tiling.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tiling.js b/tiling.js index 4a72dd0d..8cf502a8 100644 --- a/tiling.js +++ b/tiling.js @@ -541,7 +541,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; } From 369d3c99a679e5a68275bcbced167c16d8658ba8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tor=20Hedin=20Br=C3=B8nner?= Date: Sat, 25 Jan 2020 15:09:24 +0100 Subject: [PATCH 015/104] get_coords doesn't take scaling into account Use get_pointer, and transform_stage_point for to get correct coordinates... --- grab.js | 12 +++++++++--- tiling.js | 3 ++- 2 files changed, 11 insertions(+), 4 deletions(-) diff --git a/grab.js b/grab.js index 28b75197..222bb633 100644 --- a/grab.js +++ b/grab.js @@ -282,7 +282,13 @@ var MoveGrab = class MoveGrab { } spaceMotion(space, background, event) { - let [bx, by] = event.get_coords(); + e = event; + // e.get_coords() + // let p = new Clutter.Point() + // e.get_position(p) + let [x, y] = event.get_coords(); + let [gx, gy, $] = global.get_pointer(); + let [ok, bx, by] = space.actor.transform_stage_point(gx, gy); this.selectDndZone(space, bx - space.targetX, by); } @@ -387,10 +393,10 @@ var MoveGrab = class MoveGrab { } else { let space = this.initialSpace; let clone = metaWindow.clone; - space.targetX = gx - space.monitor.x - this.scrollAnchor; + let [ok, x, y] = space.actor.transform_stage_point(gx, gy); + space.targetX = x - space.monitor.x - this.scrollAnchor; space.cloneContainer.x = space.targetX; - let [ok, x, y] = space.cloneContainer.transform_stage_point(gx, gy); clone.y = y - dy; const threshold = 300; diff --git a/tiling.js b/tiling.js index 8cf502a8..808b31cf 100644 --- a/tiling.js +++ b/tiling.js @@ -1075,7 +1075,8 @@ box-shadow: 0px 0px 8px 0px rgba(0, 0, 0, .7); if (inGrab) { return; } - let [x, y] = event.get_coords(); + 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); if (windowAtPoint) { ensureViewport(windowAtPoint, this); From df0cdbc5bf3e0a8ee0d0a040ddcd15e244f57741 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tor=20Hedin=20Br=C3=B8nner?= Date: Sat, 25 Jan 2020 20:37:30 +0100 Subject: [PATCH 016/104] position clone correctly when popped out of scaled space --- grab.js | 18 ++++-------------- 1 file changed, 4 insertions(+), 14 deletions(-) diff --git a/grab.js b/grab.js index 222bb633..614bfdaa 100644 --- a/grab.js +++ b/grab.js @@ -228,24 +228,15 @@ var MoveGrab = class MoveGrab { let actor = metaWindow.get_compositor_private(); let clone = metaWindow.clone; let space = this.initialSpace; - let frame; - if (clone.get_parent()) { - // let point = clone.apply_transform_to_point(new Clutter.Vertex({x: 0, y: 0})); - let point = space.cloneContainer.apply_transform_to_point(new Clutter.Vertex({x: clone.x, y: clone.y})); - frame = {x: point.x, y: point.y, - width: clone.width, - height: clone.height - } - } else { - frame = metaWindow.get_frame_rect(); - } let i = space.indexOf(metaWindow); let single = i !== -1 && space[i].length === 1; space.removeWindow(metaWindow); clone.reparent(Main.uiGroup); - clone.x = frame.x; - clone.y = frame.y; + let [gx, gy, $] = global.get_pointer(); + let [dx, dy] = this.pointerOffset; + clone.x = gx - dx; + clone.y = gy - dy; let newScale = clone.scale_x*space.actor.scale_x; clone.set_scale(newScale, newScale); @@ -256,7 +247,6 @@ var MoveGrab = class MoveGrab { this.spaceToDndZones = new Map(); this.monitorToZones = createDndZonesForMonitors(); - let [gx, gy, $] = global.get_pointer(); let monitor = monitorAtPoint(gx, gy); let onSame = monitor === space.monitor; From b75a49c2bb721f306237028490f9b93891fb4333 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tor=20Hedin=20Br=C3=B8nner?= Date: Sat, 25 Jan 2020 20:39:45 +0100 Subject: [PATCH 017/104] navigator: always start dnd if grab is active --- navigator.js | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/navigator.js b/navigator.js index c7f0e440..a4defb32 100644 --- a/navigator.js +++ b/navigator.js @@ -156,7 +156,11 @@ var ActionDispatcher = class { if (!metaWindow && (action.options.mutterFlags & Meta.KeyBindingFlags.PER_WINDOW)) { return; } - if (action.options.opensMinimap) { + if (Tiling.inGrab && !Tiling.inGrab.dnd && Tiling.inGrab.window) { + Tiling.inGrab.beginDnD(); + } + + if (!Tiling.inGrab && action.options.opensMinimap) { this.navigator._showMinimap(space); } action.handler(metaWindow, space, {navigator: this.navigator}); @@ -197,11 +201,6 @@ var Navigator = class Navigator { this._block = Main.wm._blockAnimations; Main.wm._blockAnimations = true; // Meta.disable_unredirect_for_screen(screen); - // - if (Tiling.inGrab && Tiling.inGrab.window) { - Tiling.inGrab.beginDnD(); - } - this.space = Tiling.spaces.spaceOf(workspaceManager.get_active_workspace()); this._startWindow = this.space.selectedWindow; From 06d1a765f968cac9d8797f78f11e6f4e835a7679 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tor=20Hedin=20Br=C3=B8nner?= Date: Sat, 25 Jan 2020 20:40:11 +0100 Subject: [PATCH 018/104] space button-press: navigate to space if clicking on background --- tiling.js | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/tiling.js b/tiling.js index 808b31cf..c9dda6a9 100644 --- a/tiling.js +++ b/tiling.js @@ -1082,9 +1082,10 @@ box-shadow: 0px 0px 8px 0px rgba(0, 0, 0, .7); ensureViewport(windowAtPoint, this); inGrab = new Extension.imports.grab.MoveGrab(windowAtPoint, Meta.GrabOp.MOVING, this); inGrab.begin(); + } else { + spaces.selectedSpace = this; + Navigator.getNavigator().finish(); } - // spaces.selectedSpace = this; - // nav.finish(); }); this.signals.connect( From 2ebc2a0efb0b755e91d336e9df1affb5835f1b2f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tor=20Hedin=20Br=C3=B8nner?= Date: Sat, 25 Jan 2020 20:42:05 +0100 Subject: [PATCH 019/104] grab: remove positionChanged --- grab.js | 32 -------------------------------- 1 file changed, 32 deletions(-) diff --git a/grab.js b/grab.js index 614bfdaa..34e868de 100644 --- a/grab.js +++ b/grab.js @@ -306,38 +306,6 @@ var MoveGrab = class MoveGrab { } } - // positionChanged() { - // let metaWindow = this.window; - - // let [gx, gy, $] = global.get_pointer(); - - // if (this.dnd) { - // print("SHOULD NOT HAPPEND") - // // this.selectDndZone(gx, gy); // TODO: dead/obsolete? - // } else { // Move the window and scroll the space - // let space = this.initialSpace; - // let clone = metaWindow.clone; - // let frame = metaWindow.get_frame_rect(); - // // scrollAnchhor = gx - space.monitor.x - space.cloneContainer.x - // // scrollAnchhor - gx + space.monitor.x = - space.cloneContainer.x - - // space.targetX = gx - space.monitor.x - this.scrollAnchhor; - // space.cloneContainer.x = space.targetX; - - // const threshold = 300; - // const dy = Math.min(threshold, Math.abs(frame.y - this.initialY)); - // let s = 1 - Math.pow(dy / 500, 3); - // let actor = metaWindow.get_compositor_private(); - // actor.set_scale(s, s); - // clone.set_scale(s, s); - // [clone.x, clone.y] = space.globalToScroll(frame.x, frame.y); - - // if (dy >= threshold) { - // this.beginDnD(); - // } - // } - // } - // scroll(space, actor, event) { // let dir = event.get_scroll_direction(); // if (dir === Clutter.ScrollDirection.SMOOTH) From 41778b1f30e0275d0f259803fefd390ad9abc79d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tor=20Hedin=20Br=C3=B8nner?= Date: Sat, 25 Jan 2020 20:42:23 +0100 Subject: [PATCH 020/104] grab: remove scroll-event --- grab.js | 33 --------------------------------- 1 file changed, 33 deletions(-) diff --git a/grab.js b/grab.js index 34e868de..00e92784 100644 --- a/grab.js +++ b/grab.js @@ -306,39 +306,6 @@ var MoveGrab = class MoveGrab { } } - // scroll(space, actor, event) { - // 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; - // } - // print(dx, gx, gy); - - // switch (dir) { - // case Clutter.ScrollDirection.LEFT: - // case Clutter.ScrollDirection.DOWN: - // space.switchLeft(); - // break; - // case Clutter.ScrollDirection.RIGHT: - // case Clutter.ScrollDirection.UP: - // space.switchRight(); - // break; - // } - - // // let speed = 30 - // // space.targetX += dx * speed - // // space.cloneContainer.x += dx * speed - // } - motion(actor, event) { let metaWindow = this.window; let [gx, gy] = event.get_coords(); From 8afae530d35a970a10a919cdee12904877ec2a6c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tor=20Hedin=20Br=C3=B8nner?= Date: Sat, 25 Jan 2020 20:43:18 +0100 Subject: [PATCH 021/104] space: only scroll if navigating or in a grab --- tiling.js | 2 ++ 1 file changed, 2 insertions(+) diff --git a/tiling.js b/tiling.js index c9dda6a9..d9f0cb0a 100644 --- a/tiling.js +++ b/tiling.js @@ -1091,6 +1091,8 @@ box-shadow: 0px 0px 8px 0px rgba(0, 0, 0, .7); 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; From 76f0811a8985807c2d466d2bece0d649e589d622 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tor=20Hedin=20Br=C3=B8nner?= Date: Sat, 25 Jan 2020 20:53:52 +0100 Subject: [PATCH 022/104] reposition clone using pointer after insertion For some reason this is accurate --- grab.js | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/grab.js b/grab.js index 00e92784..4294c10d 100644 --- a/grab.js +++ b/grab.js @@ -365,9 +365,10 @@ var MoveGrab = class MoveGrab { let [x, y] = clone.get_position(); space.addWindow(metaWindow, ...dndTarget.position); - - let [ok, sx, sy] = space.cloneContainer.transform_stage_point(x, y); - [clone.x, clone.y] = [sx, sy]; + let [ok, sx, sy] = space.cloneContainer.transform_stage_point(gx, gy); + let [dx, dy] = this.pointerOffset; + clone.x = sx - dx; + clone.y = sy - dy; let newScale = clone.scale_x/space.actor.scale_x; clone.set_scale(newScale, newScale); From f257e2618c5a2c9055a2614f100aebcf7d127eb9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tor=20Hedin=20Br=C3=B8nner?= Date: Sat, 25 Jan 2020 21:28:22 +0100 Subject: [PATCH 023/104] navigator: rewrite finish/destroy code MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Activate destination workspace if it isn't active, fake it if not. If a «stuck» window have focus, consider it selected. If selected window or one its transient window has focus, fake focus handler. Otherwise activate it. --- navigator.js | 35 ++++++++++++++--------------------- 1 file changed, 14 insertions(+), 21 deletions(-) diff --git a/navigator.js b/navigator.js index a4defb32..4e29179c 100644 --- a/navigator.js +++ b/navigator.js @@ -281,36 +281,29 @@ var Navigator = class Navigator { monitor.clickOverlay.activate(); } - if (this.space === from && force) { + 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); + force && Tiling.spaces.animateToSpace(this.space); + } else { + this.space.workspace.activate(global.get_current_time()); } selected = this.space.indexOf(selected) !== -1 ? selected : this.space.selectedWindow; - if (Tiling.inGrab && Tiling.inGrab.dnd) { - Tiling.spaces.monitors.set(this.monitor, this.space); - Tiling.spaces.animateToSpace(this.space); - } else { - if (selected && - (!force || - !(display.focus_window && display.focus_window.is_on_all_workspaces())) ) { - - let hasFocus = selected.has_focus(); - selected.foreach_transient(mw => hasFocus = mw.has_focus() || hasFocus); - if (!hasFocus) { - Main.activateWindow(selected); - } else { - // Typically on cancel - the `focus` signal won't run - // automatically, so we run it manually - this.space.workspace.activate(global.get_current_time()); - Tiling.focus_handler(selected); - } + let focus = display.focus_window; + if (focus && focus.is_on_all_workspaces()) + selected = focus; + + if (selected) { + let hasFocus = selected && selected.has_focus(); + selected.foreach_transient(mw => hasFocus = mw.has_focus() || hasFocus); + if (hasFocus) { + Tiling.focus_handler(selected) } else { - this.space.workspace.activate(global.get_current_time()); + Main.activateWindow(selected); } } From 19b668d0d21d214c182cbd1c60e60f0b34c59da1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tor=20Hedin=20Br=C3=B8nner?= Date: Sun, 26 Jan 2020 13:23:06 +0100 Subject: [PATCH 024/104] navigate to correct space on grab.end() --- grab.js | 5 ++++- navigator.js | 8 ++++---- 2 files changed, 8 insertions(+), 5 deletions(-) diff --git a/grab.js b/grab.js index 4294c10d..36aea558 100644 --- a/grab.js +++ b/grab.js @@ -345,6 +345,7 @@ var MoveGrab = class MoveGrab { let actor = metaWindow.get_compositor_private(); let frame = metaWindow.get_frame_rect(); let clone = metaWindow.clone; + let destSpace; let [gx, gy, $] = global.get_pointer(); if (this.dnd) { @@ -356,6 +357,7 @@ var MoveGrab = class MoveGrab { if (dndTarget) { let space = dndTarget.space; + destSpace = space; space.selection.show() if (Scratch.isScratchWindow(metaWindow)) @@ -416,6 +418,7 @@ var MoveGrab = class MoveGrab { } } else if (this.initialSpace.indexOf(metaWindow) !== -1){ let space = this.initialSpace; + destSpace = space; space.targetX = space.cloneContainer.x; actor.set_scale(1, 1); @@ -449,7 +452,7 @@ var MoveGrab = class MoveGrab { // metaWindow.change_workspace(space.workspace) // space.workspace.activate(global.get_current_time()); Tiling.inGrab = false; - Navigator.getNavigator().finish(); + Navigator.getNavigator().finish(destSpace); global.display.set_cursor(Meta.Cursor.DEFAULT); } diff --git a/navigator.js b/navigator.js index 4e29179c..793ff673 100644 --- a/navigator.js +++ b/navigator.js @@ -233,14 +233,14 @@ var Navigator = class Navigator { this.was_accepted = true; } - finish() { + finish(space) { if (grab) return; this.accept(); - this.destroy(); + this.destroy(space); } - destroy() { + destroy(space) { this.minimaps.forEach(m => { if (typeof(m) === 'number') Mainloop.source_remove(m); @@ -258,7 +258,7 @@ var Navigator = class Navigator { 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; From cf09b9333d4be46e6484bf23ef557ffb1efed24e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tor=20Hedin=20Br=C3=B8nner?= Date: Sun, 26 Jan 2020 13:28:22 +0100 Subject: [PATCH 025/104] cleanup --- grab.js | 4 ---- 1 file changed, 4 deletions(-) diff --git a/grab.js b/grab.js index 36aea558..be2cb72e 100644 --- a/grab.js +++ b/grab.js @@ -272,10 +272,6 @@ var MoveGrab = class MoveGrab { } spaceMotion(space, background, event) { - e = event; - // e.get_coords() - // let p = new Clutter.Point() - // e.get_position(p) let [x, y] = event.get_coords(); let [gx, gy, $] = global.get_pointer(); let [ok, bx, by] = space.actor.transform_stage_point(gx, gy); From d6dc25eee24f55bc54389569b5f06cf44624ddfa Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tor=20Hedin=20Br=C3=B8nner?= Date: Sun, 26 Jan 2020 13:36:44 +0100 Subject: [PATCH 026/104] non dnd motion: coords were already monitor relative --- grab.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/grab.js b/grab.js index be2cb72e..9272d1c3 100644 --- a/grab.js +++ b/grab.js @@ -315,7 +315,7 @@ var MoveGrab = class MoveGrab { let space = this.initialSpace; let clone = metaWindow.clone; let [ok, x, y] = space.actor.transform_stage_point(gx, gy); - space.targetX = x - space.monitor.x - this.scrollAnchor; + space.targetX = x - this.scrollAnchor; space.cloneContainer.x = space.targetX; clone.y = y - dy; From ff527b992d087f7cbc3a869dfcc42bb2171e56dc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tor=20Hedin=20Br=C3=B8nner?= Date: Sun, 26 Jan 2020 21:34:55 +0100 Subject: [PATCH 027/104] navigator: never move a visible space --- navigator.js | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/navigator.js b/navigator.js index 793ff673..ca7860cb 100644 --- a/navigator.js +++ b/navigator.js @@ -271,16 +271,18 @@ 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 (!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 From 73442b9a629c0f1397348ef9e84eabb45cd4066a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tor=20Hedin=20Br=C3=B8nner?= Date: Sun, 26 Jan 2020 21:35:25 +0100 Subject: [PATCH 028/104] begin dnd when pointer moves to another monitor --- grab.js | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/grab.js b/grab.js index 9272d1c3..fc3e7f0d 100644 --- a/grab.js +++ b/grab.js @@ -181,6 +181,10 @@ var MoveGrab = class MoveGrab { global.display.end_grab_op(global.get_current_time()); global.display.set_cursor(Meta.Cursor.MOVE_OR_RESIZE_WINDOW); + for (let [monitor, $] of Tiling.spaces.monitors) { + monitor.clickOverlay.deactivate(); + } + let metaWindow = this.window; let actor = metaWindow.get_compositor_private(); let clone = metaWindow.clone; @@ -312,6 +316,11 @@ var MoveGrab = class MoveGrab { clone.x = gx - dx; clone.y = gy - dy; } else { + let monitor = monitorAtPoint(gx, gy); + if (monitor !== this.initialSpace.monitor) { + this.beginDnD(); + return; + } let space = this.initialSpace; let clone = metaWindow.clone; let [ok, x, y] = space.actor.transform_stage_point(gx, gy); From de4a31b0edcb3d31b4bcd3c4a245ddc8abab3e58 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ole=20J=C3=B8rgen=20Br=C3=B8nner?= Date: Mon, 27 Jan 2020 21:11:48 +0100 Subject: [PATCH 029/104] Ensure grab window remains focused when ending navigation before dnd --- navigator.js | 8 ++++++-- tiling.js | 4 ++++ 2 files changed, 10 insertions(+), 2 deletions(-) diff --git a/navigator.js b/navigator.js index ca7860cb..d38da09a 100644 --- a/navigator.js +++ b/navigator.js @@ -289,7 +289,11 @@ var Navigator = class Navigator { // again doesn't trigger a switch signal force && Tiling.spaces.animateToSpace(this.space); } else { - this.space.workspace.activate(global.get_current_time()); + 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 : @@ -299,7 +303,7 @@ var Navigator = class Navigator { if (focus && focus.is_on_all_workspaces()) selected = focus; - if (selected) { + if (selected && !Tiling.inGrab) { let hasFocus = selected && selected.has_focus(); selected.foreach_transient(mw => hasFocus = mw.has_focus() || hasFocus); if (hasFocus) { diff --git a/tiling.js b/tiling.js index d9f0cb0a..af2c8d68 100644 --- a/tiling.js +++ b/tiling.js @@ -1625,6 +1625,10 @@ 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. From b1f613037516c07341f83730ca2c69f0c9f1198c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tor=20Hedin=20Br=C3=B8nner?= Date: Mon, 27 Jan 2020 16:41:52 +0100 Subject: [PATCH 030/104] space: fix scroll direction --- tiling.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tiling.js b/tiling.js index af2c8d68..9c3ce34c 100644 --- a/tiling.js +++ b/tiling.js @@ -1111,11 +1111,11 @@ box-shadow: 0px 0px 8px 0px rgba(0, 0, 0, .7); switch (dir) { case Clutter.ScrollDirection.LEFT: - case Clutter.ScrollDirection.DOWN: + case Clutter.ScrollDirection.UP: this.switchLeft(); break; case Clutter.ScrollDirection.RIGHT: - case Clutter.ScrollDirection.UP: + case Clutter.ScrollDirection.DOWN: this.switchRight(); break; } From abe9ac1ec0b23b194d4e730f242f3108b7f25bec Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tor=20Hedin=20Br=C3=B8nner?= Date: Tue, 28 Jan 2020 20:05:15 +0100 Subject: [PATCH 031/104] moveDone: guard against all grabbing We now do all move grabbing in clutter. --- tiling.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tiling.js b/tiling.js index 9c3ce34c..d568220d 100644 --- a/tiling.js +++ b/tiling.js @@ -802,7 +802,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; } From ab33b9a268af0913d55ef23cfe302aa2d0b76533 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tor=20Hedin=20Br=C3=B8nner?= Date: Tue, 28 Jan 2020 20:06:11 +0100 Subject: [PATCH 032/104] grab: make re-grabbing a window smoother --- grab.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/grab.js b/grab.js index fc3e7f0d..d9c83a84 100644 --- a/grab.js +++ b/grab.js @@ -191,7 +191,8 @@ var MoveGrab = class MoveGrab { let space = this.initialSpace; let frame = metaWindow.get_frame_rect(); - this.initialY = clone.y; + this.initialY = clone.targetY; + Tweener.removeTweens(clone); let [gx, gy, $] = global.get_pointer(); let px = (gx - actor.x) / actor.width; From bf2de90d0df07079c493294d6dfd3fa2abe94744 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ole=20J=C3=B8rgen=20Br=C3=B8nner?= Date: Wed, 29 Jan 2020 21:06:22 +0100 Subject: [PATCH 033/104] Always run a complete switch workspace handler on navigation end (inPreview) Needed to ensure all windows belong to the space's workspace. Especially this case: - Start navigating (from A) to the previous workspace (B). - Drag and drop a window (X) from B to A - Navigate back to A and end navigation. X still belongs to B unless the full event handler run. ref commit: Ensure all windows in the space belong to the workspace before completing workspace switch --- navigator.js | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/navigator.js b/navigator.js index d38da09a..d9c9fa55 100644 --- a/navigator.js +++ b/navigator.js @@ -287,7 +287,10 @@ var Navigator = class Navigator { // 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 - force && 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()); From 5aeab6f1dca8d81cc92ae396aee4ae735feaa52b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ole=20J=C3=B8rgen=20Br=C3=B8nner?= Date: Wed, 29 Jan 2020 22:35:27 +0100 Subject: [PATCH 034/104] Only react to background clicks when in preview mode Background click outside preview caused background to be "permanently" tracked when only scratch windows was visible. --- tiling.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tiling.js b/tiling.js index d568220d..3eab9ce3 100644 --- a/tiling.js +++ b/tiling.js @@ -1082,7 +1082,7 @@ box-shadow: 0px 0px 8px 0px rgba(0, 0, 0, .7); ensureViewport(windowAtPoint, this); inGrab = new Extension.imports.grab.MoveGrab(windowAtPoint, Meta.GrabOp.MOVING, this); inGrab.begin(); - } else { + } else if (inPreview) { spaces.selectedSpace = this; Navigator.getNavigator().finish(); } From ab7e7cc2a4925ff894ff2234009923aa7c32fb35 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ole=20J=C3=B8rgen=20Br=C3=B8nner?= Date: Wed, 29 Jan 2020 23:00:34 +0100 Subject: [PATCH 035/104] utils:trace: don't require a metawindow --- utils.js | 19 ++++++++++++++----- 1 file changed, 14 insertions(+), 5 deletions(-) diff --git a/utils.js b/utils.js index a9ce90a2..83d1f71c 100644 --- a/utils.js +++ b/utils.js @@ -347,9 +347,22 @@ var tweener = { } }; +function isMetaWindow(obj) { + return obj && obj.window_type && obj.get_compositor_private; +} function trace(topic, ...args) { - windowTrace(topic, ...args); + if (!topic.match(/.*/)) { + return; + } + + if (isMetaWindow(args[0])) { + windowTrace(topic, ...args); + } else { + let trace = shortTrace(1).join(" < "); + let extraInfo = args.length > 0 ? "\n\t" + args.map(x => x.toString()).join("\n\t") : "" + log(topic, trace, extraInfo); + } } let existingWindows = new Set(); @@ -359,10 +372,6 @@ function windowTrace(topic, metaWindow, ...rest) { return; } - if (!topic.match(/.*/)) { - return; - } - log(topic, infoMetaWindow(metaWindow).join("\n"), ...rest.join("\n")); } From 4670338f2961e12a031c9fb3ad34246cb1e4815b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tor=20Hedin=20Br=C3=B8nner?= Date: Thu, 13 Feb 2020 16:06:12 +0100 Subject: [PATCH 036/104] wip: implemnt keyboard grab --- grab.js | 50 +++++++++++++++++++++++++++++++++++++------------- 1 file changed, 37 insertions(+), 13 deletions(-) diff --git a/grab.js b/grab.js index d9c83a84..aac9cfb2 100644 --- a/grab.js +++ b/grab.js @@ -173,7 +173,8 @@ var MoveGrab = class MoveGrab { this.initialSpace = space || Tiling.spaces.spaceOfWindow(metaWindow); } - begin() { + begin({center} = {}) { + this.center = center; log(`begin`) if (this.grabbed) return; @@ -202,7 +203,8 @@ var MoveGrab = class MoveGrab { let [ok, x, y] = space.cloneContainer.transform_stage_point(gx, gy); px = (x - clone.x) / clone.width; py = (y - clone.y) / clone.height; - clone.set_pivot_point(px, py); + !center && clone.set_pivot_point(px, py); + center && clone.set_pivot_point(0, 0); if (clone.get_parent()) { this.pointerOffset = [x - clone.x, y - clone.y]; } else { @@ -223,29 +225,42 @@ var MoveGrab = class MoveGrab { Tweener.removeTweens(space.cloneContainer); } - beginDnD() { + beginDnD({center} = {}) { if (this.dnd) return; + this.center = center; + this.dnd = true; log(`beginDND`) global.display.set_cursor(Meta.Cursor.MOVE_OR_RESIZE_WINDOW); - this.dnd = true; let metaWindow = this.window; let actor = metaWindow.get_compositor_private(); let clone = metaWindow.clone; let space = this.initialSpace; + let point = space.cloneContainer.apply_relative_transform_to_point( + global.stage, new Clutter.Vertex({x: clone.x, y: clone.y})); + let i = space.indexOf(metaWindow); let single = i !== -1 && space[i].length === 1; space.removeWindow(metaWindow); clone.reparent(Main.uiGroup); let [gx, gy, $] = global.get_pointer(); - let [dx, dy] = this.pointerOffset; - clone.x = gx - dx; - clone.y = gy - dy; + + clone.x = Math.round(point.x); + clone.y = Math.round(point.y); let newScale = clone.scale_x*space.actor.scale_x; clone.set_scale(newScale, newScale); - Tweener.addTween(clone, {time: prefs.animation_time, scale_x: 0.5, scale_y: 0.5}); + + let params = {time: prefs.animation_time, scale_x: 0.5, scale_y: 0.5} + if (center) { + this.pointerOffset = [0, 0]; + clone.set_pivot_point(0, 0) + params.x = gx + params.y = gy + } + + Tweener.addTween(clone, params); this.signals.connect(global.stage, "button-press-event", this.end.bind(this)); @@ -257,12 +272,12 @@ var MoveGrab = class MoveGrab { let onSame = monitor === space.monitor; let [ok, x, y] = space.actor.transform_stage_point(gx, gy); - if (onSame && single && space[i]) { + if (!this.center && onSame && single && space[i]) { Tiling.move_to(space, space[i][0], { x: x + prefs.window_gap/2 }); - } else if (onSame && single && space[i-1]) { + } else if (!this.center && onSame && single && space[i-1]) { Tiling.move_to(space, space[i-1][0], { x: x - space[i-1][0].clone.width - prefs.window_gap/2 }); - } else if (onSame && space.length === 0) { + } else if (!this.center && onSame && space.length === 0) { space.targetX = x; space.cloneContainer.x = x; } @@ -313,9 +328,18 @@ var MoveGrab = class MoveGrab { let [dx, dy] = this.pointerOffset; let clone = metaWindow.clone; + let tx = clone.get_transition('x') + let ty = clone.get_transition('y') + if (this.dnd) { - clone.x = gx - dx; - clone.y = gy - dy; + if (tx) { + log(`motion`, tx, this.pointerOffset) + tx.set_to(gx - dx) + ty.set_to(gy - dy) + } else { + clone.x = gx - dx; + clone.y = gy - dy; + } } else { let monitor = monitorAtPoint(gx, gy); if (monitor !== this.initialSpace.monitor) { From a2312dd7cad2934722386dd49f6d6866fb57d82f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tor=20Hedin=20Br=C3=B8nner?= Date: Sat, 15 Feb 2020 15:14:06 +0100 Subject: [PATCH 037/104] refactor to globalToScroll and globalToViewport --- grab.js | 21 ++++++++------------- tiling.js | 17 ++++++++++++----- 2 files changed, 20 insertions(+), 18 deletions(-) diff --git a/grab.js b/grab.js index aac9cfb2..b315f550 100644 --- a/grab.js +++ b/grab.js @@ -1,4 +1,3 @@ - var Extension; if (imports.misc.extensionUtils.extensions) { Extension = imports.misc.extensionUtils.extensions["paperwm@hedning:matrix.org"]; @@ -200,7 +199,7 @@ var MoveGrab = class MoveGrab { let py = (gy - actor.y) / actor.height; actor.set_pivot_point(px, py); - let [ok, x, y] = space.cloneContainer.transform_stage_point(gx, gy); + let [x, y] = space.globalToScroll(gx, gy); px = (x - clone.x) / clone.width; py = (y - clone.y) / clone.height; !center && clone.set_pivot_point(px, py); @@ -271,7 +270,7 @@ var MoveGrab = class MoveGrab { let onSame = monitor === space.monitor; - let [ok, x, y] = space.actor.transform_stage_point(gx, gy); + let [x, y] = space.globalToViewport(gx, gy); if (!this.center && onSame && single && space[i]) { Tiling.move_to(space, space[i][0], { x: x + prefs.window_gap/2 }); } else if (!this.center && onSame && single && space[i-1]) { @@ -282,7 +281,7 @@ var MoveGrab = class MoveGrab { space.cloneContainer.x = x; } - let [sx, sy] = space.globalToScroll(gx, gy, true); + let [sx, sy] = space.globalToScroll(gx, gy, {useTarget: true}); for (let [workspace, space] of Tiling.spaces) { this.signals.connect(space.background, "motion-event", this.spaceMotion.bind(this, space)); @@ -292,10 +291,9 @@ var MoveGrab = class MoveGrab { } spaceMotion(space, background, event) { - let [x, y] = event.get_coords(); let [gx, gy, $] = global.get_pointer(); - let [ok, bx, by] = space.actor.transform_stage_point(gx, gy); - this.selectDndZone(space, bx - space.targetX, by); + let [sx, sy] = space.globalToScroll(gx, gy, {useTarget: true}); + this.selectDndZone(space, sx, sy); } /** x,y in scroll cooridinates */ @@ -348,7 +346,7 @@ var MoveGrab = class MoveGrab { } let space = this.initialSpace; let clone = metaWindow.clone; - let [ok, x, y] = space.actor.transform_stage_point(gx, gy); + let [x, y] = space.globalToViewport(gx, gy); space.targetX = x - this.scrollAnchor; space.cloneContainer.x = space.targetX; @@ -397,7 +395,7 @@ var MoveGrab = class MoveGrab { let [x, y] = clone.get_position(); space.addWindow(metaWindow, ...dndTarget.position); - let [ok, sx, sy] = space.cloneContainer.transform_stage_point(gx, gy); + let [sx, sy] = space.globalToScroll(gx, gy); let [dx, dy] = this.pointerOffset; clone.x = sx - dx; clone.y = sy - dy; @@ -502,10 +500,7 @@ var MoveGrab = class MoveGrab { let clone = this.window.clone; let space = zone.space; - // let [x, y] = clone.get_transformed_position() - log(...clone.get_transformed_position(), clone.get_parent(), clone.x, clone.y, space.targetX) - // log(clone.get_transformed_size()) - let [ok, x, y] = space.cloneContainer.transform_stage_point(...clone.get_transformed_position()) + let [x, y] = space.globalToScroll(...clone.get_transformed_position()) zone.actor.set_position(x, y) zone.actor.set_size(...clone.get_transformed_size()) } else { diff --git a/tiling.js b/tiling.js index 3eab9ce3..7ba4e3f8 100644 --- a/tiling.js +++ b/tiling.js @@ -788,12 +788,19 @@ 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)]; } moveDone() { From 04a0e5ec832964da5a4d95145b94d0bd918839a5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ole=20J=C3=B8rgen=20Br=C3=B8nner?= Date: Sat, 8 Feb 2020 20:25:39 +0100 Subject: [PATCH 038/104] utils: crude actor tree printer --- utils.js | 40 ++++++++++++++++++++++++++++++++++++++-- 1 file changed, 38 insertions(+), 2 deletions(-) diff --git a/utils.js b/utils.js index 83d1f71c..bda0eac2 100644 --- a/utils.js +++ b/utils.js @@ -209,12 +209,16 @@ function toggleCloneMarks() { // NB: doesn't clean up signal on disable function markCloneOf(metaWindow) { - if (metaWindow.clone) + if (metaWindow.clone) { metaWindow.clone.opacity = 190; + metaWindow.clone.background_color = imports.gi.Clutter.color_from_string("red")[1]; + } } function unmarkCloneOf(metaWindow) { - if (metaWindow.clone) + if (metaWindow.clone) { metaWindow.clone.opacity = 255; + metaWindow.clone.background_color = null; + } } let windows = display.get_tab_list(Meta.TabList.NORMAL_ALL, null); @@ -275,6 +279,38 @@ function monitorOfPoint(x, y) { } +function indent(level, str) { + let blank = "" + for (let i = 0; i < level; i++) { + blank += " " + } + return blank + str +} + + +function fmt(actor) { + let extra = [ + `${actor.get_position()}`, + `${actor.get_size()}`, + ]; + let metaWindow = actor.meta_window || actor.metaWindow; + if (metaWindow) { + metaWindow = `(mw: ${metaWindow.title})`; + extra.push(metaWindow); + } + return `${actor.toString()} ${extra.join(" | ")}`; +} + +function printActorTree(node, fmt=fmt, limit=Infnity, level=1) { + if (level > limit) { + return; + } + print(indent(level, fmt(node))); + for (let child of node.get_children()) { + printActorTree(child, fmt, limit, level+1); + } +} + class Signals extends Map { static get [Symbol.species]() { return Map; } From 78dd56dc36d625e225e95b8f89915eaaab49ef7d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ole=20J=C3=B8rgen=20Br=C3=B8nner?= Date: Sun, 16 Feb 2020 01:56:27 +0100 Subject: [PATCH 039/104] dnd: create zones and actors on the fly Precreated zones doesn't work when the user can alter the space during grab. (eg. resize a window or insert new ones) TODO: handle empty spaces --- grab.js | 272 +++++++++++++++++++++++++------------------------------- 1 file changed, 121 insertions(+), 151 deletions(-) diff --git a/grab.js b/grab.js index b315f550..2edcafa0 100644 --- a/grab.js +++ b/grab.js @@ -32,136 +32,6 @@ function monitorAtPoint(gx, gy) { return null; } -function createDndZonesForMonitors() { - let monitorToZones = new Map(); - for (let [monitor, space] of Tiling.spaces.monitors) { - monitorToZones.set(monitor, createDnDZones(space)); - } - return monitorToZones; -} - -function createDnDZones(space) { - - // In pixels protuding into the window. Ie. exclusive window_gap - let columnZoneMargin = 100; - let tileZoneMargin = 250; - - function mkZoneActor(zone, color="red") { - let actor = new St.Widget({style_class: "tile-preview"}) - actor.visible = false; - actor.x = zone.rect.x; - actor.y = zone.rect.y; - actor.width = zone.rect.width; - actor.height = zone.rect.height; - - space.cloneContainer.add_actor(actor); - actor.raise_top(); - return actor - } - - function mkColumnZone(x, position) { - // Represent zones as center, margin instead of "regular" rects? - let margin = prefs.window_gap / 2 + columnZoneMargin - - let detection = { - x: x - prefs.window_gap - columnZoneMargin, - y: 0, - width: margin * 2, - height: space.height - }; - - let zone = { - // Detection: - rect: detection, - position: position, - - space: space, - - // Visualization: - center: detection.x + detection.width / 2, - marginA: margin, // left - marginB: margin, // right - - // Animation props: - originProp: "x", - sizeProp: "width", - }; - zone.actor = mkZoneActor(zone, "red"); - zone.actor.y = Tiling.panelBox.height; - zone.actor.height = space.height - Tiling.panelBox.height; - return zone; - } - - function mkTileZone(x, y, width, position) { - let detection = { - x: x + columnZoneMargin, - y: y - prefs.window_gap - tileZoneMargin, - width: width - columnZoneMargin * 2, - height: prefs.window_gap + tileZoneMargin * 2, - }; - - let margin = prefs.window_gap + tileZoneMargin; - let cy = detection.y + detection.height / 2; - let marginTop = Math.min(cy - Tiling.panelBox.height, margin); - let marginBottom = margin; - - let zone = { - rect: detection, - position: position, - - space: space, - - // Visualization: - center: cy, - marginA: marginTop, - marginB: marginBottom, - - // Animation props: - originProp: "y", - sizeProp: "height", - }; - zone.actor = mkZoneActor(zone, "blue"); - zone.actor.x = x - prefs.window_gap / 2; - zone.actor.width = width + prefs.window_gap; - return zone; - } - - let zones = []; - for (let i = 0; i < space.length; i++) { - let col = space[i]; - zones.push(mkColumnZone(col[0].clone.targetX, [i])); - - for (let j = 0; j < col.length; j++) { - let metaWindow = col[j]; - let x = metaWindow.clone.targetX; - let y = metaWindow.clone.targetY; - let width = metaWindow._targetWidth || metaWindow.clone.width; - zones.push(mkTileZone(x, y, width, [i, j])); - } - - let lastRow = col[col.length-1]; - let x = lastRow.clone.targetX; - let y = lastRow.clone.targetY; - let width = lastRow._targetWidth || lastRow.clone.width; - let height = lastRow._targetHeight || lastRow.clone.height; - zones.push(mkTileZone(x, y + height + prefs.window_gap, width, [i, col.length])); - } - - zones.push(mkColumnZone(space.cloneContainer.width + prefs.window_gap, [space.length])); - if (space.length === 0) { - space.targetX = Math.round(space.width/2); - space.cloneContainer.x = space.targetX; - let width = Math.round(space.width/2); - let zone = zones[0]; - zone.rect.width = width; - zone.rect.x = -Math.round(width/2); - zone.rect.center = 0; - } - - return zones; -} - - var MoveGrab = class MoveGrab { constructor(metaWindow, type, space) { this.window = metaWindow; @@ -170,6 +40,7 @@ var MoveGrab = class MoveGrab { this.grabbed = false; this.initialSpace = space || Tiling.spaces.spaceOfWindow(metaWindow); + this.zoneActors = new Set(); } begin({center} = {}) { @@ -181,6 +52,7 @@ var MoveGrab = class MoveGrab { global.display.end_grab_op(global.get_current_time()); global.display.set_cursor(Meta.Cursor.MOVE_OR_RESIZE_WINDOW); + for (let [monitor, $] of Tiling.spaces.monitors) { monitor.clickOverlay.deactivate(); } @@ -263,9 +135,6 @@ var MoveGrab = class MoveGrab { this.signals.connect(global.stage, "button-press-event", this.end.bind(this)); - this.spaceToDndZones = new Map(); - this.monitorToZones = createDndZonesForMonitors(); - let monitor = monitorAtPoint(gx, gy); let onSame = monitor === space.monitor; @@ -285,7 +154,6 @@ var MoveGrab = class MoveGrab { for (let [workspace, space] of Tiling.spaces) { this.signals.connect(space.background, "motion-event", this.spaceMotion.bind(this, space)); - this.spaceToDndZones.set(space, createDnDZones(space)); } this.selectDndZone(space, sx, sy, single && onSame); } @@ -298,25 +166,126 @@ var MoveGrab = class MoveGrab { /** x,y in scroll cooridinates */ selectDndZone(space, x, y, initial=false) { - let dndZones = this.spaceToDndZones.get(space); - - let newDndTarget = null; - for (let zone of dndZones) { - if (isInRect(x, y, zone.rect)) { - if (newDndTarget) { - // Treat ambiguous zones as non-match (this way we don't have to ensure zones are non-overlapping :P) - newDndTarget = null; + const gap = prefs.window_gap; + const halfGap = gap / 2; + const columnZoneMargin = 100 + halfGap; + const rowZoneMargin = 250 + halfGap; + + let target = null; + const tilingHeight = space.height - Tiling.panelBox.height; + + // TODO: move actor creation to activateDndTarget? + function mkZoneActor(props) { + let actor = new St.Widget({style_class: "tile-preview"}); + actor.x = props.x; + actor.y = props.y; + actor.width = props.width; + actor.height = props.height; + return actor; + } + + const lastClone = space[space.length - 1][0].clone; + const fakeClone = { + clone: { + targetX: lastClone.targetX + lastClone.width + gap, + targetY: lastClone.targetY, + width: columnZoneMargin, + height: tilingHeight + } + }; + + const columns = [...space, [fakeClone]]; + for (let j = 0; j < columns.length; j++) { + const column = columns[j]; + const metaWindow = column[0]; + const clone = metaWindow.clone; + + // FIXME: Non-uniform column width + const colX = clone.targetX; + const colW = clone.width; + + // Fast forward if pointer is not inside column + if (x < colX - gap - columnZoneMargin) { + continue; + } + if (colX + colW < x) { + continue; + } + + const cx = colX - halfGap; + const l = cx - columnZoneMargin; + const r = cx + columnZoneMargin; + if (l <= x && x <= r) { + target = { + position: [j], + center: cx, + originProp: "x", + sizeProp: "width", + marginA: columnZoneMargin, + marginB: columnZoneMargin, + space: space, + actor: mkZoneActor({ + x: l, + y: Tiling.panelBox.height, + width: columnZoneMargin*2, + height: tilingHeight + }) + }; + break; + } + + for (let i = 0; i < column.length + 1; i++) { + let clone; + if (i < column.length) { + clone = column[i].clone; + } else { + let lastClone = column[i-1].clone; + clone = { + targetX: lastClone.targetX, + targetY: lastClone.targetY + lastClone.height + gap, + width: lastClone.width, + height: 0 + }; + } + const isFirst = i === 0; + const isLast = i === column.length; + const cy = clone.targetY - halfGap; + const t = cy - rowZoneMargin; + const b = cy + rowZoneMargin; + if (t <= y && y <= b) { + target = { + position: [j, i], + center: cy, + originProp: "y", + sizeProp: "height", + marginA: isFirst ? 0 : rowZoneMargin, + marginB: isLast ? 0 : rowZoneMargin, + space: space, + actor: mkZoneActor({ + x: clone.targetX, + y: cy, + width: clone.width, + height: (isFirst ? 0 : rowZoneMargin) + (isLast ? 0 : rowZoneMargin), + }) + }; break; } - newDndTarget = zone; } } + function sameTarget(a, b) { + if (a === b) + return true; + if (!a || !b) + return false; + return a.position[0] === b.position[0] && a.position[1] === b.position[1]; + } + // TODO: rename dndTarget to selectedZone ? - if (newDndTarget !== this.dndTarget) { + if (!sameTarget(target, this.dndTarget)) { this.dndTarget && this.deactivateDndTarget(this.dndTarget); - if (newDndTarget) - this.activateDndTarget(newDndTarget, initial); + if (target) + this.activateDndTarget(target, initial); } } @@ -376,13 +345,11 @@ var MoveGrab = class MoveGrab { let destSpace; let [gx, gy, $] = global.get_pointer(); + this.zoneActors.forEach(actor => actor.destroy()); + if (this.dnd) { let dndTarget = this.dndTarget; - for (let [space, zones] of this.spaceToDndZones) { - zones.forEach(zone => zone.actor.destroy()); - } - if (dndTarget) { let space = dndTarget.space; destSpace = space; @@ -485,8 +452,11 @@ var MoveGrab = class MoveGrab { } activateDndTarget(zone, first) { + zone.space.cloneContainer.add_child(zone.actor); + zone.actor.raise_top(); zone.space.selection.hide(); this.dndTarget = zone; + this.zoneActors.add(zone.actor); let params = { time: prefs.animation_time, @@ -520,7 +490,7 @@ var MoveGrab = class MoveGrab { time: prefs.animation_time, [zone.originProp]: zone.center, [zone.sizeProp]: 0, - onComplete: () => zone.actor.hide() + onComplete: () => { zone.actor.destroy(); this.zoneActors.delete(zone.actor); } }); } From ecf487c157a77b66a112d4b27318b59bab07b364 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ole=20J=C3=B8rgen=20Br=C3=B8nner?= Date: Sun, 16 Feb 2020 15:52:14 +0100 Subject: [PATCH 040/104] dnd: handle empty spaces (on demand zones) --- grab.js | 24 ++++++++++++++---------- tiling.js | 4 ++++ 2 files changed, 18 insertions(+), 10 deletions(-) diff --git a/grab.js b/grab.js index 2edcafa0..79f6849e 100644 --- a/grab.js +++ b/grab.js @@ -184,17 +184,21 @@ var MoveGrab = class MoveGrab { return actor; } - const lastClone = space[space.length - 1][0].clone; - const fakeClone = { - clone: { - targetX: lastClone.targetX + lastClone.width + gap, - targetY: lastClone.targetY, - width: columnZoneMargin, - height: tilingHeight - } + let fakeClone = { + targetX: null, + targetY: 0, + width: columnZoneMargin, + height: tilingHeight }; + if (space.length > 0) { + const lastClone = space[space.length - 1][0].clone; + fakeClone.targetX = lastClone.x + lastClone.width + gap; + } else { + let [sx, sy] = space.viewportToScroll(Math.round(space.width/2), 0); + fakeClone.targetX = sx + halfGap; + } - const columns = [...space, [fakeClone]]; + const columns = [...space, [{ clone: fakeClone }]]; for (let j = 0; j < columns.length; j++) { const column = columns[j]; const metaWindow = column[0]; @@ -278,7 +282,7 @@ var MoveGrab = class MoveGrab { return true; if (!a || !b) return false; - return a.position[0] === b.position[0] && a.position[1] === b.position[1]; + return a.space === b.space && a.position[0] === b.position[0] && a.position[1] === b.position[1]; } // TODO: rename dndTarget to selectedZone ? diff --git a/tiling.js b/tiling.js index 7ba4e3f8..360eae94 100644 --- a/tiling.js +++ b/tiling.js @@ -803,6 +803,10 @@ class Space extends Array { return [Math.round(sx), Math.round(sy)]; } + viewportToScroll(vx, vy=0) { + return [vx - this.cloneContainer.x, vy - this.cloneContainer.y]; + } + moveDone() { if (this.cloneContainer.x !== this.targetX || this.actor.y !== 0 || From 6e22da7278fce08bc8b73758aa3704b5bfe9982c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ole=20J=C3=B8rgen=20Br=C3=B8nner?= Date: Sun, 16 Feb 2020 17:50:37 +0100 Subject: [PATCH 041/104] dnd: rip out the window immediatedly when ctrl is held down (only works properly in wayland) --- grab.js | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/grab.js b/grab.js index 79f6849e..ff7fa3f0 100644 --- a/grab.js +++ b/grab.js @@ -317,6 +317,13 @@ var MoveGrab = class MoveGrab { this.beginDnD(); return; } + + if (event.get_state() & Clutter.ModifierType.CONTROL_MASK) { + // NB: only works in wayland + this.beginDnD(); + return; + } + let space = this.initialSpace; let clone = metaWindow.clone; let [x, y] = space.globalToViewport(gx, gy); From 775db1b2a54bfca54208db8b3450649e1bbe98fb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tor=20Hedin=20Br=C3=B8nner?= Date: Thu, 20 Feb 2020 19:06:57 +0100 Subject: [PATCH 042/104] navigator: Start DnD if grab is active on finish --- navigator.js | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/navigator.js b/navigator.js index d9c9fa55..313ef72e 100644 --- a/navigator.js +++ b/navigator.js @@ -248,6 +248,10 @@ var Navigator = class Navigator { m.destroy(); }); + if (Tiling.inGrab && !Tiling.inGrab.dnd) { + Tiling.inGrab.beginDnD() + } + if (Main.panel.statusArea.appMenu) Main.panel.statusArea.appMenu.container.show(); From 87b6518851d4c3e3d65552d5620ba19ecf59db50 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tor=20Hedin=20Br=C3=B8nner?= Date: Thu, 20 Feb 2020 19:37:23 +0100 Subject: [PATCH 043/104] scratch: Add animateWindows and showWindows --- scratch.js | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/scratch.js b/scratch.js index 08a5e9aa..62c1bf45 100644 --- a/scratch.js +++ b/scratch.js @@ -201,6 +201,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; From 25fcdeaf29c5045ce681875d5ab42e31bbec0edb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tor=20Hedin=20Br=C3=B8nner?= Date: Thu, 20 Feb 2020 19:43:59 +0100 Subject: [PATCH 044/104] Substitute scratch windows with clones This is necessary for events to pass through to the space. --- grab.js | 1 + navigator.js | 4 ++++ 2 files changed, 5 insertions(+) diff --git a/grab.js b/grab.js index ff7fa3f0..3918d8fc 100644 --- a/grab.js +++ b/grab.js @@ -93,6 +93,7 @@ var MoveGrab = class MoveGrab { space.startAnimate(); // Make sure the window actor is visible Tiling.animateWindow(metaWindow); + Navigator.getNavigator(); Tweener.removeTweens(space.cloneContainer); } diff --git a/navigator.js b/navigator.js index 313ef72e..2f9466f7 100644 --- a/navigator.js +++ b/navigator.js @@ -211,6 +211,7 @@ var Navigator = class Navigator { TopBar.fixTopBar(); + Scratch.animateWindows(); this.space.startAnimate(); } @@ -320,6 +321,9 @@ var Navigator = class Navigator { } } + if (!Tiling.inGrab) + Scratch.showWindows(); + TopBar.fixTopBar(); Main.wm._blockAnimations = this._block; From a4fd3e45ff6a855304d8fd178acf6f5f9bf393ec Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tor=20Hedin=20Br=C3=B8nner?= Date: Thu, 20 Feb 2020 19:47:52 +0100 Subject: [PATCH 045/104] navigator: pseudo focus when hitting escape in grab --- navigator.js | 3 +++ 1 file changed, 3 insertions(+) diff --git a/navigator.js b/navigator.js index 2f9466f7..e3627890 100644 --- a/navigator.js +++ b/navigator.js @@ -320,6 +320,9 @@ var Navigator = class Navigator { Main.activateWindow(selected); } } + if (selected && Tiling.inGrab && !this.was_accepted) { + Tiling.focus_handler(selected) + } if (!Tiling.inGrab) Scratch.showWindows(); From 94529ad29ac869059d5fc53e4f6a6fb05fbfc7d1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tor=20Hedin=20Br=C3=B8nner?= Date: Thu, 20 Feb 2020 20:10:32 +0100 Subject: [PATCH 046/104] dnd: hide minimap This is a bit ugly, should probably have a proper method for this. --- grab.js | 3 +++ 1 file changed, 3 insertions(+) diff --git a/grab.js b/grab.js index 3918d8fc..25da708c 100644 --- a/grab.js +++ b/grab.js @@ -103,6 +103,9 @@ var MoveGrab = class MoveGrab { this.center = center; this.dnd = true; log(`beginDND`) + Navigator.getNavigator() + .minimaps.forEach(m => typeof(m) === 'number' ? + Mainloop.source_remove(m) : m.hide()); global.display.set_cursor(Meta.Cursor.MOVE_OR_RESIZE_WINDOW); let metaWindow = this.window; let actor = metaWindow.get_compositor_private(); From 512ea788985508fd13516b4affddbbcbd28f4349 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tor=20Hedin=20Br=C3=B8nner?= Date: Fri, 21 Feb 2020 12:39:42 +0100 Subject: [PATCH 047/104] begin: fix scratchish detection --- grab.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/grab.js b/grab.js index 25da708c..8ea9bbc0 100644 --- a/grab.js +++ b/grab.js @@ -76,7 +76,7 @@ var MoveGrab = class MoveGrab { py = (y - clone.y) / clone.height; !center && clone.set_pivot_point(px, py); center && clone.set_pivot_point(0, 0); - if (clone.get_parent()) { + if (clone.get_parent() === this.initialSpace.cloneContainer) { this.pointerOffset = [x - clone.x, y - clone.y]; } else { this.pointerOffset = [gx - frame.x, gy - frame.y]; From 45608e03c8dabbce4697586cf8b12c88d243a9dc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tor=20Hedin=20Br=C3=B8nner?= Date: Fri, 21 Feb 2020 13:14:49 +0100 Subject: [PATCH 048/104] grab: fix for scratch windows --- grab.js | 77 ++++++++++++++++++++++++++++++--------------------------- 1 file changed, 40 insertions(+), 37 deletions(-) diff --git a/grab.js b/grab.js index 8ea9bbc0..8d3da1a8 100644 --- a/grab.js +++ b/grab.js @@ -72,15 +72,20 @@ var MoveGrab = class MoveGrab { actor.set_pivot_point(px, py); let [x, y] = space.globalToScroll(gx, gy); - px = (x - clone.x) / clone.width; - py = (y - clone.y) / clone.height; - !center && clone.set_pivot_point(px, py); - center && clone.set_pivot_point(0, 0); if (clone.get_parent() === this.initialSpace.cloneContainer) { this.pointerOffset = [x - clone.x, y - clone.y]; + px = (x - clone.x) / clone.width; + py = (y - clone.y) / clone.height; } else { this.pointerOffset = [gx - frame.x, gy - frame.y]; + clone.x = frame.x; + clone.y = frame.y; + px = (gx - clone.x) / clone.width; + py = (gy - clone.y) / clone.height; } + !center && clone.set_pivot_point(px, py); + center && clone.set_pivot_point(0, 0); + log('pointeroffset', this.pointerOffset, clone.get_pivot_point()) this.signals.connect(global.stage, "button-release-event", this.end.bind(this)); this.signals.connect(global.stage, "motion-event", this.motion.bind(this)); @@ -92,8 +97,8 @@ var MoveGrab = class MoveGrab { this.scrollAnchor = x; space.startAnimate(); // Make sure the window actor is visible - Tiling.animateWindow(metaWindow); Navigator.getNavigator(); + Tiling.animateWindow(metaWindow); Tweener.removeTweens(space.cloneContainer); } @@ -112,22 +117,24 @@ var MoveGrab = class MoveGrab { let clone = metaWindow.clone; let space = this.initialSpace; - let point = space.cloneContainer.apply_relative_transform_to_point( - global.stage, new Clutter.Vertex({x: clone.x, y: clone.y})); + let point = {x: clone.x, y: clone.y}; + if (clone.get_parent() !== Main.uiGroup) { + point = space.cloneContainer.apply_relative_transform_to_point( + global.stage, new Clutter.Vertex({x: clone.x, y: clone.y})); + } let i = space.indexOf(metaWindow); let single = i !== -1 && space[i].length === 1; space.removeWindow(metaWindow); clone.reparent(Main.uiGroup); - let [gx, gy, $] = global.get_pointer(); - + log(`begin dnd`, point.x, point.y) clone.x = Math.round(point.x); clone.y = Math.round(point.y); - let newScale = clone.scale_x*space.actor.scale_x; clone.set_scale(newScale, newScale); - let params = {time: prefs.animation_time, scale_x: 0.5, scale_y: 0.5} + let params = {time: prefs.animation_time, scale_x: 0.5, scale_y: 0.5, opacity: 240} + let [gx, gy, $] = global.get_pointer(); if (center) { this.pointerOffset = [0, 0]; clone.set_pivot_point(0, 0) @@ -361,6 +368,12 @@ var MoveGrab = class MoveGrab { let [gx, gy, $] = global.get_pointer(); this.zoneActors.forEach(actor => actor.destroy()); + let params = { + time: prefs.animation_time, + scale_x: 1, + scale_y: 1, + opacity: 255 + }; if (this.dnd) { let dndTarget = this.dndTarget; @@ -388,15 +401,11 @@ var MoveGrab = class MoveGrab { actor.set_pivot_point(0, 0); // Tiling.animateWindow(metaWindow); - Tweener.addTween(clone, { - time: prefs.animation_time, - scale_x: 1, - scale_y: 1, - onComplete: () => { - space.moveDone() - clone.set_pivot_point(0, 0) - } - }); + params.onStopped = () => { + space.moveDone() + clone.set_pivot_point(0, 0) + } + Tweener.addTween(clone, params); space.targetX = space.cloneContainer.x; space.selectedWindow = metaWindow; @@ -414,17 +423,15 @@ var MoveGrab = class MoveGrab { Scratch.makeScratch(metaWindow); this.initialSpace.moveDone(); + actor.set_scale(clone.scale_x, clone.scale_y); + actor.opacity = clone.opacity; + + clone.opacity = 255; clone.set_scale(1, 1); clone.set_pivot_point(0, 0); - Tweener.addTween(actor, { - time: prefs.animation_time, - scale_x: 1, - scale_y: 1, - onComplete: () => { - actor.set_pivot_point(0, 0) - } - }); + params.onStopped = () => { actor.set_pivot_point(0, 0) }; + Tweener.addTween(actor, params); } } else if (this.initialSpace.indexOf(metaWindow) !== -1){ let space = this.initialSpace; @@ -435,15 +442,11 @@ var MoveGrab = class MoveGrab { actor.set_pivot_point(0, 0); Tiling.animateWindow(metaWindow); - Tweener.addTween(clone, { - time: prefs.animation_time, - scale_x: 1, - scale_y: 1, - onComplete: () => { - space.moveDone() - clone.set_pivot_point(0, 0) - } - }); + params.onStopped = () => { + space.moveDone() + clone.set_pivot_point(0, 0) + } + Tweener.addTween(clone, params); Tiling.ensureViewport(metaWindow, space); } From 61ff9ce48f281270a54802b2c0a289241ac063be Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tor=20Hedin=20Br=C3=B8nner?= Date: Fri, 21 Feb 2020 13:25:02 +0100 Subject: [PATCH 049/104] fix clone jumping when dragging in a scaled space --- grab.js | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/grab.js b/grab.js index 8d3da1a8..e658b4aa 100644 --- a/grab.js +++ b/grab.js @@ -117,10 +117,16 @@ var MoveGrab = class MoveGrab { let clone = metaWindow.clone; let space = this.initialSpace; - let point = {x: clone.x, y: clone.y}; - if (clone.get_parent() !== Main.uiGroup) { + let [gx, gy, $] = global.get_pointer(); + let point = {}; + if (center) { point = space.cloneContainer.apply_relative_transform_to_point( - global.stage, new Clutter.Vertex({x: clone.x, y: clone.y})); + global.stage, new Clutter.Vertex({x: Math.round(clone.x), y: Math.round(clone.y)})); + } else { + // For some reason the above isn't smooth when DnD is triggered from dragging + let [dx, dy] = this.pointerOffset; + point.x = gx - dx; + point.y = gy - dy; } let i = space.indexOf(metaWindow); @@ -134,7 +140,6 @@ var MoveGrab = class MoveGrab { clone.set_scale(newScale, newScale); let params = {time: prefs.animation_time, scale_x: 0.5, scale_y: 0.5, opacity: 240} - let [gx, gy, $] = global.get_pointer(); if (center) { this.pointerOffset = [0, 0]; clone.set_pivot_point(0, 0) From 0be41a04a7dfefbdd728bbe314ddb4303bd0203e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ole=20J=C3=B8rgen=20Br=C3=B8nner?= Date: Sat, 22 Feb 2020 20:11:51 +0100 Subject: [PATCH 050/104] dnd: rip out the window immediatedly when ctrl is held down (scratch also) (only works properly in wayland) --- tiling.js | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/tiling.js b/tiling.js index 360eae94..b8240baa 100644 --- a/tiling.js +++ b/tiling.js @@ -2725,10 +2725,15 @@ 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; + let dm = Clutter.DeviceManager.get_default(); + let d = dm.get_core_device(Clutter.InputDeviceType.KEYBOARD_DEVICE); + if (d.get_modifier_state() & 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: From ac58cb5a7f94071d6794237ccd249e7ce975fb70 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tor=20Hedin=20Br=C3=B8nner?= Date: Sat, 22 Feb 2020 20:58:34 +0100 Subject: [PATCH 051/104] ctrl grab: support gnome 3.36 --- tiling.js | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/tiling.js b/tiling.js index b8240baa..9615159a 100644 --- a/tiling.js +++ b/tiling.js @@ -2725,8 +2725,15 @@ function grabBegin(metaWindow, type) { case Meta.GrabOp.MOVING: inGrab = new Extension.imports.grab.MoveGrab(metaWindow, type); - let dm = Clutter.DeviceManager.get_default(); - let d = dm.get_core_device(Clutter.InputDeviceType.KEYBOARD_DEVICE); + let d; + if (Clutter.DeviceManager) { + let dm = Clutter.DeviceManager.get_default(); + d = dm.get_core_device(Clutter.InputDeviceType.KEYBOARD_DEVICE); + } else { + let backend = Clutter.get_default_backend() + let seat = backend.get_default_seat() + d = seat.get_keyboard() + } if (d.get_modifier_state() & Clutter.ModifierType.CONTROL_MASK) { inGrab.begin(); inGrab.beginDnD(); From 09020cc8ec65e619025f869e56a83af3f0e4b8a3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ole=20J=C3=B8rgen=20Br=C3=B8nner?= Date: Sun, 23 Feb 2020 11:58:31 +0100 Subject: [PATCH 052/104] dnd: restore wider zone on empty workspaces (regression) Also move zone actor creation to zone activation. (regression after "dnd: create zones and actors on the fly") --- grab.js | 47 ++++++++++++++++++++++------------------------- 1 file changed, 22 insertions(+), 25 deletions(-) diff --git a/grab.js b/grab.js index e658b4aa..e7c5b80f 100644 --- a/grab.js +++ b/grab.js @@ -184,22 +184,13 @@ var MoveGrab = class MoveGrab { selectDndZone(space, x, y, initial=false) { const gap = prefs.window_gap; const halfGap = gap / 2; - const columnZoneMargin = 100 + halfGap; + const columnZoneMarginViz = 100 + halfGap; + const columnZoneMargin = space.length > 0 ? columnZoneMarginViz : Math.round(space.width / 4); const rowZoneMargin = 250 + halfGap; let target = null; const tilingHeight = space.height - Tiling.panelBox.height; - // TODO: move actor creation to activateDndTarget? - function mkZoneActor(props) { - let actor = new St.Widget({style_class: "tile-preview"}); - actor.x = props.x; - actor.y = props.y; - actor.width = props.width; - actor.height = props.height; - return actor; - } - let fakeClone = { targetX: null, targetY: 0, @@ -241,15 +232,13 @@ var MoveGrab = class MoveGrab { center: cx, originProp: "x", sizeProp: "width", - marginA: columnZoneMargin, - marginB: columnZoneMargin, + marginA: columnZoneMarginViz, + marginB: columnZoneMarginViz, space: space, - actor: mkZoneActor({ - x: l, + actorParams: { y: Tiling.panelBox.height, - width: columnZoneMargin*2, height: tilingHeight - }) + } }; break; } @@ -281,12 +270,10 @@ var MoveGrab = class MoveGrab { marginA: isFirst ? 0 : rowZoneMargin, marginB: isLast ? 0 : rowZoneMargin, space: space, - actor: mkZoneActor({ + actorParams: { x: clone.targetX, - y: cy, width: clone.width, - height: (isFirst ? 0 : rowZoneMargin) + (isLast ? 0 : rowZoneMargin), - }) + } }; break; } @@ -475,9 +462,17 @@ var MoveGrab = class MoveGrab { } activateDndTarget(zone, first) { - zone.space.cloneContainer.add_child(zone.actor); - zone.actor.raise_top(); - zone.space.selection.hide(); + function mkZoneActor(props) { + let actor = new St.Widget({style_class: "tile-preview"}); + actor.x = props.x; + actor.y = props.y; + actor.width = props.width; + actor.height = props.height; + return actor; + } + + zone.actor = mkZoneActor({...zone.actorParams}); + this.dndTarget = zone; this.zoneActors.add(zone.actor); @@ -501,14 +496,16 @@ var MoveGrab = class MoveGrab { zone.actor[zone.originProp] = zone.center; } + zone.space.cloneContainer.add_child(zone.actor); + zone.space.selection.hide(); zone.actor.show(); zone.actor.raise_top(); Tweener.addTween(zone.actor, params); } deactivateDndTarget(zone) { - zone.space.selection.show(); if (zone) { + zone.space.selection.show(); Tweener.addTween(zone.actor, { time: prefs.animation_time, [zone.originProp]: zone.center, From 740628781aeac3a614f20fa446da827e6b6e94ea Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ole=20J=C3=B8rgen=20Br=C3=B8nner?= Date: Sun, 23 Feb 2020 15:14:56 +0100 Subject: [PATCH 053/104] dnd: bugfix zone selection --- grab.js | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/grab.js b/grab.js index e7c5b80f..983288e9 100644 --- a/grab.js +++ b/grab.js @@ -215,7 +215,7 @@ var MoveGrab = class MoveGrab { const colX = clone.targetX; const colW = clone.width; - // Fast forward if pointer is not inside column + // Fast forward if pointer is not inside the column or the column zone if (x < colX - gap - columnZoneMargin) { continue; } @@ -243,6 +243,10 @@ var MoveGrab = class MoveGrab { break; } + // Must be strictly within the column to tile vertically + if (x < colX) + continue; + for (let i = 0; i < column.length + 1; i++) { let clone; if (i < column.length) { From 9c94606355bb4f7279e73707ea8957a3794ae1b1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tor=20Hedin=20Br=C3=B8nner?= Date: Sun, 23 Feb 2020 20:20:24 +0100 Subject: [PATCH 054/104] navigator: do action before forcing dnd --- navigator.js | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/navigator.js b/navigator.js index e3627890..06d3a8da 100644 --- a/navigator.js +++ b/navigator.js @@ -156,9 +156,6 @@ var ActionDispatcher = class { if (!metaWindow && (action.options.mutterFlags & Meta.KeyBindingFlags.PER_WINDOW)) { return; } - if (Tiling.inGrab && !Tiling.inGrab.dnd && Tiling.inGrab.window) { - Tiling.inGrab.beginDnD(); - } if (!Tiling.inGrab && action.options.opensMinimap) { this.navigator._showMinimap(space); @@ -168,6 +165,10 @@ var ActionDispatcher = class { 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(); From d1e594e6cfffaadf8d4ee2ee28490b7bf736ed0e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tor=20Hedin=20Br=C3=B8nner?= Date: Sun, 1 Mar 2020 16:35:20 +0100 Subject: [PATCH 055/104] navigator finish: ensure moveDone is run --- navigator.js | 1 + 1 file changed, 1 insertion(+) diff --git a/navigator.js b/navigator.js index 06d3a8da..ad71b069 100644 --- a/navigator.js +++ b/navigator.js @@ -331,6 +331,7 @@ var Navigator = class Navigator { TopBar.fixTopBar(); Main.wm._blockAnimations = this._block; + this.space.moveDone(); this.emit('destroy', this.was_accepted); navigator = false; From 7bc192a6d4d4ffbbbf296be6793d85e4deea8a3a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tor=20Hedin=20Br=C3=B8nner?= Date: Sun, 8 Mar 2020 14:17:47 +0100 Subject: [PATCH 056/104] grab: pass the correct focus window to navigator When ending a grab we want to have full control over what space and window gains focus. So simply pass the window through navigator.finish in addition to the space. --- grab.js | 2 +- navigator.js | 13 ++++++++----- 2 files changed, 9 insertions(+), 6 deletions(-) diff --git a/grab.js b/grab.js index 983288e9..81eacee0 100644 --- a/grab.js +++ b/grab.js @@ -461,7 +461,7 @@ var MoveGrab = class MoveGrab { // metaWindow.change_workspace(space.workspace) // space.workspace.activate(global.get_current_time()); Tiling.inGrab = false; - Navigator.getNavigator().finish(destSpace); + Navigator.getNavigator().finish(destSpace, metaWindow); global.display.set_cursor(Meta.Cursor.DEFAULT); } diff --git a/navigator.js b/navigator.js index ad71b069..01b795c0 100644 --- a/navigator.js +++ b/navigator.js @@ -235,14 +235,14 @@ var Navigator = class Navigator { this.was_accepted = true; } - finish(space) { + finish(space, focus) { if (grab) return; this.accept(); - this.destroy(space); + this.destroy(space, focus); } - destroy(space) { + destroy(space, focus) { this.minimaps.forEach(m => { if (typeof(m) === 'number') Mainloop.source_remove(m); @@ -308,8 +308,11 @@ var Navigator = class Navigator { selected = this.space.indexOf(selected) !== -1 ? selected : this.space.selectedWindow; - let focus = display.focus_window; - if (focus && focus.is_on_all_workspaces()) + let curFocus = display.focus_window; + if (force && curFocus && curFocus.is_on_all_workspaces()) + selected = curFocus; + + if (focus) selected = focus; if (selected && !Tiling.inGrab) { From 9c232f2a6d05bf6c9719411b20e9052335fc2837 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ole=20J=C3=B8rgen=20Br=C3=B8nner?= Date: Sat, 14 Mar 2020 11:51:59 +0100 Subject: [PATCH 057/104] getModiferState: always return 0 on X11 The clutter reported modifier state is easily stuck on X11: - Enter "clutter mode". Press and hold ctrl whild exiting (usually ctrl-ecs) - Clutter will now report ctrl after ctrl is released causing windows to go directly to dnd mode when moved. --- notes.org | 2 ++ tiling.js | 11 +---------- utils.js | 33 +++++++++++++++++++++++++++++++++ 3 files changed, 36 insertions(+), 10 deletions(-) diff --git a/notes.org b/notes.org index bd57cbc3..3ec4cf73 100644 --- a/notes.org +++ b/notes.org @@ -294,3 +294,5 @@ To turn on off without disrupting flow too much use ~GLib.setenv("CLUTTER_SHOW_F ** 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. diff --git a/tiling.js b/tiling.js index 9615159a..ea03856a 100644 --- a/tiling.js +++ b/tiling.js @@ -2725,16 +2725,7 @@ function grabBegin(metaWindow, type) { case Meta.GrabOp.MOVING: inGrab = new Extension.imports.grab.MoveGrab(metaWindow, type); - let d; - if (Clutter.DeviceManager) { - let dm = Clutter.DeviceManager.get_default(); - d = dm.get_core_device(Clutter.InputDeviceType.KEYBOARD_DEVICE); - } else { - let backend = Clutter.get_default_backend() - let seat = backend.get_default_seat() - d = seat.get_keyboard() - } - if (d.get_modifier_state() & Clutter.ModifierType.CONTROL_MASK) { + if (utils.getModiferState() & Clutter.ModifierType.CONTROL_MASK) { inGrab.begin(); inGrab.beginDnD(); } else if (inGrab.initialSpace && inGrab.initialSpace.indexOf(metaWindow) > -1) { diff --git a/utils.js b/utils.js index bda0eac2..39d80225 100644 --- a/utils.js +++ b/utils.js @@ -80,6 +80,16 @@ function ppEnumValue(value, genum) { } } +function ppModiferState(state) { + let mods = []; + for (let [mod, mask] of Object.entries(imports.gi.Clutter.ModifierType)) { + if (mask & state) { + mods.push(mod); + } + } + return mods.join(", ") +} + /** * Look up the function by name at call time. This makes it convenient to * redefine the function without re-registering all signal handler, keybindings, @@ -264,6 +274,29 @@ function warpPointer(x, y) { } } +/** + * Return current modifiers state (or'ed Clutter.ModifierType.*) + * NB: Only on wayland. (Returns 0 on X11) + * + * Note: It's possible to get the modifier state through Gdk on X11, but move + * grabs is not triggered when ctrl is held down, making it useless for our purpose atm. + */ +function getModiferState() { + if (!Meta.is_wayland_compositor()) + return 0; + + let keyboard; + if (Clutter.DeviceManager) { + let dm = Clutter.DeviceManager.get_default(); + keyboard = dm.get_core_device(Clutter.InputDeviceType.KEYBOARD_DEVICE); + } else { + let backend = Clutter.get_default_backend(); + let seat = backend.get_default_seat(); + keyboard = seat.get_keyboard(); + } + return keyboard.get_modifier_state(); +} + function monitorOfPoint(x, y) { // get_monitor_index_for_rect "helpfully" returns the primary monitor index for out of bounds rects.. const Main = imports.ui.main; From 87de4aaf16d0397129f25b61d550a88188a4d817 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ole=20J=C3=B8rgen=20Br=C3=B8nner?= Date: Fri, 29 May 2020 16:10:20 +0200 Subject: [PATCH 058/104] Bugfix: (X11 only?) fix occationally initial jump when moving window The `global.get_pointer` is not necessarily in sync with the clutter motion event. We use `global.get_pointer` to set the anchor in `begin`, but sometimes (fast motions more likely to trigger) it's an extra event in the `motion` event queue. The motion handler will in effect see a reality that occured before the anchor was set, triggering a "jump". Also notice that `global.get_pointer` seems to be ahead of the actual grab event :/ meaning we get less responsive window moving. We don't have access the actual click coordinates which triggered the move :( --- grab.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/grab.js b/grab.js index 81eacee0..75c8c166 100644 --- a/grab.js +++ b/grab.js @@ -302,7 +302,8 @@ var MoveGrab = class MoveGrab { motion(actor, event) { let metaWindow = this.window; - let [gx, gy] = event.get_coords(); + // let [gx, gy] = event.get_coords(); + let [gx, gy, $] = global.get_pointer(); let [dx, dy] = this.pointerOffset; let clone = metaWindow.clone; From 5f8dc39d687c5b223ccb99335d7df1f973dcfb8e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ole=20J=C3=B8rgen=20Br=C3=B8nner?= Date: Fri, 29 May 2020 16:17:00 +0200 Subject: [PATCH 059/104] Hack to make clone marks survive grabs --- grab.js | 5 +++-- utils.js | 3 +++ 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/grab.js b/grab.js index 75c8c166..bd75508e 100644 --- a/grab.js +++ b/grab.js @@ -147,6 +147,7 @@ var MoveGrab = class MoveGrab { params.y = gy } + clone.__oldOpacity = clone.opacity Tweener.addTween(clone, params); this.signals.connect(global.stage, "button-press-event", this.end.bind(this)); @@ -369,7 +370,7 @@ var MoveGrab = class MoveGrab { time: prefs.animation_time, scale_x: 1, scale_y: 1, - opacity: 255 + opacity: clone.__oldOpacity || 255 }; if (this.dnd) { @@ -423,7 +424,7 @@ var MoveGrab = class MoveGrab { actor.set_scale(clone.scale_x, clone.scale_y); actor.opacity = clone.opacity; - clone.opacity = 255; + clone.opacity = clone.__oldOpacity || 255; clone.set_scale(1, 1); clone.set_pivot_point(0, 0); diff --git a/utils.js b/utils.js index 39d80225..c6d3508e 100644 --- a/utils.js +++ b/utils.js @@ -221,12 +221,15 @@ function toggleCloneMarks() { function markCloneOf(metaWindow) { if (metaWindow.clone) { metaWindow.clone.opacity = 190; + metaWindow.clone.__oldOpacity = 190; + metaWindow.clone.background_color = imports.gi.Clutter.color_from_string("red")[1]; } } function unmarkCloneOf(metaWindow) { if (metaWindow.clone) { metaWindow.clone.opacity = 255; + metaWindow.clone.__oldOpacity = 255; metaWindow.clone.background_color = null; } } From eb051c4a0db65a6efda6c554b2f46a31115720c3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ole=20J=C3=B8rgen=20Br=C3=B8nner?= Date: Fri, 29 May 2020 16:17:31 +0200 Subject: [PATCH 060/104] get_pointer seems to be a reliable way to get modifiers on both X11 and wayland --- utils.js | 19 ++----------------- 1 file changed, 2 insertions(+), 17 deletions(-) diff --git a/utils.js b/utils.js index c6d3508e..907ee39c 100644 --- a/utils.js +++ b/utils.js @@ -279,25 +279,10 @@ function warpPointer(x, y) { /** * Return current modifiers state (or'ed Clutter.ModifierType.*) - * NB: Only on wayland. (Returns 0 on X11) - * - * Note: It's possible to get the modifier state through Gdk on X11, but move - * grabs is not triggered when ctrl is held down, making it useless for our purpose atm. */ function getModiferState() { - if (!Meta.is_wayland_compositor()) - return 0; - - let keyboard; - if (Clutter.DeviceManager) { - let dm = Clutter.DeviceManager.get_default(); - keyboard = dm.get_core_device(Clutter.InputDeviceType.KEYBOARD_DEVICE); - } else { - let backend = Clutter.get_default_backend(); - let seat = backend.get_default_seat(); - keyboard = seat.get_keyboard(); - } - return keyboard.get_modifier_state(); + let [x, y, mods] = global.get_pointer(); + return mods; } function monitorOfPoint(x, y) { From 529fb80e0dee62b0b59ddebfa8cec9020722c054 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ole=20J=C3=B8rgen=20Br=C3=B8nner?= Date: Sun, 24 Nov 2019 12:03:37 +0100 Subject: [PATCH 061/104] playground --- virtTiling.js | 264 ++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 264 insertions(+) create mode 100644 virtTiling.js diff --git a/virtTiling.js b/virtTiling.js new file mode 100644 index 00000000..2b84db29 --- /dev/null +++ b/virtTiling.js @@ -0,0 +1,264 @@ +var Extension; +if (imports.misc.extensionUtils.extensions) { + Extension = imports.misc.extensionUtils.extensions["paperwm@hedning:matrix.org"]; +} else { + Extension = imports.ui.main.extensionManager.lookup("paperwm@hedning:matrix.org"); +} + +var Clutter = imports.gi.Clutter +var St = imports.gi.St + +var Tiling = Extension.imports.tiling +let fitProportionally = Tiling.fitProportionally + +let prefs = { + window_gap: 5, + minimum_margin: 3, +} + +function repl() { + virtStage.destroy() + let stageStyle = `background-color: white;` + let virtStage = new St.Widget({ + style: stageStyle, height: 200, width: 800 + }) + + let canvasStyle = `background-color: yellow;` + let canvas = new St.Widget({name: "canvas", style: canvasStyle, x: 5, y: 5}) + let monitorStyle = `background-color: blue;` + let monitor = new St.Widget({ + name: "monitor0", + style: monitorStyle, + x: 0, y: 0, width: 300, height: virtStage.height - 10 + }) + + let panel = new St.Widget({ + name: "panel", + style: `background-color: gray`, + x: 0, y: 0, + width: monitor.width, + height: 10 + + }) + let workArea = { + x: monitor.x, + y: monitor.y + panel.height, + width: monitor.width, + height: monitor.height - panel.height, + } + + let tilingContainer = new St.Widget() + + + global.stage.add_actor(virtStage) + virtStage.add_actor(canvas) + canvas.add_actor(monitor) + monitor.add_actor(panel) + + canvas.add_actor(tilingContainer) + virtStage.x = 1300 + + renderAndView( + tilingContainer, + layout( + fromSpace(space), + workArea, + prefs + ) + ) + + virtStage.show() + virtStage.hide() +} + + +function renderAndView(container, columns) { + let tiling = render(columns) + if (container.first_child) + container.first_child.destroy() + + container.add_actor(tiling) +} + +function fromSpace(space) { + return space.map( + col => col.map( + metaWindow => { + let f = metaWindow.get_frame_rect() + return { + width: f.width / 10, + height: f.height / 10, + } + } + ) + ) +} + +/** Render a dummy view of the windows */ +function render(columns) { + let windowStyle = `border: black solid 1px; background-color: red` + let tilingStyle = `background-color: yellow` + tilingStyle = "" + let tiling = new St.Widget({name: "tiling", style: tilingStyle}) + + function createWindowActor(window) { + return new St.Widget({ + style: windowStyle, + width: window.width, + height: window.height, + x: window.x, + y: window.y + }) + } + + for (let col of columns) { + for (let window of col) { + let windowActor = createWindowActor(window) + tiling.add_actor(windowActor) + } + } + return tiling +} + +function allocateDefault(column, availableHeight, preAllocatedWindow) { + if (column.length === 1) { + return [availableHeight]; + } else { + // Distribute available height amongst non-selected windows in proportion to their existing height + const gap = prefs.window_gap; + const minHeight = 20; + + function heightOf(window) { + return window.height + } + + const k = preAllocatedWindow && column.indexOf(preAllocatedWindow); + const selectedHeight = preAllocatedWindow && heightOf(preAllocatedWindow); + + let nonSelected = column.slice(); + if (preAllocatedWindow) nonSelected.splice(k, 1) + + const nonSelectedHeights = nonSelected.map(heightOf); + let availableForNonSelected = Math.max( + 0, + availableHeight + - (column.length-1) * gap + - (preAllocatedWindow ? selectedHeight : 0) + ); + + const deficit = Math.max( + 0, nonSelected.length * minHeight - availableForNonSelected); + + let heights = fitProportionally( + nonSelectedHeights, + availableForNonSelected + deficit + ); + + if (preAllocatedWindow) + heights.splice(k, 0, selectedHeight - deficit); + + return heights + } +} + +function allocateEqualHeight(column, available) { + available = available - (column.length-1)*prefs.window_gap; + return column.map(_ => Math.floor(available / column.length)); +} + +function layoutGrabColumn(column, x, y0, targetWidth, availableHeight, grabWindow) { + let needRelayout = false; + + function mosh(windows, height, y0) { + let targetHeights = fitProportionally( + windows.map(mw => mw.rect.height), + height + ); + let [w, y] = layoutColumnSimple(windows, x, y0, targetWidth, targetHeights); + return y; + } + + const k = column.indexOf(grabWindow); + if (k < 0) { + throw new Error("Anchor doesn't exist in column " + grabWindow.title); + } + + const gap = prefs.window_gap; + const f = grabWindow.globalRect(); + let yGrabRel = f.y - this.monitor.y; + targetWidth = f.width; + + const H1 = (yGrabRel - y0) - gap - (k-1)*gap; + const H2 = availableHeight - (yGrabRel + f.height - y0) - gap - (column.length-k-2)*gap; + k > 0 && mosh(column.slice(0, k), H1, y0); + let y = mosh(column.slice(k, k+1), f.height, yGrabRel); + k+1 < column.length && mosh(column.slice(k+1), H2, y); + + return targetWidth; +} + + +function layoutColumnSimple(windows, x, y0, targetWidth, targetHeights, time) { + let y = y0; + + for (let i = 0; i < windows.length; i++) { + let virtWindow = windows[i]; + let targetHeight = targetHeights[i]; + + virtWindow.x = x + virtWindow.y = y + virtWindow.width = targetWidth + virtWindow.height = targetHeight + + y += targetHeight + prefs.window_gap; + } + return targetWidth, y +} + + +/** + Mutates columns + */ +function layout(columns, workArea, prefs, options={}) { + let gap = prefs.window_gap; + let availableHeight = workArea.height + + let {inGrab, selectedWindow} = options + let selectedIndex = -1 + + if (selectedWindow) { + selectedIndex = columns.findIndex(col => col.includes(selectedWindow)) + } + + let y0 = workArea.y + let x = workArea.x + + for (let i = 0; i < columns.length; i++) { + let column = columns[i]; + + let selectedInColumn = i === selectedIndex ? selectedWindow : null; + + let targetWidth; + if (i === selectedIndex) { + targetWidth = selectedInColumn.width; + } else { + targetWidth = Math.max(...column.map(w => w.width)); + } + targetWidth = Math.min(targetWidth, workArea.width - 2*prefs.minimum_margin) + + let resultingWidth, relayout; + if (inGrab && i === selectedIndex) { + layoutGrabColumn(column, x, y0, targetWidth, availableHeight, selectedInColumn); + } else { + let allocator = options.customAllocators && options.customAllocators[i]; + allocator = allocator || allocateDefault; + + let targetHeights = allocator(column, availableHeight, selectedInColumn); + layoutColumnSimple(column, x, y0, targetWidth, targetHeights); + } + + x += targetWidth + gap; + } + + return columns +} From 0a995900f4052c2df3a37ca14e8f224ecb2fbfef Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ole=20J=C3=B8rgen=20Br=C3=B8nner?= Date: Sun, 24 Nov 2019 12:38:52 +0100 Subject: [PATCH 062/104] viewport positioning --- virtTiling.js | 38 ++++++++++++++++++++++++++++++++------ 1 file changed, 32 insertions(+), 6 deletions(-) diff --git a/virtTiling.js b/virtTiling.js index 2b84db29..6a5560c5 100644 --- a/virtTiling.js +++ b/virtTiling.js @@ -20,16 +20,17 @@ function repl() { virtStage.destroy() let stageStyle = `background-color: white;` let virtStage = new St.Widget({ - style: stageStyle, height: 200, width: 800 + style: stageStyle, height: 80, width: 800 }) let canvasStyle = `background-color: yellow;` + canvasStyle = "" let canvas = new St.Widget({name: "canvas", style: canvasStyle, x: 5, y: 5}) let monitorStyle = `background-color: blue;` let monitor = new St.Widget({ name: "monitor0", style: monitorStyle, - x: 0, y: 0, width: 300, height: virtStage.height - 10 + x: virtStage.width/2 - 300/2, y: 0, width: 300, height: virtStage.height - 10 }) let panel = new St.Widget({ @@ -56,7 +57,7 @@ function repl() { monitor.add_actor(panel) canvas.add_actor(tilingContainer) - virtStage.x = 1300 + virtStage.x = 1000 renderAndView( tilingContainer, @@ -67,10 +68,35 @@ function repl() { ) ) - virtStage.show() + let columns = layout( + fromSpace(space), + workArea, + prefs + ) + monitor.x + columns[1][0].x + movecolumntoviewportposition(tilingContainer, monitor, columns[1][0], 30) + virtStage.hide() + virtStage.show() +} + +/** tiling position given: + m_s: monitor position + w_m: window position (relative to monitor) + w_t: window position (relative to tiling) + */ +function t_s(m_s, w_m, w_t) { + return w_m - w_t + m_s } +/** + Calculates the tiling position such that column `k` is positioned at `x` + relative to the viewport (or workArea?) + */ +function movecolumntoviewportposition(tilingActor, viewport, window, x) { + tilingActor.x = t_s(viewport.x, x, window.x) +} function renderAndView(container, columns) { let tiling = render(columns) @@ -126,7 +152,7 @@ function allocateDefault(column, availableHeight, preAllocatedWindow) { } else { // Distribute available height amongst non-selected windows in proportion to their existing height const gap = prefs.window_gap; - const minHeight = 20; + const minHeight = 15; function heightOf(window) { return window.height @@ -231,7 +257,7 @@ function layout(columns, workArea, prefs, options={}) { } let y0 = workArea.y - let x = workArea.x + let x = 0 for (let i = 0; i < columns.length; i++) { let column = columns[i]; From 87b70bfd68f5a6b7605f174cd7ba6f99c4f05644 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ole=20J=C3=B8rgen=20Br=C3=B8nner?= Date: Sun, 24 Nov 2019 12:55:52 +0100 Subject: [PATCH 063/104] ... --- virtTiling.js | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/virtTiling.js b/virtTiling.js index 6a5560c5..6281f258 100644 --- a/virtTiling.js +++ b/virtTiling.js @@ -16,10 +16,14 @@ let prefs = { minimum_margin: 3, } +var virtStage = null + function repl() { - virtStage.destroy() + if (virtStage) + virtStage.destroy() + let stageStyle = `background-color: white;` - let virtStage = new St.Widget({ + virtStage = new St.Widget({ style: stageStyle, height: 80, width: 800 }) @@ -73,8 +77,7 @@ function repl() { workArea, prefs ) - monitor.x - columns[1][0].x + movecolumntoviewportposition(tilingContainer, monitor, columns[1][0], 30) virtStage.hide() @@ -123,8 +126,8 @@ function fromSpace(space) { /** Render a dummy view of the windows */ function render(columns) { let windowStyle = `border: black solid 1px; background-color: red` - let tilingStyle = `background-color: yellow` - tilingStyle = "" + let tilingStyle = `background-color: rgba(190, 190, 0, 0.5);` + // tilingStyle = "" let tiling = new St.Widget({name: "tiling", style: tilingStyle}) function createWindowActor(window) { From c1de5eb06ba4de052ea38468c24b67a1aa6bda9a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ole=20J=C3=B8rgen=20Br=C3=B8nner?= Date: Mon, 16 Mar 2020 08:55:36 +0100 Subject: [PATCH 064/104] WIP --- tiling.js | 46 ++++++++++++++++++++++++++++++++++++++++++---- utils.js | 14 ++++++++++++++ virtTiling.js | 1 + 3 files changed, 57 insertions(+), 4 deletions(-) diff --git a/tiling.js b/tiling.js index ea03856a..8cded28d 100644 --- a/tiling.js +++ b/tiling.js @@ -386,10 +386,14 @@ class Space extends Array { return; if (this._inLayout) return; + print("LAYOUT") this._inLayout = true; this.startAnimate(); let time = animate ? prefs.animation_time : 0; + if (window.instant) { + time = 0; + } let gap = prefs.window_gap; let x = 0; let selectedIndex = this.selectedIndex(); @@ -492,20 +496,43 @@ class Space extends Array { }); } - isVisible(metaWindow) { + // Space.prototype.isVisible = function + isVisible(metaWindow, margin=0) { let clone = metaWindow.clone; let x = clone.x + this.cloneContainer.x; let workArea = this.workArea(); let min = workArea.x; - if (x + clone.width < min - || x > min + workArea.width) { + if (x - margin + clone.width < min + || x + margin > min + workArea.width) { return false; } else { return true; } } + isFullyVisible(metaWindow) { + let clone = metaWindow.clone; + let x = clone.targetX + this.targetX; + let workArea = this.workArea(); + let min = workArea.x; + + return min <= x && x + clone.width < min + workArea.width; + } + + visibleRatio(metaWindow) { + + let clone = metaWindow.clone; + let x = clone.targetX + this.targetX; + let workArea = this.workArea(); + let min = workArea.x; + + let left = min - x + let right = x + clone.width + + return min <= x && x + clone.width < min + workArea.width; + } + isPlaceable(metaWindow) { let clone = metaWindow.clone; let x = clone.targetX + this.targetX; @@ -730,6 +757,9 @@ class Space extends Array { switch(direction) { let space = this; let index = space.selectedIndex(); + if (index === -1) { + return; + } let row = space[index].indexOf(space.selectedWindow); switch (direction) { case Meta.MotionDirection.RIGHT: @@ -1597,10 +1627,14 @@ class Spaces extends Map { let monitor = Scratch.focusMonitor(); let currentSpace = this.monitors.get(monitor); let i = display.get_monitor_neighbor_index(monitor.index, direction); + print("switch", utils.ppEnumValue(direction, Meta.DisplayDirection), i, monitor.index) + print("currentSpace", currentSpace.name) + print("focus window", global.display.focus_window) if (i === -1) return; let newMonitor = Main.layoutManager.monitors[i]; let space = this.monitors.get(newMonitor); + print("nextSpace", space.name) if (move && focus) { let metaWindow = focus.get_transient_for() || focus; @@ -1636,6 +1670,7 @@ class Spaces extends Map { let toSpace = this.spaceOf(to); let fromSpace = this.spaceOf(from); + print("switchWorkspace", fromSpace.monitor.index, toSpace.monitor.index) if (inGrab && inGrab.window) { inGrab.window.change_workspace(toSpace.workspace); } @@ -2254,6 +2289,9 @@ function resizeHandler(metaWindow) { if (inGrab && inGrab.window === metaWindow) return; + print("resize-handler-width", metaWindow.get_frame_rect().width) + // print("RESIZE\n", GLib.on_error_stack_trace(GLib.get_prgname())); + let f = metaWindow.get_frame_rect(); let needLayout = false; if (metaWindow._targetWidth !== f.width || metaWindow._targetHeight !== f.height) { @@ -2671,7 +2709,7 @@ function updateSelection(space, metaWindow) { * Move the column containing @meta_window to x, y and propagate the change * in @space. Coordinates are relative to monitor and y is optional. */ -function move_to(space, metaWindow, { x, y, force }) { +function move_to(space, metaWindow, { x, y, force, instant }) { if (space.indexOf(metaWindow) === -1) return; diff --git a/utils.js b/utils.js index 907ee39c..ef76094b 100644 --- a/utils.js +++ b/utils.js @@ -122,6 +122,20 @@ function findNext(cur, values, slack=0) { return values[0]; // cycle } +function arrayEqual(a, b) { + if (a === b) + return true; + if (!a || !b) + return false; + if (a.length !== b.length) + return false; + for (let i = 0; i < a.length; i++) { + if (a[i] !== b[i]) + return false; + } + return true; +} + function swap(array, i, j) { let temp = array[i]; array[i] = array[j]; diff --git a/virtTiling.js b/virtTiling.js index 6281f258..fc6492e5 100644 --- a/virtTiling.js +++ b/virtTiling.js @@ -82,6 +82,7 @@ function repl() { virtStage.hide() virtStage.show() + virtStage.y = 400 } /** tiling position given: From 465a6ea304b7dc4a9ba0ee17604601109d839e29 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ole=20J=C3=B8rgen=20Br=C3=B8nner?= Date: Sat, 28 Mar 2020 10:51:44 +0100 Subject: [PATCH 065/104] fixup! WIP --- virtTiling.js | 99 +++++++++++++++++++++++++++++---------------------- 1 file changed, 56 insertions(+), 43 deletions(-) diff --git a/virtTiling.js b/virtTiling.js index fc6492e5..210d5e78 100644 --- a/virtTiling.js +++ b/virtTiling.js @@ -8,7 +8,8 @@ if (imports.misc.extensionUtils.extensions) { var Clutter = imports.gi.Clutter var St = imports.gi.St -var Tiling = Extension.imports.tiling +var Tiling = Extension.imports.tiling; +var Utils = Extension.imports.utils; let fitProportionally = Tiling.fitProportionally let prefs = { @@ -22,19 +23,29 @@ function repl() { if (virtStage) virtStage.destroy() + let realMonitor = space.monitor + let scale = 0.10 + let padding = 10 + const monitorWidth = realMonitor.width * scale + const monitorHeight = realMonitor.height * scale let stageStyle = `background-color: white;` virtStage = new St.Widget({ - style: stageStyle, height: 80, width: 800 + name: "stage", + style: stageStyle, + height: monitorHeight + padding*2, + width: monitorWidth*3 }) - let canvasStyle = `background-color: yellow;` + // let canvasStyle = `background-color: yellow;` canvasStyle = "" - let canvas = new St.Widget({name: "canvas", style: canvasStyle, x: 5, y: 5}) + // let canvas = new St.Widget({name: "canvas", style: canvasStyle, x: 5, y: 5}) let monitorStyle = `background-color: blue;` let monitor = new St.Widget({ name: "monitor0", style: monitorStyle, - x: virtStage.width/2 - 300/2, y: 0, width: 300, height: virtStage.height - 10 + x: virtStage.width/2 - monitorWidth/2, y: padding, + width: monitorWidth, + height: virtStage.height - padding*2 }) let panel = new St.Widget({ @@ -47,44 +58,50 @@ function repl() { }) let workArea = { x: monitor.x, - y: monitor.y + panel.height, + y: panel.height, width: monitor.width, height: monitor.height - panel.height, } - let tilingContainer = new St.Widget() - + let tilingStyle = `background-color: rgba(190, 190, 0, 0.3);` + let tilingContainer = new St.Widget({name: "tiling", style: tilingStyle}) global.stage.add_actor(virtStage) - virtStage.add_actor(canvas) - canvas.add_actor(monitor) - monitor.add_actor(panel) + virtStage.x = 3000 + virtStage.y = 300 - canvas.add_actor(tilingContainer) - virtStage.x = 1000 + virtStage.add_actor(monitor) + monitor.add_actor(panel) + monitor.add_actor(tilingContainer) - renderAndView( - tilingContainer, - layout( - fromSpace(space), + function sync(space_=space) { + let columns = layout( + fromSpace(space_), workArea, prefs - ) - ) + ); + renderAndView( + tilingContainer, + columns + ); + tilingContainer.x = space_.targetX * scale; + } - let columns = layout( - fromSpace(space), - workArea, - prefs - ) + sync() + + Utils.printActorTree(virtStage, Utils.mkFmt({nameOnly: true})) movecolumntoviewportposition(tilingContainer, monitor, columns[1][0], 30) + // Utils.printA virtStage.hide() virtStage.show() virtStage.y = 400 } +Utils.printActorTree(space.actor, Utils.mkFmt({nameOnly: true})) +Utils.printActorTree(virtStage, Utils.mkFmt({nameOnly: true}), {collapseChains: false}) + /** tiling position given: m_s: monitor position w_m: window position (relative to monitor) @@ -99,37 +116,34 @@ function t_s(m_s, w_m, w_t) { relative to the viewport (or workArea?) */ function movecolumntoviewportposition(tilingActor, viewport, window, x) { - tilingActor.x = t_s(viewport.x, x, window.x) + tilingActor.x = t_s(viewport.x, x, window.x); } function renderAndView(container, columns) { - let tiling = render(columns) - if (container.first_child) - container.first_child.destroy() + for (let child of container.get_children()) { + child.destroy(); + } - container.add_actor(tiling) + render(columns, container); } -function fromSpace(space) { +function fromSpace(space, scale=0.1) { return space.map( col => col.map( metaWindow => { - let f = metaWindow.get_frame_rect() + let f = metaWindow.get_frame_rect(); return { - width: f.width / 10, - height: f.height / 10, - } + width: f.width * scale, + height: f.height * scale, + }; } ) ) } /** Render a dummy view of the windows */ -function render(columns) { - let windowStyle = `border: black solid 1px; background-color: red` - let tilingStyle = `background-color: rgba(190, 190, 0, 0.5);` - // tilingStyle = "" - let tiling = new St.Widget({name: "tiling", style: tilingStyle}) +function render(columns, tiling) { + const windowStyle = `border: black solid 1px; background-color: red`; function createWindowActor(window) { return new St.Widget({ @@ -138,16 +152,15 @@ function render(columns) { height: window.height, x: window.x, y: window.y - }) + }); } for (let col of columns) { for (let window of col) { - let windowActor = createWindowActor(window) - tiling.add_actor(windowActor) + let windowActor = createWindowActor(window); + tiling.add_actor(windowActor); } } - return tiling } function allocateDefault(column, availableHeight, preAllocatedWindow) { From 9a991db1374b719bbe5e180fad6b73d5c30164c7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ole=20J=C3=B8rgen=20Br=C3=B8nner?= Date: Sat, 28 Mar 2020 12:27:51 +0100 Subject: [PATCH 066/104] fixup! WIP --- virtTiling.js | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/virtTiling.js b/virtTiling.js index 210d5e78..b1ccb377 100644 --- a/virtTiling.js +++ b/virtTiling.js @@ -76,7 +76,7 @@ function repl() { function sync(space_=space) { let columns = layout( - fromSpace(space_), + fromSpace(space_, scale), workArea, prefs ); @@ -99,9 +99,6 @@ function repl() { virtStage.y = 400 } -Utils.printActorTree(space.actor, Utils.mkFmt({nameOnly: true})) -Utils.printActorTree(virtStage, Utils.mkFmt({nameOnly: true}), {collapseChains: false}) - /** tiling position given: m_s: monitor position w_m: window position (relative to monitor) @@ -127,7 +124,7 @@ function renderAndView(container, columns) { render(columns, container); } -function fromSpace(space, scale=0.1) { +function fromSpace(space, scale=1) { return space.map( col => col.map( metaWindow => { From 9e29438b372ac29797c72f7fcc0fa3cc7c2b56c2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ole=20J=C3=B8rgen=20Br=C3=B8nner?= Date: Sat, 28 Mar 2020 12:28:03 +0100 Subject: [PATCH 067/104] utils: fixes to printActorTree --- utils.js | 82 ++++++++++++++++++++++++++++++++++++++++++++++---------- 1 file changed, 68 insertions(+), 14 deletions(-) diff --git a/utils.js b/utils.js index ef76094b..c5950634 100644 --- a/utils.js +++ b/utils.js @@ -323,26 +323,80 @@ function indent(level, str) { } -function fmt(actor) { - let extra = [ - `${actor.get_position()}`, - `${actor.get_size()}`, - ]; - let metaWindow = actor.meta_window || actor.metaWindow; - if (metaWindow) { - metaWindow = `(mw: ${metaWindow.title})`; - extra.push(metaWindow); +function mkFmt({nameOnly}={nameOnly: false}) { + function defaultFmt(actor, prefix="") { + const fmtNum = num => num.toFixed(0); + let extra = [ + `${actor.get_position().map(fmtNum)}`, + `${actor.get_size().map(fmtNum)}`, + ]; + let metaWindow = actor.meta_window || actor.metaWindow; + if (metaWindow) { + metaWindow = `(mw: ${metaWindow.title})`; + extra.push(metaWindow); + } + const extraStr = extra.join(" | "); + let actorId = ""; + if (nameOnly) { + actorId = actor.name ? actor.name : (prefix.length == 0 ? "" : "#") + } else { + actorId = actor.toString(); + } + actorId = prefix+actorId + let spacing = actorId.length > 0 ? " " : "" + return `*${spacing}${actorId} ${extraStr}`; } - return `${actor.toString()} ${extra.join(" | ")}`; + return defaultFmt; } -function printActorTree(node, fmt=fmt, limit=Infnity, level=1) { - if (level > limit) { +function printActorTree(node, fmt=mkFmt(), options={}, state=null) { + state = Object.assign({}, (state || {level: 0, actorPrefix: ""})) + const defaultOptions = { + limit: 9999, + collapseChains: true, + }; + options = Object.assign(defaultOptions, options) + + if (state.level > options.limit) { return; } - print(indent(level, fmt(node))); + let collapse = false; + if (options.collapseChains) { + /* + a + b + s + t + c 30,10 + u + -> + a.b.s + a.b.t + a.b.c ... + u + + + */ + if (node.get_children().length > 0) { + if (node.x === 0 && node.y === 0) { + state.actorPrefix += (node.name ? node.name : "#") + "." + // print("#### ", state.actorPrefix) + collapse = true + } else { + collapse = false + } + } else { + collapse = false + } + } + if (!collapse) { + print(indent(state.level, fmt(node, state.actorPrefix))); + state.actorPrefix = ""; + state.level += 1; + } + for (let child of node.get_children()) { - printActorTree(child, fmt, limit, level+1); + printActorTree(child, fmt, options, state) } } From 6aba5726fc05f454cf90c63186ffaf77918b81b5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ole=20J=C3=B8rgen=20Br=C3=B8nner?= Date: Sat, 28 Mar 2020 12:28:31 +0100 Subject: [PATCH 068/104] Name some actors we use to aid debugging --- tiling.js | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/tiling.js b/tiling.js index 8cded28d..a74c2bb6 100644 --- a/tiling.js +++ b/tiling.js @@ -123,17 +123,17 @@ class Space extends Array { this._floating = []; this._populated = false; - let clip = new Clutter.Actor(); + let clip = new Clutter.Actor({name: "clip"}); this.clip = clip; - let actor = new Clutter.Actor(); + let actor = new Clutter.Actor({name: "space-actor"}); this._visible = true; this.hide(); // We keep the space actor hidden when inactive due to performance this.actor = actor; - let cloneClip = new Clutter.Actor(); + let cloneClip = new Clutter.Actor({name: "clone-clip"}); this.cloneClip = cloneClip; - let cloneContainer = new St.Widget(); + let cloneContainer = new St.Widget({name: "clone-container"}); this.cloneContainer = cloneContainer; // Pick up the same css as the top bar label @@ -166,7 +166,7 @@ class Space extends Array { actor.add_actor(cloneClip); cloneClip.add_actor(cloneContainer); - this.border = new St.Widget(); + this.border = new St.Widget({name: "border"}); this.actor.add_actor(this.border); this.border.hide(); @@ -1096,6 +1096,7 @@ box-shadow: 0px 0px 8px 0px rgba(0, 0, 0, .7); } this.background = new Meta.BackgroundActor( Object.assign({ + name: "background", monitor: monitor.index, reactive: true // Disable the background menu }, backgroundParams) From 80a9fb1e0b507fa43ecaba62d20254cf49b6d1dc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ole=20J=C3=B8rgen=20Br=C3=B8nner?= Date: Sat, 28 Mar 2020 12:30:28 +0100 Subject: [PATCH 069/104] examples/keybindings: small fix swap-neighbours --- examples/keybindings.js | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/examples/keybindings.js b/examples/keybindings.js index d3bb5b98..50f404a4 100644 --- a/examples/keybindings.js +++ b/examples/keybindings.js @@ -84,9 +84,10 @@ function swapNeighbours(binding = "y") { let space = Tiling.spaces.spaceOfWindow(mw) let i = space.indexOf(mw); if (space[i+1]) { - space.swap(Meta.MotionDirection.RIGHT, space[i+1][0]) + space.swap(Meta.MotionDirection.RIGHT, space[i+1][0]); + space[i+1].map(mw => mw.clone.raise_top()); } - }, {activeInNavigator: true}) + }, {activeInNavigator: true}); } function cycleMonitor(binding = "d") { From 9addaf8132197ccb6f070d51834e4467758d4e94 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ole=20J=C3=B8rgen=20Br=C3=B8nner?= Date: Sat, 28 Mar 2020 12:39:46 +0100 Subject: [PATCH 070/104] examples/keybindings: expand-available-width --- examples/keybindings.js | 39 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 39 insertions(+) diff --git a/examples/keybindings.js b/examples/keybindings.js index 50f404a4..a0bc3765 100644 --- a/examples/keybindings.js +++ b/examples/keybindings.js @@ -126,6 +126,45 @@ function cycleWorkspaceSettings(binding = "q") { ); } +function expand(binding = "l") { + var Tiling = Extension.imports.tiling; + + function findNonVisibleIndex(space, metaWindow, dir=1, margin=1) { + let k = space.indexOf(metaWindow) + dir; + while (0 <= k && k < space.length && space.isFullyVisible(space[k][0], margin)) { + k += dir; + } + return k + } + + function action(metaWindow) { + let space = Tiling.spaces.spaceOfWindow(metaWindow); + + let a = findNonVisibleIndex(space, metaWindow, -1); + let b = findNonVisibleIndex(space, metaWindow, 1); + + let leftMost = space[a+1][0]; + let availableLeft = space.targetX + leftMost.clone.targetX; + + let rightMost = space[b-1][0]; + let rightEdge = space.targetX + rightMost.clone.targetX + rightMost.clone.width; + let availableRight = space.width - rightEdge; + + let f = metaWindow.get_frame_rect(); + let available = f.width + availableRight + availableLeft - Tiling.prefs.horizontal_margin*2; + + if (a+1 === b-1) { + // We're the only window + Tiling.toggleMaximizeHorizontally(metaWindow) + } else { + metaWindow.move_resize_frame(true, f.x, f.y, available, f.height); + Tiling.move_to(space, space[a+1][0], { x: Tiling.prefs.horizontal_margin }) + } + } + + Keybindings.bindkey(binding, "expand-available-width", action, {activeInNavigator: true}); +} + function showNavigator(binding = "j") { Keybindings.bindkey(binding, "show-minimap", () => null, { opensMinimap: true }) } From fea0302c6bb5d8e0fa5fdd6eeade24e8f9c6bd96 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ole=20J=C3=B8rgen=20Br=C3=B8nner?= Date: Sat, 28 Mar 2020 13:46:58 +0100 Subject: [PATCH 071/104] examples/keybindings: cycle-layouts --- examples/keybindings.js | 95 +++++++++++++++++++++++++++++++++++++++++ utils.js | 5 +++ 2 files changed, 100 insertions(+) diff --git a/examples/keybindings.js b/examples/keybindings.js index a0bc3765..4c75a271 100644 --- a/examples/keybindings.js +++ b/examples/keybindings.js @@ -165,6 +165,101 @@ function expand(binding = "l") { Keybindings.bindkey(binding, "expand-available-width", action, {activeInNavigator: true}); } +function cycleLayouts(binding = "d") { + var Tiling = Extension.imports.tiling; + var Virt = Extension.imports.virtTiling; + var Tweener = Extension.imports.utils.tweener; + var Utils = Extension.imports.utils; + var prefs = Tiling.prefs; + + const splits = [ + [0.5, 0.5], + [0.7, 0.3], + [0.3, 0.7] + ]; + + function moveTo(space, metaWindow, target) { + space.startAnimate(); + space.targetX = target; + Tweener.addTween(space.cloneContainer, + { x: space.targetX, + time: prefs.animation_time, + onComplete: space.moveDone.bind(space) + }); + + space.fixOverlays(metaWindow); + } + + function action(metaWindow, space, {navigator}={}) { + const m = 50 + space = Tiling.spaces.spaceOfWindow(metaWindow); + + prefs = {...prefs, minimum_margin: Tiling.minimumMargin()} + + const tiling = Virt.layout(Virt.fromSpace(space), space.workArea(), prefs); + + function resize(i, width) { + for (let w of tiling[i]) { + w.width = width; + } + } + + let k = space.indexOf(metaWindow); + let next = space.length > k+1 && space.isVisible(space[k+1][0], m) && space[k+1][0]; + let prev = k > 0 && space.isVisible(space[k-1][0], m) && space[k-1][0]; + + + let neighbour = next || prev; + let f = metaWindow.get_frame_rect(); + let f2 = neighbour.get_frame_rect(); + + let neighbourK = space.indexOf(neighbour); + + let available = space.width - Tiling.prefs.horizontal_margin*2 - Tiling.prefs.window_gap; + + let s1 = f.width / available; + let s2 = f2.width / available; + + let state; + if (!navigator["cycle-layouts"]) { + navigator["cycle-layouts"] = {i: Utils.eq(s1, splits[0][0]) ? 1 : 0 } + } + state = navigator["cycle-layouts"]; + + let [a, b] = splits[(state.i++) % splits.length]; + + let metaWindowWidth = Math.round(available * a);; + metaWindow.move_resize_frame(true, f.x, f.y, metaWindowWidth, f.height); + resize(k, metaWindowWidth); + + let neighbourWidth = Math.round(available * b); + neighbour.move_resize_frame(true, f2.x, f2.y, neighbourWidth, f2.height); + resize(neighbourK, neighbourWidth); + + Virt.layout(tiling, space.workArea(), prefs); + + let margin = Tiling.prefs.horizontal_margin; + let width = f.width; + let workarea = space.workArea(); + let wax = workarea.x - space.monitor.x; + + let leftSnapPos = wax + margin; + let rightSnapPos = wax + workarea.width - metaWindowWidth - margin; + + if (neighbour == next) { + print("next", metaWindow.title, leftSnapPos, tiling[k][0].x); + moveTo(space, metaWindow, leftSnapPos - tiling[k][0].x); + // Tiling.move_to(space, metaWindow, {x: leftSnapPos}); + } else { + print("prev", neighbour.title, rightSnapPos, tiling[neighbourK][0].x); + moveTo(space, neighbour, leftSnapPos - tiling[neighbourK][0].x); + // Tiling.move_to(space, neighbour, {x: leftSnapPos}); + } + } + + Keybindings.bindkey(binding, "cycle-layouts", action, { opensNavigator: true }) +} + function showNavigator(binding = "j") { Keybindings.bindkey(binding, "show-minimap", () => null, { opensMinimap: true }) } diff --git a/utils.js b/utils.js index c5950634..16706af3 100644 --- a/utils.js +++ b/utils.js @@ -136,6 +136,11 @@ function arrayEqual(a, b) { return true; } +/** Is the floating point numbers equal enough */ +function eq(a, b, epsilon=0.00000001) { + return Math.abs(a-b) < epsilon; +} + function swap(array, i, j) { let temp = array[i]; array[i] = array[j]; From 618726ba00fc2bd50f4dc29034f85fc3f007aadc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ole=20J=C3=B8rgen=20Br=C3=B8nner?= Date: Sat, 30 May 2020 12:28:47 +0200 Subject: [PATCH 072/104] refactor: move minimumMargin function to a getter prop on `prefs` Simplify passing a complete prefs object around. --- settings.js | 3 +++ tiling.js | 18 ++++++++---------- 2 files changed, 11 insertions(+), 10 deletions(-) diff --git a/settings.js b/settings.js index 31187dfe..31a671c9 100644 --- a/settings.js +++ b/settings.js @@ -37,6 +37,9 @@ var prefs = {}; 'cycle-width-steps', 'cycle-height-steps', 'topbar-follow-focus'] .forEach((k) => setState(null, k)); +prefs.__defineGetter__("minimum_margin", function() { return Math.min(15, this.horizontal_margin) }); + + function setVerticalMargin() { let vMargin = settings.get_int('vertical-margin'); let gap = settings.get_int('window-gap'); diff --git a/tiling.js b/tiling.js index a74c2bb6..3c6144a8 100644 --- a/tiling.js +++ b/tiling.js @@ -42,8 +42,6 @@ var backgroundSettings = new Gio.Settings({ var borderWidth = 8; // Mutter prevints windows from being placed further off the screen than 75 pixels. var stack_margin = 75; -// Minimum margin -var minimumMargin = () => Math.min(15, prefs.horizontal_margin); // Some features use this to determine if to sizes is considered equal. ie. `abs(w1 - w2) < sizeSlack` var sizeSlack = 30; @@ -422,7 +420,7 @@ class Space extends Array { } else { targetWidth = Math.max(...column.map(w => w.get_frame_rect().width)); } - targetWidth = Math.min(targetWidth, workArea.width - 2*minimumMargin()); + targetWidth = Math.min(targetWidth, workArea.width - 2*prefs.minimum_margin); let resultingWidth, relayout; let allocator = options.customAllocators && options.customAllocators[i]; @@ -2640,10 +2638,10 @@ function ensuredX(meta_window, space) { } else if (x + frame.width === max) { // When opening new windows at the end, in the background, we want to // show some minimup margin - x = max - minimumMargin() - frame.width; + x = max - prefs.minimum_margin - frame.width; } else if (x === min) { // Same for the start (though the case isn't as common) - x = min + minimumMargin(); + x = min + prefs.minimum_margin; } return x; @@ -2944,7 +2942,7 @@ function toggleMaximizeHorizontally(metaWindow) { let space = spaces.spaceOfWindow(metaWindow); let workArea = space.workArea(); let frame = metaWindow.get_frame_rect(); - let reqWidth = workArea.width - minimumMargin()*2; + let reqWidth = workArea.width - prefs.minimum_margin*2; // Some windows only resize in increments > 1px so we can't rely on a precise width // Hopefully this heuristic is good enough @@ -2958,9 +2956,9 @@ function toggleMaximizeHorizontally(metaWindow) { metaWindow.unmaximizedRect = null; } else { - let x = workArea.x + space.monitor.x + minimumMargin(); + let x = workArea.x + space.monitor.x + prefs.minimum_margin; metaWindow.unmaximizedRect = frame; - metaWindow.move_resize_frame(true, x, frame.y, workArea.width - minimumMargin()*2, frame.height); + metaWindow.move_resize_frame(true, x, frame.y, workArea.width - prefs.minimum_margin*2, frame.height); } } @@ -3067,9 +3065,9 @@ function cycleWindowWidth(metaWindow) { let targetX = frame.x; if (Scratch.isScratchWindow(metaWindow)) { - if (targetX+targetWidth > workArea.x + workArea.width - minimumMargin()) { + if (targetX+targetWidth > workArea.x + workArea.width - prefs.minimum_margin) { // Move the window so it remains fully visible - targetX = workArea.x + workArea.width - minimumMargin() - targetWidth; + targetX = workArea.x + workArea.width - prefs.minimum_margin - targetWidth; } } From d9bd8f972aaad0ef711014179a1b188ad597e8e7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ole=20J=C3=B8rgen=20Br=C3=B8nner?= Date: Sat, 30 May 2020 12:29:50 +0200 Subject: [PATCH 073/104] WIP: - move most layout related function into separate file - restructure a bit - create cycleLayoutDir to allow explict control over which neighbour to use - add bindings for expandOrShrink (aka. "fit") right neighbour --- examples/keybindings.js | 164 +------------------------ examples/layouts.js | 260 ++++++++++++++++++++++++++++++++++++++++ virtTiling.js | 4 - 3 files changed, 262 insertions(+), 166 deletions(-) create mode 100644 examples/layouts.js diff --git a/examples/keybindings.js b/examples/keybindings.js index 4c75a271..49de07cd 100644 --- a/examples/keybindings.js +++ b/examples/keybindings.js @@ -126,139 +126,6 @@ function cycleWorkspaceSettings(binding = "q") { ); } -function expand(binding = "l") { - var Tiling = Extension.imports.tiling; - - function findNonVisibleIndex(space, metaWindow, dir=1, margin=1) { - let k = space.indexOf(metaWindow) + dir; - while (0 <= k && k < space.length && space.isFullyVisible(space[k][0], margin)) { - k += dir; - } - return k - } - - function action(metaWindow) { - let space = Tiling.spaces.spaceOfWindow(metaWindow); - - let a = findNonVisibleIndex(space, metaWindow, -1); - let b = findNonVisibleIndex(space, metaWindow, 1); - - let leftMost = space[a+1][0]; - let availableLeft = space.targetX + leftMost.clone.targetX; - - let rightMost = space[b-1][0]; - let rightEdge = space.targetX + rightMost.clone.targetX + rightMost.clone.width; - let availableRight = space.width - rightEdge; - - let f = metaWindow.get_frame_rect(); - let available = f.width + availableRight + availableLeft - Tiling.prefs.horizontal_margin*2; - - if (a+1 === b-1) { - // We're the only window - Tiling.toggleMaximizeHorizontally(metaWindow) - } else { - metaWindow.move_resize_frame(true, f.x, f.y, available, f.height); - Tiling.move_to(space, space[a+1][0], { x: Tiling.prefs.horizontal_margin }) - } - } - - Keybindings.bindkey(binding, "expand-available-width", action, {activeInNavigator: true}); -} - -function cycleLayouts(binding = "d") { - var Tiling = Extension.imports.tiling; - var Virt = Extension.imports.virtTiling; - var Tweener = Extension.imports.utils.tweener; - var Utils = Extension.imports.utils; - var prefs = Tiling.prefs; - - const splits = [ - [0.5, 0.5], - [0.7, 0.3], - [0.3, 0.7] - ]; - - function moveTo(space, metaWindow, target) { - space.startAnimate(); - space.targetX = target; - Tweener.addTween(space.cloneContainer, - { x: space.targetX, - time: prefs.animation_time, - onComplete: space.moveDone.bind(space) - }); - - space.fixOverlays(metaWindow); - } - - function action(metaWindow, space, {navigator}={}) { - const m = 50 - space = Tiling.spaces.spaceOfWindow(metaWindow); - - prefs = {...prefs, minimum_margin: Tiling.minimumMargin()} - - const tiling = Virt.layout(Virt.fromSpace(space), space.workArea(), prefs); - - function resize(i, width) { - for (let w of tiling[i]) { - w.width = width; - } - } - - let k = space.indexOf(metaWindow); - let next = space.length > k+1 && space.isVisible(space[k+1][0], m) && space[k+1][0]; - let prev = k > 0 && space.isVisible(space[k-1][0], m) && space[k-1][0]; - - - let neighbour = next || prev; - let f = metaWindow.get_frame_rect(); - let f2 = neighbour.get_frame_rect(); - - let neighbourK = space.indexOf(neighbour); - - let available = space.width - Tiling.prefs.horizontal_margin*2 - Tiling.prefs.window_gap; - - let s1 = f.width / available; - let s2 = f2.width / available; - - let state; - if (!navigator["cycle-layouts"]) { - navigator["cycle-layouts"] = {i: Utils.eq(s1, splits[0][0]) ? 1 : 0 } - } - state = navigator["cycle-layouts"]; - - let [a, b] = splits[(state.i++) % splits.length]; - - let metaWindowWidth = Math.round(available * a);; - metaWindow.move_resize_frame(true, f.x, f.y, metaWindowWidth, f.height); - resize(k, metaWindowWidth); - - let neighbourWidth = Math.round(available * b); - neighbour.move_resize_frame(true, f2.x, f2.y, neighbourWidth, f2.height); - resize(neighbourK, neighbourWidth); - - Virt.layout(tiling, space.workArea(), prefs); - - let margin = Tiling.prefs.horizontal_margin; - let width = f.width; - let workarea = space.workArea(); - let wax = workarea.x - space.monitor.x; - - let leftSnapPos = wax + margin; - let rightSnapPos = wax + workarea.width - metaWindowWidth - margin; - - if (neighbour == next) { - print("next", metaWindow.title, leftSnapPos, tiling[k][0].x); - moveTo(space, metaWindow, leftSnapPos - tiling[k][0].x); - // Tiling.move_to(space, metaWindow, {x: leftSnapPos}); - } else { - print("prev", neighbour.title, rightSnapPos, tiling[neighbourK][0].x); - moveTo(space, neighbour, leftSnapPos - tiling[neighbourK][0].x); - // Tiling.move_to(space, neighbour, {x: leftSnapPos}); - } - } - - Keybindings.bindkey(binding, "cycle-layouts", action, { opensNavigator: true }) -} function showNavigator(binding = "j") { Keybindings.bindkey(binding, "show-minimap", () => null, { opensMinimap: true }) @@ -341,37 +208,10 @@ function adjustWidth(incBinding="plus", decBinding="minus", increm Keybindings.bindkey(decBinding, "dec-width", adjuster(-increment)); } -function tileInto(leftBinding="less", rightBinding="less") { - // less: '<' - let Tiling = Extension.imports.tiling; - - const tileIntoDirection = (dir=-1) => (metaWindow) => { - let space = Tiling.spaces.spaceOfWindow(metaWindow); - let jFrom = space.indexOf(metaWindow); - let jTo = jFrom + dir; - if (jTo < 0 || jTo >= space.length) - return; - - space[jFrom].splice(space.rowOf(metaWindow), 1); - space[jTo].push(metaWindow); - - if (space[jFrom].length === 0) { - space.splice(jFrom, 1); - } - space.layout(true, { - customAllocators: { [space.indexOf(metaWindow)]: Tiling.allocateEqualHeight } - }); - space.emit("full-layout"); - } - - let options = { activeInNavigator: true }; - if (leftBinding) - Keybindings.bindkey(leftBinding, "tile-into-left-column", tileIntoDirection(-1), options); - if (rightBinding) - Keybindings.bindkey(rightBinding, "tile-into-right-column", tileIntoDirection(1), options); +function tileInto(leftBinding="less", rightBinding="less") { + Extension.imports.examples.layouts.bindTileInto(leftBinding, rightBinding); } - function cycleEdgeSnap(binding = "u") { var Tiling = Extension.imports.tiling; var Meta = imports.gi.Meta; diff --git a/examples/layouts.js b/examples/layouts.js new file mode 100644 index 00000000..466ea92d --- /dev/null +++ b/examples/layouts.js @@ -0,0 +1,260 @@ +var Extension; +if (imports.misc.extensionUtils.extensions) { + Extension = imports.misc.extensionUtils.extensions["paperwm@hedning:matrix.org"]; +} else { + Extension = imports.ui.main.extensionManager.lookup("paperwm@hedning:matrix.org"); +} +var Keybindings = Extension.imports.keybindings; +var Main = imports.ui.main; +var Tiling = Extension.imports.tiling; +var Scratch = Extension.imports.scratch; +var Virt = Extension.imports.virtTiling; +var Tweener = Extension.imports.utils.tweener; +var Utils = Extension.imports.utils; +var prefs = Tiling.prefs; + + +/** Adapts an action handler to operate on the neighbour in the given direction */ +function useNeigbour(dir, action) { + return (metaWindow) => { + let space = Tiling.spaces.spaceOfWindow(metaWindow) + let i = space.indexOf(metaWindow) + if (!space[i+dir]) + return action(undefined) + + return action(space[i+dir][0]) + } +} + +/** Find the index of the first not fully visible column in the given direction */ +function findNonVisibleIndex(space, metaWindow, dir=1, margin=1) { + let k = space.indexOf(metaWindow) + dir; + while (0 <= k && k < space.length && space.isFullyVisible(space[k][0], margin)) { + k += dir; + } + return k +} + +function moveTo(space, metaWindow, target) { + space.startAnimate(); + space.targetX = target; + Tweener.addTween(space.cloneContainer, + { x: space.targetX, + time: prefs.animation_time, + onComplete: space.moveDone.bind(space) + }); + + space.fixOverlays(); +} + +function getLeftSnapPosition(space) { + let margin = Tiling.prefs.horizontal_margin; + let workarea = space.workArea(); + let wax = workarea.x - space.monitor.x; + + return wax + margin; +} + +function getSnapPositions(space, windowWidth) { + let margin = Tiling.prefs.horizontal_margin; + let workarea = space.workArea(); + let wax = workarea.x - space.monitor.x; + + let leftSnapPos = wax + margin; + let rightSnapPos = wax + workarea.width - windowWidth - margin; + return [leftSnapPos, rightSnapPos] +} + +function mkVirtTiling(space) { + return Virt.layout(Virt.fromSpace(space), space.workArea(), prefs); +} + +function moveToViewport(space, tiling, i, vx) { + moveTo(space, null, vx - tiling[i][0].x); +} + +function resize(tiling, i, width) { + for (let w of tiling[i]) { + w.width = width; + } +} + + +////// Actions + + +/** + Expands or shrinks the window to fit the available viewport space. + Available space is space not occupied by fully visible windows + Will move the tiling as necessary. + */ +function fitAvailable(metaWindow) { + // TERMINOLOGY: mold-into ? + let space = Tiling.spaces.spaceOfWindow(metaWindow); + + let a = findNonVisibleIndex(space, metaWindow, -1); + let b = findNonVisibleIndex(space, metaWindow, 1); + + let leftMost = space[a+1][0]; + let availableLeft = space.targetX + leftMost.clone.targetX; + + let rightMost = space[b-1][0]; + let rightEdge = space.targetX + rightMost.clone.targetX + rightMost.clone.width; + let availableRight = space.width - rightEdge; + + let f = metaWindow.get_frame_rect(); + let available = f.width + availableRight + availableLeft - Tiling.prefs.horizontal_margin*2; + + if (a+1 === b-1) { + // We're the only window + Tiling.toggleMaximizeHorizontally(metaWindow) + } else { + metaWindow.move_resize_frame(true, f.x, f.y, available, f.height); + Tiling.move_to(space, space[a+1][0], { x: Tiling.prefs.horizontal_margin }) + } +} + + + +function cycleLayoutDirection(dir) { + + const splits = [ + [0.5, 0.5], + [0.7, 0.3], + [0.3, 0.7] + ]; + + return (metaWindow, space, {navigator}={}) => { + let k = space.indexOf(metaWindow) + let j = k+dir + let neighbourCol = space[j] + if (!neighbourCol) + return + + let neighbour = neighbourCol[0] + + let tiling = mkVirtTiling(space) + + let available = space.width - Tiling.prefs.horizontal_margin*2 - Tiling.prefs.window_gap; + + let f1 = metaWindow.get_frame_rect(); + let f2 = neighbour.get_frame_rect(); + + let s1 = f1.width / available; + let s2 = f2.width / available; + + let state; + if (!navigator["cycle-layouts"]) { + navigator["cycle-layouts"] = {i: Utils.eq(s1, splits[0][0]) ? 1 : 0 } + } + state = navigator["cycle-layouts"]; + + let [a, b] = splits[state.i % splits.length]; + state.i++; + + let metaWindowWidth = Math.round(available * a);; + metaWindow.move_resize_frame(true, f1.x, f1.y, metaWindowWidth, f1.height); + resize(tiling, k, metaWindowWidth); + + let neighbourWidth = Math.round(available * b); + neighbour.move_resize_frame(true, f2.x, f2.y, neighbourWidth, f2.height); + resize(tiling, j, neighbourWidth); + + Virt.layout(tiling, space.workArea(), prefs); + + let snapLeft = getLeftSnapPosition(space) + + if (dir === 1) + moveToViewport(space, tiling, k, snapLeft); + else + moveToViewport(space, tiling, j, snapLeft); + } +} + +function cycleLayouts(binding = "d") { + function action(metaWindow, space, {navigator}={}) { + const m = 50 + space = Tiling.spaces.spaceOfWindow(metaWindow); + + let k = space.indexOf(metaWindow); + let next = space.length > k+1 && space.isVisible(space[k+1][0], m) && space[k+1][0]; + let prev = k > 0 && space.isVisible(space[k-1][0], m) && space[k-1][0]; + + let neighbour = next || prev; + + if (neighbour === next) { + return cycleLayoutDirection(1)(metaWindow, space, {navigator}) + } else { + return cycleLayoutDirection(-1)(metaWindow, space, {navigator}) + } + } + + Keybindings.bindkey(binding, "cycle-layouts", action, { opensNavigator: true }); +} + + +function tileInto(dir=-1) { + return (metaWindow, space) => { + space = space || Tiling.spaces.spaceOfWindow(metaWindow); + let jFrom = space.indexOf(metaWindow); + if (space[jFrom].length > 1) { + return tileOut(dir)(metaWindow, space); + } + let jTo = jFrom + dir; + if (jTo < 0 || jTo >= space.length) + return; + + space[jFrom].splice(space.rowOf(metaWindow), 1); + space[jTo].push(metaWindow); + + if (space[jFrom].length === 0) { + space.splice(jFrom, 1); + } + space.layout(true, { + customAllocators: { [space.indexOf(metaWindow)]: Tiling.allocateEqualHeight } + }); + space.emit("full-layout"); + } +} + +function tileOut(dir) { + return (metaWindow, space) => { + space = space || Tiling.spaces.spaceOfWindow(metaWindow); + let [j, i] = space.positionOf(metaWindow); + if (space[j].length === 0) + return; + + space[j].splice(i, 1); + space.splice(j + (dir === 1 ? 1 : 0), 0, [metaWindow]); + space.layout(); + space.emit("full-layout"); + space.fixOverlays(); + } +} + + +////// Bindings + +function bindTileInto(leftBinding="Left", rightBinding="Right") { + let options = { activeInNavigator: true }; + if (leftBinding) + Keybindings.bindkey(leftBinding, "tile-into-left-column", tileInto(-1), options); + if (rightBinding) + Keybindings.bindkey(rightBinding, "tile-into-right-column", tileInto(1), options); +} + +function bindTileOut(left="k", right="l") { + Keybindings.bindkey(left, "tile-out-left", tileOut(-1), {activeInNavigator: true}); + Keybindings.bindkey(right, "tile-out-right", tileOut(1), {activeInNavigator: true}); +} + + +function bindFitAvailable(focus = "l", right="l") { + Keybindings.bindkey(focus, "fit-available-width", fitAvailable, {activeInNavigator: true}); + Keybindings.bindkey(right, "fit-available-width-right", useNeigbour(1, fitAvailable), {activeInNavigator: true}); +} + +function bindCycleLayoutDirection(left="d", right="d") { + Keybindings.bindkey(left, "cycle-layout-left", cycleLayoutDirection(-1), { opensNavigator: true }); + Keybindings.bindkey(right, "cycle-layout-right", cycleLayoutDirection(1), { opensNavigator: true }); +} diff --git a/virtTiling.js b/virtTiling.js index b1ccb377..4230e81f 100644 --- a/virtTiling.js +++ b/virtTiling.js @@ -36,9 +36,6 @@ function repl() { width: monitorWidth*3 }) - // let canvasStyle = `background-color: yellow;` - canvasStyle = "" - // let canvas = new St.Widget({name: "canvas", style: canvasStyle, x: 5, y: 5}) let monitorStyle = `background-color: blue;` let monitor = new St.Widget({ name: "monitor0", @@ -92,7 +89,6 @@ function repl() { Utils.printActorTree(virtStage, Utils.mkFmt({nameOnly: true})) movecolumntoviewportposition(tilingContainer, monitor, columns[1][0], 30) - // Utils.printA virtStage.hide() virtStage.show() From 01201b3b152bf32f76668b3a3cd340b401bcb071 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ole=20J=C3=B8rgen=20Br=C3=B8nner?= Date: Sat, 30 May 2020 12:42:17 +0200 Subject: [PATCH 074/104] semicolons --- examples/layouts.js | 32 ++++++++++++++++---------------- 1 file changed, 16 insertions(+), 16 deletions(-) diff --git a/examples/layouts.js b/examples/layouts.js index 466ea92d..855ffdda 100644 --- a/examples/layouts.js +++ b/examples/layouts.js @@ -17,12 +17,12 @@ var prefs = Tiling.prefs; /** Adapts an action handler to operate on the neighbour in the given direction */ function useNeigbour(dir, action) { return (metaWindow) => { - let space = Tiling.spaces.spaceOfWindow(metaWindow) - let i = space.indexOf(metaWindow) + let space = Tiling.spaces.spaceOfWindow(metaWindow); + let i = space.indexOf(metaWindow); if (!space[i+dir]) - return action(undefined) + return action(undefined); - return action(space[i+dir][0]) + return action(space[i+dir][0]); } } @@ -107,10 +107,10 @@ function fitAvailable(metaWindow) { if (a+1 === b-1) { // We're the only window - Tiling.toggleMaximizeHorizontally(metaWindow) + Tiling.toggleMaximizeHorizontally(metaWindow); } else { metaWindow.move_resize_frame(true, f.x, f.y, available, f.height); - Tiling.move_to(space, space[a+1][0], { x: Tiling.prefs.horizontal_margin }) + Tiling.move_to(space, space[a+1][0], { x: Tiling.prefs.horizontal_margin }); } } @@ -125,13 +125,13 @@ function cycleLayoutDirection(dir) { ]; return (metaWindow, space, {navigator}={}) => { - let k = space.indexOf(metaWindow) - let j = k+dir - let neighbourCol = space[j] + let k = space.indexOf(metaWindow); + let j = k+dir; + let neighbourCol = space[j]; if (!neighbourCol) - return + return; - let neighbour = neighbourCol[0] + let neighbour = neighbourCol[0]; let tiling = mkVirtTiling(space) @@ -145,7 +145,7 @@ function cycleLayoutDirection(dir) { let state; if (!navigator["cycle-layouts"]) { - navigator["cycle-layouts"] = {i: Utils.eq(s1, splits[0][0]) ? 1 : 0 } + navigator["cycle-layouts"] = {i: Utils.eq(s1, splits[0][0]) ? 1 : 0 }; } state = navigator["cycle-layouts"]; @@ -162,7 +162,7 @@ function cycleLayoutDirection(dir) { Virt.layout(tiling, space.workArea(), prefs); - let snapLeft = getLeftSnapPosition(space) + let snapLeft = getLeftSnapPosition(space); if (dir === 1) moveToViewport(space, tiling, k, snapLeft); @@ -173,7 +173,7 @@ function cycleLayoutDirection(dir) { function cycleLayouts(binding = "d") { function action(metaWindow, space, {navigator}={}) { - const m = 50 + const m = 50; space = Tiling.spaces.spaceOfWindow(metaWindow); let k = space.indexOf(metaWindow); @@ -183,9 +183,9 @@ function cycleLayouts(binding = "d") { let neighbour = next || prev; if (neighbour === next) { - return cycleLayoutDirection(1)(metaWindow, space, {navigator}) + return cycleLayoutDirection(1)(metaWindow, space, {navigator}); } else { - return cycleLayoutDirection(-1)(metaWindow, space, {navigator}) + return cycleLayoutDirection(-1)(metaWindow, space, {navigator}); } } From f6e9ab4d901e00ade4acb1caef28e33b31540c59 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ole=20J=C3=B8rgen=20Br=C3=B8nner?= Date: Sat, 30 May 2020 12:50:21 +0200 Subject: [PATCH 075/104] add left binding for fit available --- examples/layouts.js | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/examples/layouts.js b/examples/layouts.js index 855ffdda..c2fc4a1a 100644 --- a/examples/layouts.js +++ b/examples/layouts.js @@ -249,9 +249,10 @@ function bindTileOut(left="k", right="l") { } -function bindFitAvailable(focus = "l", right="l") { - Keybindings.bindkey(focus, "fit-available-width", fitAvailable, {activeInNavigator: true}); - Keybindings.bindkey(right, "fit-available-width-right", useNeigbour(1, fitAvailable), {activeInNavigator: true}); +function bindFitAvailable(left="j", focus = "k", right="l") { + left && Keybindings.bindkey(left, "fit-available-width-left", useNeigbour(-1, fitAvailable), {activeInNavigator: true}); + focus && Keybindings.bindkey(focus, "fit-available-width", fitAvailable, {activeInNavigator: true}); + right && Keybindings.bindkey(right, "fit-available-width-right", useNeigbour(1, fitAvailable), {activeInNavigator: true}); } function bindCycleLayoutDirection(left="d", right="d") { From aeda0cf2909a85e96bb9d5411300693e833f1245 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tor=20Hedin=20Br=C3=B8nner?= Date: Mon, 5 Apr 2021 11:46:38 +0200 Subject: [PATCH 076/104] Polyfill version info for gnome 40 --- kludges.js | 4 ++-- utils.js | 3 +++ 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/kludges.js b/kludges.js index 737be313..8c870c4f 100644 --- a/kludges.js +++ b/kludges.js @@ -12,7 +12,6 @@ if (imports.misc.extensionUtils.extensions) { Extension = imports.ui.main.extensionManager.lookup("paperwm@hedning:matrix.org"); } - var Meta = imports.gi.Meta; var Gio = imports.gi.Gio; var Main = imports.ui.main; @@ -29,6 +28,8 @@ var settings = Convenience.getSettings(); var Clutter = imports.gi.Clutter; let St = imports.gi.St; +var version = Extension.imports.utils.version + function overrideHotCorners() { for (let corner of Main.layoutManager.hotCorners) { if (!corner) @@ -88,7 +89,6 @@ if (!Clutter.Actor.prototype.set) { } // Polyfill gnome-3.34 transition API, taken from gnome-shell/js/ui/environment.js -const version = imports.misc.config.PACKAGE_VERSION.split('.'); if (version[0] >= 3 && version[1] < 34) { function _makeEaseCallback(params, cleanup) { let onComplete = params.onComplete; diff --git a/utils.js b/utils.js index 16706af3..50ae3fac 100644 --- a/utils.js +++ b/utils.js @@ -10,6 +10,9 @@ var workspaceManager = global.workspace_manager; var display = global.display; var version = imports.misc.config.PACKAGE_VERSION.split('.').map(Number); +if (version[0] !== 3) { + version = [3, ...version] +} var registerClass; { From 61546c4970835e7ab2cf0c682b97712429b650b4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tor=20Hedin=20Br=C3=B8nner?= Date: Mon, 5 Apr 2021 11:47:38 +0200 Subject: [PATCH 077/104] Claim to support gnome 40 --- metadata.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/metadata.json b/metadata.json index 6f77d9a5..cb126e98 100644 --- a/metadata.json +++ b/metadata.json @@ -4,6 +4,6 @@ "description": "Tiling window manager with a twist", "url": "https://github.com/paperwm/PaperWM", "settings-schema": "org.gnome.Shell.Extensions.PaperWM", - "shell-version": [ "3.28", "3.30", "3.32", "3.34", "3.36", "3.38"], - "version": "38.0" + "shell-version": [ "3.28", "3.30", "3.32", "3.34", "3.36", "3.38", "40"], + "version": "40.0" } From 4e15a1c18ed41eb7c6c7328c78427a4bfa8c85fd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tor=20Hedin=20Br=C3=B8nner?= Date: Mon, 5 Apr 2021 11:51:59 +0200 Subject: [PATCH 078/104] kludges: viewSelector no longer exists --- kludges.js | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/kludges.js b/kludges.js index 8c870c4f..8e784b5c 100644 --- a/kludges.js +++ b/kludges.js @@ -403,8 +403,10 @@ function init() { registerOverridePrototype(Workspace.UnalignedLayoutStrategy, 'computeLayout', layout); - // Kill pinch gestures as they work pretty bad (especially when 3-finger swiping) - registerOverrideProp(imports.ui.viewSelector, "PINCH_GESTURE_THRESHOLD", 0); + // Kill pinch gestures as they work pretty bad (especially when 3-finger swipin + if (version[1] < 40) { + registerOverrideProp(imports.ui.viewSelector, "PINCH_GESTURE_THRESHOLD", 0) + } if (Main.wm._swipeTracker) registerOverrideProp(Main.wm._swipeTracker._touchpadGesture, "enabled", false); From e9f714846b9eac8bdd5b33c3d33f1a9d2fbdecd4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tor=20Hedin=20Br=C3=B8nner?= Date: Mon, 5 Apr 2021 11:55:09 +0200 Subject: [PATCH 079/104] window creation: queue redraw no longer exists --- tiling.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tiling.js b/tiling.js index 3c6144a8..c4c96cb0 100644 --- a/tiling.js +++ b/tiling.js @@ -2180,7 +2180,7 @@ class Spaces extends Map { debug('window-created', metaWindow.title); let actor = metaWindow.get_compositor_private(); - if (utils.version[1] < 34) { + if (utils.version[1] < 34 || utils.version[1] >= 40) { animateWindow(metaWindow); } else { /* HACK 3.34: Hidden actors aren't allocated if hidden, use opacity From 330de23acfefb6ce4cc4e5ce53fff262dbf2b11c Mon Sep 17 00:00:00 2001 From: Gelbana Date: Sat, 16 Oct 2021 19:30:46 +1000 Subject: [PATCH 080/104] Converting Settings.ui to GTK4 Using the gtk4-builder-tool and fixing any remaining issues --- Settings.ui | 1573 ++++++++++++++++++++++----------------------------- 1 file changed, 671 insertions(+), 902 deletions(-) diff --git a/Settings.ui b/Settings.ui index 80f805e9..5dd32144 100644 --- a/Settings.ui +++ b/Settings.ui @@ -1,7 +1,6 @@ - - + 10 0.1 @@ -18,8 +17,7 @@ 10 - True - False + 0 org.gnome.Settings-symbolic @@ -48,1017 +46,788 @@ 10 - True - True - - True - False - 24 - 24 - 24 - 24 - vertical - 12 - - - True - False - 0 - 1 - in + + + + 0 + 24 + 24 + 24 + 24 + vertical + 12 - - True - False + + 0 - - True - False - none - - - True - True + + 0 + none - - True - False - 12 - 12 - 6 - 6 - 32 - - - True - False - True - Gap between windows - 0 + + + + 0 + 12 + 12 + 6 + 6 + 32 + + + 0 + 1 + Gap between windows + 0 + + 0 + 0 + + + + + + 2 + 2 + window_gap + 1 + 1 + if-valid + + 1 + 0 + + + - - 0 - 0 - - - - - True - True - 2 - 2 - 1 - number - window_gap - True - True - if-valid + + + + + + 0 + The minimum margin to the left and right monitor edge + 12 + 12 + 6 + 6 + 32 + + + 0 + 1 + Horizontal margin + 0 + + 0 + 0 + + + + + + 2 + 2 + 0 + horizontal_margin + 1 + 1 + if-valid + + 1 + 0 + + + - - 1 - 0 - - + - - - - - True - True - - True - False - The minimum margin to the left and right monitor edge - 12 - 12 - 6 - 6 - 32 - - - True - False - True - Horizontal margin - 0 + + + + 0 + 12 + 12 + 6 + 6 + 32 + + + 0 + 1 + Top margin + 0 + + 0 + 0 + + + + + + 2 + 2 + 0 + vertical_margin + 1 + 1 + if-valid + + 1 + 0 + + + - - 0 - 0 - - - - - True - True - 2 - 2 - 0 - 1 - number - horizontal_margin - True - True - if-valid - - - 1 - 0 - - + - - - - - True - True - - True - False - 12 - 12 - 6 - 6 - 32 - - - True - False - True - Top margin - 0 + + + + 0 + 12 + 12 + 6 + 6 + 32 + + + 0 + 1 + Bottom margin + 0 + + 0 + 0 + + + + + + 2 + 2 + 0 + vertical_margin_bottom + 1 + 1 + if-valid + + 1 + 0 + + + - - 0 - 0 - - - - - True - True - 2 - 2 - 0 - 1 - number - vertical_margin - True - True - if-valid - - - 1 - 0 - - + - - - True - True + + + + + 0 + 6 + 6 + 6 + 6 + Gaps and margins + + + + + + + + + + 0 + + + 0 + none - - True - False - 12 - 12 - 6 - 6 - 32 - - - True - False - True - Bottom margin - 0 + + + + 0 + 12 + 12 + 6 + 6 + 12 + 32 + + + 0 + 1 + Horizontal sensitivity + 0 + + 0 + 0 + + + + + + 2 + 2 + 0 + horizontal-sensitivity-adjustment + 1 + 1 + 1 + if-valid + + 1 + 0 + + + + + + 0 + 1 + Horizontal friction + 0 + + 0 + 1 + + + + + + 2 + 2 + 0,0 + horizontal-friction-adjustment + 1 + 1 + 1 + if-valid + + 1 + 1 + + + - - 0 - 0 - - - - - True - True - 2 - 2 - 0 - 1 - number - vertical_margin_bottom - True - True - if-valid + + + + + + 0 + 12 + 12 + 6 + 6 + 12 + 32 + + + 0 + 1 + Vertical sensitivity + 0 + + 0 + 0 + + + + + + 2 + 2 + 0 + vertical-sensitivity-adjustment + 1 + 1 + 1 + if-valid + + 1 + 0 + + + + + + 0 + 1 + Vertical friction + 0 + + 0 + 1 + + + + + + 2 + 2 + 0,0 + vertical-friction-adjustment + 1 + 1 + 1 + if-valid + + 1 + 1 + + + - - 1 - 0 - - + + + + 0 + 6 + 6 + Three finger swipes + + + + + - - - True - False - 6 - 6 - 6 - 6 - Gaps and margins - - - - - - - - False - True - 0 - - - - - True - False - 0 - 1 - in - - True - False + + 0 - - True - False - none - - - True - True + + 0 + none - - True - False - 12 - 12 - 6 - 6 - 12 - 32 - - - True - False - True - Horizontal sensitivity - 0 - - - 0 - 0 - - - - - True - True - 2 - 2 - 0 - number - horizontal-sensitivity-adjustment - 1 - True - True - if-valid - - - 1 - 0 - - - - - True - False - True - Horizontal friction - 0 + + + + 0 + 12 + 12 + 6 + 6 + 32 + + + 0 + 1 + Show scratch windows in overview + 0 + + 0 + 0 + + + + + + 0 + 0 + + Always + Only + Never + + + 1 + 0 + + + - - 0 - 1 - - - - - True - True - 2 - 2 - 0,0 - number - horizontal-friction-adjustment - 1 - True - True - if-valid + + + + + + 0 + 12 + 12 + 6 + 6 + 32 + + + 0 + 1 + Disable hot corner + 0 + + 0 + 0 + + + + + + + 1 + 0 + + + - - 1 - 1 - - + - - - - - True - True - - True - False - 12 - 12 - 6 - 6 - 12 - 32 - - - True - False - True - Vertical sensitivity - 0 - - - 0 - 0 - - - - - True - True - 2 - 2 - 0 - 1 - number - vertical-sensitivity-adjustment - 1 - True - True - if-valid + + + + 0 + 12 + 12 + 6 + 6 + 32 + + + 0 + 1 + Make topbar follow monitor focus + 0 + + 0 + 0 + + + + + + + 1 + 0 + + + - - 1 - 0 - - - - - True - False - True - Vertical friction - 0 - - - 0 - 1 - - - - - True - True - 2 - 2 - 0,0 - 1 - number - vertical-friction-adjustment - 1 - True - True - if-valid - - - 1 - 1 - - + - - - - - True - False - 6 - 6 - Three finger swipes - - - + + + 0 + 6 + 6 + 6 + 6 + Toggles + + + + + - - False - True - 1 - - - - - True - False - 0 - 1 - in + + + + 0 + _General + 1 + + + + + + + 1 + + + 0 + 24 + 24 + 24 + 24 + vertical + 24 - - True - False - + + 0 + - True - False + 0 none - - True - True - - - True - False - 12 - 12 - 6 - 6 - 32 + + + + 0 + Only supports static backgrounds + 12 + 12 + 12 + 12 + 24 - - True - False - True - Show scratch windows in overview + + 0 + 1 + Use default GNOME Shell background 0 + + 0 + 0 + + + + + + center + + 2 + 0 + - - 0 - 0 - - - True - False - False - - Always - Only - Never - + + 1 + Launch gnome background picker + + 1 + 0 + - - 1 - 0 - - + + + + + + 0 + 6 + 6 + 6 + 6 + All workspaces + + + + + + + + + + 0 + + + 0 + none - - True - True - - - True - False - 12 - 12 - 6 - 6 + + + + 0 + 12 + 12 + 12 + 12 32 - - True - False - True - Disable hot corner + + 0 + 1 + Configure workspace 0 + + 0 + 0 + - - 0 - 0 - - - True - True + + 0 + center + + 1 + 0 + - - 1 - 0 - - + - True - True - - - True - False - 12 - 12 - 6 - 6 - 32 + 100 + 80 + + + 0 - - True - False - True - Make topbar follow monitor focus - 0 - - - 0 - 0 - - - - - True - True - - - 1 - 0 - + - + + + + + 0 + 6 + 6 + 6 + 6 + Per workspace + + + + - - - True - False - 6 - 6 - 6 - 6 - Toggles - - - - - - - False - True - 2 - - - - - - - True - False - _General - True + + + + 0 + _Workspaces + 1 + + - - False - - - True - False - 24 - 24 - 24 - 24 - vertical - 24 - - - True - False - 0 - 1 - in + + 2 + + + 0 + vertical - - True - False - none - - - True - True - - - True - False - Only supports static backgrounds - 12 - 12 - 12 - 12 - 24 - - - True - False - True - Use default GNOME Shell background - 0 - - - 0 - 0 - - - - - True - True - center - - - 2 - 0 - - - - - True - True - True - Launch gnome background picker - image1 - - - 1 - 0 - - - - - - - - - - - True - False - 6 - 6 - 6 - 6 - All workspaces - - - + - - - False - True - 0 - - - - - True - False - 0 - 1 - in - - True - False - none - - - True - True - - - True - False - 12 - 12 - 12 - 12 - 32 + + never + + + 0 + + + 0 + 24 + 24 + 24 + 24 + 1 + vertical - - True - False - True - Configure workspace - 0 - - - 0 - 0 - + - - True - False - center - - - 1 - 0 - + - - - - - - - 100 - 80 - True - True - - - True - False - + - - - - - - True - False - 6 - 6 - 6 - 6 - Per workspace - - - + - - False - True - 1 - - - - - 1 - - - - - True - False - _Workspaces - True + + + + 0 + _Keybindings + 1 + + - - 1 - False - - - True - False - vertical - - - True - True - edit-find-symbolic - False - False - - - False - True - 0 - - - - - True - True - never + + 3 + + + 0 + vertical + 12 - - True - False - - - True - False - 24 - 24 - 24 - 24 - True - vertical - - - - - - - - - - - + + + + + 0 + <b>PaperWM</b> + 1 + center + + + + + 0 + $VERSION + 1 + center + + + + + 0 + Tiled scrollable window manager extension for Gnome Shell + center + 1 + + + + + Webpage + 1 + https://github.com/paperwm/PaperWM - - False - True - 1 - - - - - 2 - - - - - True - False - _Keybindings - True - - - 2 - False - - - - - True - False - vertical - 12 - - - - False - True - 0 - - - - - True - False - <b>PaperWM</b> - True - center - - - False - True - 1 - - - - - True - False - $VERSION - True - center - - - False - True - 2 - - - - - True - False - Tiled scrollable window manager extension for Gnome Shell - center - True - - - False - True - 3 - - - - - Webpage - True - True - True - none - https://github.com/paperwm/PaperWM + + + + 0 + _About + 1 - - False - True - 4 - - - - - 3 - - - - - True - False - _About - True + - - 3 - False - From 467a9d75e5eb19ce26d8783130361c5943ce76f4 Mon Sep 17 00:00:00 2001 From: Gelbana Date: Sat, 16 Oct 2021 19:31:34 +1000 Subject: [PATCH 081/104] Fix the crashing extensions UI Commenting out removed functions (will be rewritten) and updating function names for compatibility with GTK4 --- prefs.js | 120 ++++++++++++++++++++++++++++++------------------------- 1 file changed, 65 insertions(+), 55 deletions(-) diff --git a/prefs.js b/prefs.js index 96fca540..7a60ed44 100644 --- a/prefs.js +++ b/prefs.js @@ -224,8 +224,8 @@ class SettingsWidget { let windowFrame = new Gtk.Frame({label: _('Windows'), label_xalign: 0.5}); let windows = createKeybindingWidget(settings, searchEntry); - box.add(windowFrame); - windowFrame.add(windows); + box.append(windowFrame); + windowFrame.set_child(windows); ['new-window', 'close-window', 'switch-next', 'switch-previous', @@ -245,8 +245,8 @@ class SettingsWidget { let workspaceFrame = new Gtk.Frame({label: _('Workspaces'), label_xalign: 0.5}); let workspaces = createKeybindingWidget(settings, searchEntry); - box.add(workspaceFrame); - workspaceFrame.add(workspaces); + box.append(workspaceFrame); + workspaceFrame.set_child(workspaces); ['previous-workspace', 'previous-workspace-backward', 'move-previous-workspace', 'move-previous-workspace-backward', @@ -262,8 +262,8 @@ class SettingsWidget { let monitorFrame = new Gtk.Frame({label: _('Monitors'), label_xalign: 0.5}); let monitors = createKeybindingWidget(settings, searchEntry); - box.add(monitorFrame); - monitorFrame.add(monitors); + box.append(monitorFrame); + monitorFrame.set_child(monitors); ['switch-monitor-right', 'switch-monitor-left', @@ -283,8 +283,8 @@ class SettingsWidget { let scratchFrame = new Gtk.Frame({label: _('Scratch layer'), label_xalign: 0.5}); let scratch = createKeybindingWidget(settings, searchEntry); - box.add(scratchFrame); - scratchFrame.add(scratch); + box.append(scratchFrame); + scratchFrame.set_child(scratch); ['toggle-scratch-layer', 'toggle-scratch', "toggle-scratch-window"] .forEach(k => { @@ -310,28 +310,39 @@ class SettingsWidget { orientation: Gtk.Orientation.VERTICAL, can_focus: false, }); - list.add(new Gtk.LevelBar()); + list.append(new Gtk.LevelBar()); let nameEntry = new Gtk.Entry(); let colorButton = new Gtk.ColorButton(); let backgroundBox = new Gtk.Box({spacing: 32}); // same spacing as used in glade for default background - let background = new Gtk.FileChooserButton(); + let background = new Gtk.Button({ + label: "Choose Workspace Folder" + }); + background.connect('clicked', () => { + let fileChooser = new Gtk.FileChooserNative({ + title: "Choose Workspace Folder", + action: Gtk.FileChooseAction.SELECT_FOLDER, + accept_label: "Select", + cancel_label: "Cancel" + }); + fileChooser.show(); + }); let clearBackground = new Gtk.Button({label: 'Clear', sensitive: settings.get_string('background') != ''}); let hideTopBarSwitch = new Gtk.Switch({active: !settings.get_boolean('show-top-bar')}); - backgroundBox.add(background) - backgroundBox.add(clearBackground) + backgroundBox.append(background) + backgroundBox.append(clearBackground) - let directoryChooser = new Gtk.FileChooserButton({ - action: Gtk.FileChooserAction.SELECT_FOLDER, - title: 'Select workspace directory' - }); + // let directoryChooser = new Gtk.FileChooserDialog({ + // action: Gtk.FileChooserAction.SELECT_FOLDER, + // title: 'Select workspace directory' + // }); - list.add(createRow('Name', nameEntry)); - list.add(createRow('Color', colorButton)); - list.add(createRow('Background', backgroundBox)); - list.add(createRow('Hide top bar', hideTopBarSwitch)); - list.add(createRow('Directory', directoryChooser)); + list.append(createRow('Name', nameEntry)); + list.append(createRow('Color', colorButton)); + list.append(createRow('Background', backgroundBox)); + list.append(createRow('Hide top bar', hideTopBarSwitch)); + // list.append(createRow('Directory', directoryChooser)); let rgba = new Gdk.RGBA(); let color = settings.get_string('color'); @@ -342,11 +353,11 @@ class SettingsWidget { rgba.parse(color); colorButton.set_rgba(rgba); - let filename = settings.get_string('background'); - if (filename === '') - background.unselect_all(); - else - background.set_filename(filename); + // let filename = settings.get_string('background'); + // if (filename === '') + // background.unselect_all(); + // else + // background.set_filename(filename); nameEntry.set_text(this.getWorkspaceName(settings, index)); @@ -373,32 +384,32 @@ class SettingsWidget { background.unselect_all(); }); - background.connect('file-set', () => { - let filename = background.get_filename(); - settings.set_string('background', filename); - clearBackground.sensitive = filename != ''; - }); + // background.connect('file-set', () => { + // let filename = background.get_filename(); + // settings.set_string('background', filename); + // clearBackground.sensitive = filename != ''; + // }); - clearBackground.connect('clicked', () => { - background.unselect_all(); // Note: does not trigger 'file-set' - settings.reset('background'); - clearBackground.sensitive = settings.get_string('background') != ''; - }); + // clearBackground.connect('clicked', () => { + // background.unselect_all(); // Note: does not trigger 'file-set' + // settings.reset('background'); + // clearBackground.sensitive = settings.get_string('background') != ''; + // }); hideTopBarSwitch.connect('state-set', (gtkswitch_, state) => { settings.set_boolean('show-top-bar', !state); }); - let dir = settings.get_string('directory') - if (dir === '') - directoryChooser.unselect_all(); - else - directoryChooser.set_filename(dir) + // let dir = settings.get_string('directory') + // if (dir === '') + // directoryChooser.unselect_all(); + // else + // directoryChooser.set_filename(dir) - directoryChooser.connect('file-set', () => { - let dir = directoryChooser.get_filename(); - settings.set_string('directory', dir); - }); + // directoryChooser.connect('file-set', () => { + // let dir = directoryChooser.get_filename(); + // settings.set_string('directory', dir); + // }); return list; } @@ -425,8 +436,8 @@ function createRow(text, widget, signal, handler) { label: text, hexpand: true, xalign: 0 }); - box.add(label); - box.add(widget); + box.append(label); + box.append(widget); return box; } @@ -634,13 +645,13 @@ function createKeybindingWidget(settings, searchEntry) { function parseAccelerator(accelerator) { let key, mods; - if (accelerator.match(/Above_Tab/)) { - let keymap = Gdk.Keymap.get_default(); - let entries = keymap.get_entries_for_keycode(49)[1]; - let keyval = keymap.lookup_key(entries[0]); - let name = Gtk.accelerator_name(keyval, 0); - accelerator = accelerator.replace('Above_Tab', name); - } + // if (accelerator.match(/Above_Tab/)) { + // let keymap = Gdk.Keymap.get_default(); + // let entries = keymap.get_entries_for_keycode(49)[1]; + // let keyval = keymap.lookup_key(entries[0]); + // let name = Gtk.accelerator_name(keyval, 0); + // accelerator = accelerator.replace('Above_Tab', name); + // } [key, mods] = Gtk.accelerator_parse(accelerator); return [key, mods]; @@ -750,6 +761,5 @@ function buildPrefsWidget() { } let settings = new SettingsWidget(selectedTab, selectedWorkspace || 0); let widget = settings.widget; - widget.show_all(); return widget; } From c935a0c9c390a1d8049d49018ad25b7ce32f06a8 Mon Sep 17 00:00:00 2001 From: Gelbana Date: Sun, 17 Oct 2021 08:25:02 +1000 Subject: [PATCH 082/104] Fix window movement with mouse The callback arguments for the 'grab-op-begin' and 'grab-op-end' events changed from GTK3 to GTK4. Removing the first argument from the arguments allows window movement to function as before. --- tiling.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tiling.js b/tiling.js index c4c96cb0..b6ff02cb 100644 --- a/tiling.js +++ b/tiling.js @@ -1391,9 +1391,9 @@ class Spaces extends Map { this.signals.connect(display, 'window-created', this.window_created.bind(this)); this.signals.connect(display, 'grab-op-begin', - (display, d, mw, type) => grabBegin(mw, type)); + (display, mw, type) => grabBegin(mw, type)); this.signals.connect(display, 'grab-op-end', - (display, d, mw, type) => grabEnd(mw, type)); + (display, mw, type) => grabEnd(mw, type)); this.signals.connect(Main.layoutManager, 'monitors-changed', this.monitorsChanged.bind(this)); From 2cb7cc13dc1af5687f1feab737c6f9268720e51e Mon Sep 17 00:00:00 2001 From: Gelbana Date: Sun, 21 Nov 2021 13:52:16 +1000 Subject: [PATCH 083/104] Fix - Invalid keybinding display in ext settings Gtk.accelerator_parse("x") added a success return value in Gtk4 leading to modifier masks being treated as keycodes and the new success bool being treated as a keycode. This lead to parsing errors of the keybind display widget. --- prefs.js | 2 +- settings.js | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/prefs.js b/prefs.js index 7a60ed44..6d24e892 100644 --- a/prefs.js +++ b/prefs.js @@ -653,7 +653,7 @@ function parseAccelerator(accelerator) { // accelerator = accelerator.replace('Above_Tab', name); // } - [key, mods] = Gtk.accelerator_parse(accelerator); + [success, key, mods] = Gtk.accelerator_parse(accelerator); return [key, mods]; } diff --git a/settings.js b/settings.js index 31a671c9..b73bca4f 100644 --- a/settings.js +++ b/settings.js @@ -217,7 +217,8 @@ function keystrToKeycombo(keystr) { keystr = keystr.replace('Above_Tab', 'A'); aboveTab = true; } - let [key, mask] = Gtk.accelerator_parse(keystr); + let [success, key, mask] = Gtk.accelerator_parse(keystr); + if (aboveTab) key = META_KEY_ABOVE_TAB; return `${key}|${mask}`; // Since js doesn't have a mapable tuple type From b600ec4aa7cc65b7a77c442c04709bc76c9bd885 Mon Sep 17 00:00:00 2001 From: Gelbana Date: Wed, 1 Dec 2021 20:00:45 +1000 Subject: [PATCH 084/104] Fix - Overview not showing any windows UnalignedLayoutStrategy now uses a return object instead of editing the passed in "layout" variable. --- kludges.js | 72 +++++++++++++++++++++++++++++++++++++++++++++++++++ metadata.json | 4 +-- 2 files changed, 74 insertions(+), 2 deletions(-) diff --git a/kludges.js b/kludges.js index 8e784b5c..c2b5f959 100644 --- a/kludges.js +++ b/kludges.js @@ -20,6 +20,7 @@ var Workspace = imports.ui.workspace; var WindowManager = imports.ui.windowManager; var Shell = imports.gi.Shell; var utils = Extension.imports.utils; +var Params = imports.misc.params; var Convenience = Extension.imports.convenience; var Scratch = Extension.imports.scratch; @@ -399,9 +400,14 @@ function init() { registerOverridePrototype(Workspace.WorkspaceLayout, 'addWindow', addWindow) } + if (version[1] > 32) registerOverridePrototype(Workspace.UnalignedLayoutStrategy, 'computeLayout', layout); + if (version[1] > 39) { + layout = computeLayout40 + registerOverridePrototype(Workspace.UnalignedLayoutStrategy, 'computeLayout', layout) + } // Kill pinch gestures as they work pretty bad (especially when 3-finger swipin if (version[1] < 40) { @@ -634,6 +640,72 @@ function sortWindows(a, b) { return ia - ib; } +function computeLayout40(windows, layoutParams) { + layoutParams = Params.parse(layoutParams, { + numRows: 0, + }); + + if (layoutParams.numRows === 0) + throw new Error(`${this.constructor.name}: No numRows given in layout params`); + + let numRows = layoutParams.numRows; + + let rows = []; + let totalWidth = 0; + for (let i = 0; i < windows.length; i++) { + let window = windows[i]; + let s = this._computeWindowScale(window); + totalWidth += window.boundingBox.width * s; + } + + let idealRowWidth = totalWidth / numRows; + + let sortedWindows = windows.slice(); + // addWindow should have made sure we're already sorted. + // sortedWindows.sort(sortWindows); + + let windowIdx = 0; + for (let i = 0; i < numRows; i++) { + let row = this._newRow(); + rows.push(row); + + for (; windowIdx < sortedWindows.length; windowIdx++) { + let window = sortedWindows[windowIdx]; + let s = this._computeWindowScale(window); + let width = window.boundingBox.width * s; + let height = window.boundingBox.height * s; + row.fullHeight = Math.max(row.fullHeight, height); + + // either new width is < idealWidth or new width is nearer from idealWidth then oldWidth + if (this._keepSameRow(row, window, width, idealRowWidth) || (i == numRows - 1)) { + row.windows.push(window); + row.fullWidth += width; + } else { + break; + } + } + } + + let gridHeight = 0; + let maxRow; + for (let i = 0; i < numRows; i++) { + let row = rows[i]; + this._sortRow(row); + + if (!maxRow || row.fullWidth > maxRow.fullWidth) + maxRow = row; + gridHeight += row.fullHeight; + } + + return { + numRows, + rows, + maxColumns: maxRow.windows.length, + gridWidth: maxRow.fullWidth, + gridHeight + }; +} + function computeLayout338(windows, layout) { let numRows = layout.numRows; diff --git a/metadata.json b/metadata.json index cb126e98..6da22980 100644 --- a/metadata.json +++ b/metadata.json @@ -4,6 +4,6 @@ "description": "Tiling window manager with a twist", "url": "https://github.com/paperwm/PaperWM", "settings-schema": "org.gnome.Shell.Extensions.PaperWM", - "shell-version": [ "3.28", "3.30", "3.32", "3.34", "3.36", "3.38", "40"], - "version": "40.0" + "shell-version": [ "3.28", "3.30", "3.32", "3.34", "3.36", "3.38", "40", "41"], + "version": "41.0" } From 676ad9f903f43278e8c9fbbd7044d80791a8e51b Mon Sep 17 00:00:00 2001 From: Gelbana Date: Thu, 23 Dec 2021 17:50:38 +1000 Subject: [PATCH 085/104] Set compatibility to Gnome 40+ --- kludges.js | 2 +- metadata.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/kludges.js b/kludges.js index c2b5f959..75822873 100644 --- a/kludges.js +++ b/kludges.js @@ -404,7 +404,7 @@ function init() { if (version[1] > 32) registerOverridePrototype(Workspace.UnalignedLayoutStrategy, 'computeLayout', layout); - if (version[1] > 39) { + if (version[1] >= 40) { layout = computeLayout40 registerOverridePrototype(Workspace.UnalignedLayoutStrategy, 'computeLayout', layout) } diff --git a/metadata.json b/metadata.json index 6da22980..66664091 100644 --- a/metadata.json +++ b/metadata.json @@ -4,6 +4,6 @@ "description": "Tiling window manager with a twist", "url": "https://github.com/paperwm/PaperWM", "settings-schema": "org.gnome.Shell.Extensions.PaperWM", - "shell-version": [ "3.28", "3.30", "3.32", "3.34", "3.36", "3.38", "40", "41"], + "shell-version": [ "40", "41"], "version": "41.0" } From 38d53d03e84343de16060e539ea9a4a767250cb5 Mon Sep 17 00:00:00 2001 From: Gelbana Date: Thu, 23 Dec 2021 17:51:15 +1000 Subject: [PATCH 086/104] Fix - Crashing on lock (viewSelector changes) Extension crashes when locking and unlocking the desktop. Also breaks the overview, requiring a relog. The viewSelector class has been renamed to SearchController and has been relocated. Some keybinding functions (eg. toggleAppsPage()) have been moved as a result, so updating the calling class fixes the keybinding issues (crashing on disable). --- keybindings.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/keybindings.js b/keybindings.js index 9a96e23c..0e4d474c 100644 --- a/keybindings.js +++ b/keybindings.js @@ -659,7 +659,7 @@ function resetConflicts() { break; case 'toggle-application-view': // overview._controls: Backward compatibility for 3.34 and below: - const viewSelector = (Main.overview.viewSelector || Main.overview._controls.viewSelector); + const viewSelector = (Main.overview._overview._controls || Main.overview.viewSelector || Main.overview._controls.viewSelector); Main.wm.setCustomKeybindingHandler( name, From 921506482a976107688701d40228061e695b9321 Mon Sep 17 00:00:00 2001 From: Michael Thomas Date: Thu, 23 Dec 2021 13:09:37 -0500 Subject: [PATCH 087/104] Update metadata.json --- metadata.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/metadata.json b/metadata.json index 66664091..9a93bb50 100644 --- a/metadata.json +++ b/metadata.json @@ -2,8 +2,8 @@ "uuid": "paperwm@hedning:matrix.org", "name": "PaperWM", "description": "Tiling window manager with a twist", - "url": "https://github.com/paperwm/PaperWM", + "url": "https://github.com/PaperWM-community/PaperWM", "settings-schema": "org.gnome.Shell.Extensions.PaperWM", - "shell-version": [ "40", "41"], + "shell-version": [ "40", "41" ], "version": "41.0" } From 219adcc8cee787943e057d26b6e752bc80c5f9ef Mon Sep 17 00:00:00 2001 From: smichel17 Date: Thu, 23 Dec 2021 18:09:51 +0000 Subject: [PATCH 088/104] Add soft fork explanation to README.md --- README.md | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/README.md b/README.md index 1a339d81..12648531 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,11 @@ +# This is a *soft* fork of [PaperWM](https://github.com/paperwm/PaperWM) # + +PaperWM's maintainer(s) have been unresponsive since April, 2021. However, they have not said that they are stepping down from maintainership; it may just be a busy period in their lives, etc. + +Meanwhile, Gnome 40 introduced changes which broke PaperWM. Several people independently started fixing the issues for themselves. This repository is for them to coordinate, so they don't end up introducing incompatible patches; **the goal is for this work to eventually be merged back upstream**, not to start an independent development effort. + +For details, see https://github.com/paperwm/PaperWM/issues/376#issuecomment-988178131 (and comments below). + # PaperWM # [![project chat](https://img.shields.io/badge/zulip-join_chat-brightgreen.svg)](https://paperwm.zulipchat.com) From 3e4dcd1f4506670f626cd2176979ded2757235f0 Mon Sep 17 00:00:00 2001 From: Michael Thomas Date: Thu, 23 Dec 2021 13:24:31 -0500 Subject: [PATCH 089/104] Add soft-fork explanation to next-release --- README.md | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/README.md b/README.md index 4c3de3bd..551085c1 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,11 @@ +# This is a *soft* fork of [PaperWM](https://github.com/paperwm/PaperWM) # + +PaperWM's maintainer(s) have been unresponsive since April, 2021. However, they have not said that they are stepping down from maintainership; it may just be a busy period in their lives, etc. + +Meanwhile, Gnome 40 introduced changes which broke PaperWM. Several people independently started fixing the issues for themselves. This repository is for them to coordinate, so they don't end up introducing incompatible patches; **the goal is for this work to eventually be merged back upstream**, not to start an independent development effort. + +For details, see https://github.com/paperwm/PaperWM/issues/376#issuecomment-988178131 (and comments below). + # PaperWM # [![project chat](https://img.shields.io/badge/zulip-join_chat-brightgreen.svg)](https://paperwm.zulipchat.com) From ad9fb43c51dddd550a49754a1308e63fcc832a10 Mon Sep 17 00:00:00 2001 From: Michael Thomas Date: Thu, 23 Dec 2021 14:03:53 -0500 Subject: [PATCH 090/104] Revert "Add soft-fork explanation to next-release" This reverts commit 3e4dcd1f4506670f626cd2176979ded2757235f0. --- README.md | 8 -------- 1 file changed, 8 deletions(-) diff --git a/README.md b/README.md index 551085c1..4c3de3bd 100644 --- a/README.md +++ b/README.md @@ -1,11 +1,3 @@ -# This is a *soft* fork of [PaperWM](https://github.com/paperwm/PaperWM) # - -PaperWM's maintainer(s) have been unresponsive since April, 2021. However, they have not said that they are stepping down from maintainership; it may just be a busy period in their lives, etc. - -Meanwhile, Gnome 40 introduced changes which broke PaperWM. Several people independently started fixing the issues for themselves. This repository is for them to coordinate, so they don't end up introducing incompatible patches; **the goal is for this work to eventually be merged back upstream**, not to start an independent development effort. - -For details, see https://github.com/paperwm/PaperWM/issues/376#issuecomment-988178131 (and comments below). - # PaperWM # [![project chat](https://img.shields.io/badge/zulip-join_chat-brightgreen.svg)](https://paperwm.zulipchat.com) From 4511f4ec0e49df57713764cf4537206e77baf2fb Mon Sep 17 00:00:00 2001 From: Michael Thomas Date: Thu, 23 Dec 2021 14:04:26 -0500 Subject: [PATCH 091/104] PR Friendliness Revision --- metadata.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/metadata.json b/metadata.json index 9a93bb50..d6a41f79 100644 --- a/metadata.json +++ b/metadata.json @@ -2,7 +2,7 @@ "uuid": "paperwm@hedning:matrix.org", "name": "PaperWM", "description": "Tiling window manager with a twist", - "url": "https://github.com/PaperWM-community/PaperWM", + "url": "https://github.com/paperwm/PaperWM", "settings-schema": "org.gnome.Shell.Extensions.PaperWM", "shell-version": [ "40", "41" ], "version": "41.0" From 9c362967327ffd1f5ce654633f59d9bef8015c5e Mon Sep 17 00:00:00 2001 From: Michael Thomas Date: Thu, 23 Dec 2021 14:30:24 -0500 Subject: [PATCH 092/104] Add workspace animation fix to kludges --- kludges.js | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/kludges.js b/kludges.js index 75822873..63268a5f 100644 --- a/kludges.js +++ b/kludges.js @@ -18,6 +18,7 @@ var Main = imports.ui.main; var Mainloop = imports.mainloop; var Workspace = imports.ui.workspace; var WindowManager = imports.ui.windowManager; +var WorkspaceAnimation = imports.ui.workspaceAnimation; var Shell = imports.gi.Shell; var utils = Extension.imports.utils; var Params = imports.misc.params; @@ -265,6 +266,12 @@ function getOriginalPosition() { return [x, y]; } +// WorkspaceAnimation.WorkspaceAnimationController.animateSwitch +// Disable the workspace switching animation in Gnome 40+ +function animateSwitch(_from, _to, _direction, onComplete) { + onComplete(); +}; + function disableHotcorners() { let override = settings.get_boolean("override-hot-corner"); if (override) { @@ -371,6 +378,7 @@ var signals; function init() { registerOverridePrototype(imports.ui.messageTray.MessageTray, '_updateState'); registerOverridePrototype(WindowManager.WindowManager, '_prepareWorkspaceSwitch'); + registerOverridePrototype(WorkspaceAnimation.WorkspaceAnimationController, 'animateSwitch', animateSwitch); registerOverridePrototype(Workspace.Workspace, '_isOverviewWindow'); if (Workspace.WindowClone) registerOverridePrototype(Workspace.WindowClone, 'getOriginalPosition', getOriginalPosition); From b3103b22c68cbb6af250d4f8ce4119740f2c5c57 Mon Sep 17 00:00:00 2001 From: Tad Fisher Date: Mon, 20 Sep 2021 15:48:09 -0700 Subject: [PATCH 093/104] prefs.js: Initial port to GTK 4 --- Settings.ui | 1462 +++++++++++++++++++++++++++------------------------ prefs.js | 135 ++--- 2 files changed, 847 insertions(+), 750 deletions(-) diff --git a/Settings.ui b/Settings.ui index 5dd32144..f71737ec 100644 --- a/Settings.ui +++ b/Settings.ui @@ -1,4 +1,4 @@ - + @@ -17,8 +17,9 @@ 10 - 0 - org.gnome.Settings-symbolic + True + False + 10 @@ -46,787 +47,870 @@ 10 + True + True - - - - 0 - 24 - 24 - 24 - 24 - vertical - 12 + + True + True + 24 + 24 + 24 + 24 + vertical + 12 + + + True + False - - 0 + + True + False + none - - 0 - none + + True + False + + + True + False + 12 + 12 + 6 + 6 + 32 - - - - 0 - 12 - 12 - 6 - 6 - 32 - - - 0 - 1 - Gap between windows - 0 - - 0 - 0 - - - - - - 2 - 2 - window_gap - 1 - 1 - if-valid - - 1 - 0 - - - - - + + True + False + 1 + Gap between windows + 0 + + 0 + 0 + + + - - - - 0 - The minimum margin to the left and right monitor edge - 12 - 12 - 6 - 6 - 32 - - - 0 - 1 - Horizontal margin - 0 - - 0 - 0 - - - - - - 2 - 2 - 0 - horizontal_margin - 1 - 1 - if-valid - - 1 - 0 - - - - - + + True + 2 + 2 + window_gap + 1 + 1 + if-valid + + 1 + 0 + + + + + + + + True + False + + + + True + False + The minimum margin to the left and right monitor edge + 12 + 12 + 6 + 6 + 32 - - - - 0 - 12 - 12 - 6 - 6 - 32 - - - 0 - 1 - Top margin - 0 - - 0 - 0 - - - - - - 2 - 2 - 0 - vertical_margin - 1 - 1 - if-valid - - 1 - 0 - - - - - + + True + False + 1 + Horizontal margin + 0 + + 0 + 0 + - - - - 0 - 12 - 12 - 6 - 6 - 32 - - - 0 - 1 - Bottom margin - 0 - - 0 - 0 - - - - - - 2 - 2 - 0 - vertical_margin_bottom - 1 - 1 - if-valid - - 1 - 0 - - - - - + + True + 2 + 2 + 0 + horizontal_margin + 1 + 1 + if-valid + + 1 + 0 + - - - 0 - 6 - 6 - 6 - 6 - Gaps and margins - - - - - - - - - - 0 - - 0 - none + + True + False + + + True + False + 12 + 12 + 6 + 6 + 32 - - - - 0 - 12 - 12 - 6 - 6 - 12 - 32 - - - 0 - 1 - Horizontal sensitivity - 0 - - 0 - 0 - - - - - - 2 - 2 - 0 - horizontal-sensitivity-adjustment - 1 - 1 - 1 - if-valid - - 1 - 0 - - - - - - 0 - 1 - Horizontal friction - 0 - - 0 - 1 - - - - - - 2 - 2 - 0,0 - horizontal-friction-adjustment - 1 - 1 - 1 - if-valid - - 1 - 1 - - - - - + + True + False + 1 + Top margin + 0 + + 0 + 0 + + + - - - - 0 - 12 - 12 - 6 - 6 - 12 - 32 - - - 0 - 1 - Vertical sensitivity - 0 - - 0 - 0 - - - - - - 2 - 2 - 0 - vertical-sensitivity-adjustment - 1 - 1 - 1 - if-valid - - 1 - 0 - - - - - - 0 - 1 - Vertical friction - 0 - - 0 - 1 - - - - - - 2 - 2 - 0,0 - vertical-friction-adjustment - 1 - 1 - 1 - if-valid - - 1 - 1 - - - - - + + True + 2 + 2 + 0 + vertical_margin + 1 + 1 + if-valid + + 1 + 0 + - - - 0 - 6 - 6 - Three finger swipes - - - + + + True + False + + + True + False + 12 + 12 + 6 + 6 + 32 + + + True + False + 1 + Bottom margin + 0 + + 0 + 0 + + + + + + True + 2 + 2 + 0 + vertical_margin_bottom + 1 + 1 + if-valid + + 1 + 0 + + + + + + + + True + False + 6 + 6 + 6 + 6 + Gaps and margins + + + + + + + + + + True + False - - 0 + + True + False + none - - 0 - none + + True + False + + + True + False + 12 + 12 + 6 + 6 + 12 + 32 - - - - 0 - 12 - 12 - 6 - 6 - 32 - - - 0 - 1 - Show scratch windows in overview - 0 - - 0 - 0 - - - - - - 0 - 0 - - Always - Only - Never - - - 1 - 0 - - - - - + + True + False + 1 + Horizontal sensitivity + 0 + + 0 + 0 + + + + + + True + True + True + 2 + 2 + 0 + horizontal-sensitivity-adjustment + 1 + 1 + 1 + if-valid + + 1 + 0 + + + - - - - 0 - 12 - 12 - 6 - 6 - 32 - - - 0 - 1 - Disable hot corner - 0 - - 0 - 0 - - - - - - - 1 - 0 - - - - - + + True + False + 1 + Horizontal friction + 0 + + 0 + 1 + - - - - 0 - 12 - 12 - 6 - 6 - 32 - - - 0 - 1 - Make topbar follow monitor focus - 0 - - 0 - 0 - - - - - - - 1 - 0 - - - - - + + True + 2 + 2 + 0,0 + horizontal-friction-adjustment + 1 + 1 + 1 + if-valid + + 1 + 1 + - - - 0 - 6 - 6 - 6 - 6 - Toggles - - - + + + True + False + + + True + False + 12 + 12 + 6 + 6 + 12 + 32 + + + True + False + 1 + Vertical sensitivity + 0 + + 0 + 0 + + + + + + True + 2 + 2 + 0 + vertical-sensitivity-adjustment + 1 + 1 + 1 + if-valid + + 1 + 0 + + + + + + True + False + 1 + Vertical friction + 0 + + 0 + 1 + + + + + + True + 2 + 2 + 0,0 + vertical-friction-adjustment + 1 + 1 + 1 + if-valid + + 1 + 1 + + + + + + + + True + False + 6 + 6 + Three finger swipes + + + + + - - - - 0 - _General - 1 - - - - - - - 1 - - - 0 - 24 - 24 - 24 - 24 - vertical - 24 + + + + True + False - - 0 - - - 0 - none + + True + False + none + + + True + False - - - - 0 - Only supports static backgrounds - 12 - 12 - 12 - 12 - 24 - - - 0 - 1 - Use default GNOME Shell background - 0 - - 0 - 0 - - - - - - center - - 2 - 0 - - - - - - 1 - Launch gnome background picker - - 1 - 0 - - - + + True + False + 12 + 12 + 6 + 6 + 32 + + + True + False + 1 + Show scratch windows in overview + 0 + + 0 + 0 + - + + + + True + False + 0 + + Always + Only + Never + + + 1 + 0 + + + - - - - 0 - 6 - 6 - 6 - 6 - All workspaces - - - - - - - - - 0 - - - 0 - none + + + True + False - - - - 0 - 12 - 12 - 12 - 12 - 32 - - - 0 - 1 - Configure workspace - 0 - - 0 - 0 - - - - - - 0 - center - - 1 - 0 - - - + + True + False + 12 + 12 + 6 + 6 + 32 + + + True + False + 1 + Disable hot corner + 0 + + 0 + 0 + - + + + + True + + 1 + 0 + + + + + + + + True + False - - 100 - 80 - - - 0 - - - + + True + False + 12 + 12 + 6 + 6 + 32 + + + True + False + 1 + Make topbar follow monitor focus + 0 + + 0 + 0 + + + + + + True + + 1 + 0 + - + - - - - 0 - 6 - 6 - 6 - 6 - Per workspace - - - - + + + True + False + 6 + 6 + 6 + 6 + Toggles + + + + + - - - - 0 - _Workspaces - 1 - - + + + + + + True + False + _General + 1 - - 2 - - - 0 - vertical + + True + False + 24 + 24 + 24 + 24 + vertical + 24 + + + True + False - + + True + False + none + + + True + False + + + True + False + Only supports static backgrounds + 12 + 12 + 12 + 12 + 24 + + + True + False + 1 + Use default GNOME Shell background + 0 + + 0 + 0 + + + + + + True + center + + 2 + 0 + + + + + + True + 1 + Launch gnome background picker + org.gnome.Settings-symbolic + + 1 + 0 + + + + + + + + + + + + True + False + 6 + 6 + 6 + 6 + All workspaces + + + + + + + + True + False - - never - - - 0 - - - 0 - 24 - 24 - 24 - 24 - 1 - vertical + + True + False + none + + + True + False + + + True + False + 12 + 12 + 12 + 12 + 32 - + + True + False + 1 + Configure workspace + 0 + + 0 + 0 + + - + + True + False + center + + 1 + 0 + + + + + + + + + True + False + 100 + 80 + + + True + False - + - + + + + + + True + False + 6 + 6 + 6 + 6 + Per workspace + + + - - - - 0 - _Keybindings - 1 - - + + + + + + True + False + _Workspaces + 1 - - 3 - - - 0 - vertical - 12 - - - - - - 0 - <b>PaperWM</b> - 1 - center - - - - - 0 - $VERSION - 1 - center - - - - - 0 - Tiled scrollable window manager extension for Gnome Shell - center - 1 - - + + True + False + vertical + + + True + True + + + + + True + never - - Webpage - 1 - https://github.com/paperwm/PaperWM + + True + False + + + True + False + 24 + 24 + 24 + 24 + 1 + vertical + + + + + + + + + + + - - - - 0 - _About - 1 + + + + + + True + False + _Keybindings + 1 + + + + + True + False + vertical + 12 + + + + + + True + False + <b>PaperWM</b> + 1 + center + + + + + True + False + $VERSION + 1 + center - + + + + True + False + Tiled scrollable window manager extension for Gnome Shell + center + 1 + + + + + True + Webpage + 1 + https://github.com/paperwm/PaperWM + + + + + + + True + False + _About + 1 diff --git a/prefs.js b/prefs.js index 6d24e892..1ca2244f 100644 --- a/prefs.js +++ b/prefs.js @@ -49,9 +49,9 @@ function swapArrayElements(array, i, j) { function ok(okValue) { if(okValue[0]) { - return okValue[1] + return okValue[1]; } else { - return null + return null; } } @@ -68,6 +68,8 @@ class SettingsWidget { this._notebook = this.builder.get_object('paperwm_settings'); this.widget = this._notebook; this._notebook.page = selectedTab; + this._backgroundFilter = new Gtk.FileFilter(); + this._backgroundFilter.add_pixbuf_formats(); // General @@ -138,7 +140,7 @@ class SettingsWidget { this._settings.set_boolean('only-scratch-in-overview', false); this._settings.set_boolean('disable-scratch-in-overview', false); } - + }); let disableCorner = this.builder.get_object('override-hot-corner'); @@ -315,34 +317,44 @@ class SettingsWidget { let nameEntry = new Gtk.Entry(); let colorButton = new Gtk.ColorButton(); + // Background + let backgroundBox = new Gtk.Box({spacing: 32}); // same spacing as used in glade for default background - let background = new Gtk.Button({ - label: "Choose Workspace Folder" - }); - background.connect('clicked', () => { - let fileChooser = new Gtk.FileChooserNative({ - title: "Choose Workspace Folder", - action: Gtk.FileChooseAction.SELECT_FOLDER, - accept_label: "Select", - cancel_label: "Cancel" - }); - fileChooser.show(); - }); + let background = createFileChooserButton( + settings, + 'background', + { + action: Gtk.FileChooserAction.OPEN, + title: 'Select workspace background', + filter: this._backgroundFilter, + select_multiple: false, + modal: true, + transient_for: this.widget.get_root() + } + ); let clearBackground = new Gtk.Button({label: 'Clear', sensitive: settings.get_string('background') != ''}); - let hideTopBarSwitch = new Gtk.Switch({active: !settings.get_boolean('show-top-bar')}); - backgroundBox.append(background) - backgroundBox.append(clearBackground) - // let directoryChooser = new Gtk.FileChooserDialog({ - // action: Gtk.FileChooserAction.SELECT_FOLDER, - // title: 'Select workspace directory' - // }); + let hideTopBarSwitch = new Gtk.Switch({active: !settings.get_boolean('show-top-bar')}); + backgroundBox.append(background); + backgroundBox.append(clearBackground); + + let directoryChooser = createFileChooserButton( + settings, + 'directory', + { + action: Gtk.FileChooserAction.SELECT_FOLDER, + title: 'Select workspace background', + select_multiple: false, + modal: true, + transient_for: this.widget.get_root() + } + ); list.append(createRow('Name', nameEntry)); list.append(createRow('Color', colorButton)); list.append(createRow('Background', backgroundBox)); list.append(createRow('Hide top bar', hideTopBarSwitch)); - // list.append(createRow('Directory', directoryChooser)); + list.append(createRow('Directory', directoryChooser)); let rgba = new Gdk.RGBA(); let color = settings.get_string('color'); @@ -353,12 +365,6 @@ class SettingsWidget { rgba.parse(color); colorButton.set_rgba(rgba); - // let filename = settings.get_string('background'); - // if (filename === '') - // background.unselect_all(); - // else - // background.set_filename(filename); - nameEntry.set_text(this.getWorkspaceName(settings, index)); let workspace_combo = this.builder.get_object('workspace_combo_text'); @@ -384,33 +390,15 @@ class SettingsWidget { background.unselect_all(); }); - // background.connect('file-set', () => { - // let filename = background.get_filename(); - // settings.set_string('background', filename); - // clearBackground.sensitive = filename != ''; - // }); - - // clearBackground.connect('clicked', () => { - // background.unselect_all(); // Note: does not trigger 'file-set' - // settings.reset('background'); - // clearBackground.sensitive = settings.get_string('background') != ''; - // }); + clearBackground.connect('clicked', () => { + settings.reset('background'); + clearBackground.sensitive = settings.get_string('background') != ''; + }); hideTopBarSwitch.connect('state-set', (gtkswitch_, state) => { settings.set_boolean('show-top-bar', !state); }); - // let dir = settings.get_string('directory') - // if (dir === '') - // directoryChooser.unselect_all(); - // else - // directoryChooser.set_filename(dir) - - // directoryChooser.connect('file-set', () => { - // let dir = directoryChooser.get_filename(); - // settings.set_string('directory', dir); - // }); - return list; } @@ -645,15 +633,11 @@ function createKeybindingWidget(settings, searchEntry) { function parseAccelerator(accelerator) { let key, mods; - // if (accelerator.match(/Above_Tab/)) { - // let keymap = Gdk.Keymap.get_default(); - // let entries = keymap.get_entries_for_keycode(49)[1]; - // let keyval = keymap.lookup_key(entries[0]); - // let name = Gtk.accelerator_name(keyval, 0); - // accelerator = accelerator.replace('Above_Tab', name); - // } - - [success, key, mods] = Gtk.accelerator_parse(accelerator); + if (accelerator.match(/Above_Tab/)) { + accelerator = accelerator.replace('Above_Tab', 'grave'); + } + + [key, mods] = Gtk.accelerator_parse(accelerator); return [key, mods]; } @@ -738,7 +722,6 @@ function annotateKeybindings(model, settings) { if (conflict.length > 0) { let keystr = Settings.keycomboToKeystr(combo); tooltip = `${keystr} overrides ${conflict[0].conflicts} in ${conflict[0].settings.path}`; - model.set_value(iter, COLUMN_TOOLTIP, GLib.markup_escape_text(tooltip, -1)); model.set_value(iter, COLUMN_WARNING, true); @@ -750,6 +733,35 @@ function annotateKeybindings(model, settings) { }); } +function createFileChooserButton(settings, key, properties) { + const button = new Gtk.Button(); + syncStringSetting(settings, key, (path) => { + button.label = path == '' ? '(none)' : GLib.filename_display_basename(path); + }); + button.connect('clicked', () => { + const chooser = new Gtk.FileChooserDialog(properties); + let path = settings.get_string(key); + if (path !== undefined && path != '') chooser.set_file(Gio.File.new_from_path(value)); + chooser.add_button('Open', Gtk.ResponseType.OK); + chooser.add_button('Cancel', Gtk.ResponseType.CANCEL); + chooser.connect('response', (dialog, response) => { + if (response === Gtk.ResponseType.OK) { + settings.set_string(key, chooser.get_file().get_path()); + } + chooser.close(); + }); + chooser.show(); + }); + return button; +} + +function syncStringSetting(settings, key, callback) { + settings.connect('changed::' + key, () => { + callback(settings.get_string(key)); + }); + callback(settings.get_string(key)); +} + function init() { } @@ -761,5 +773,6 @@ function buildPrefsWidget() { } let settings = new SettingsWidget(selectedTab, selectedWorkspace || 0); let widget = settings.widget; + widget.show(); return widget; } From fdc4767adc7f45cadd0b8b9913082e3c5fa0c095 Mon Sep 17 00:00:00 2001 From: Tad Fisher Date: Tue, 21 Sep 2021 15:50:26 -0700 Subject: [PATCH 094/104] Improve prefs layout --- .dir-locals.el | 2 + Settings.ui | 365 +++++++++++++++++++------------------------------ prefs.js | 18 ++- 3 files changed, 152 insertions(+), 233 deletions(-) diff --git a/.dir-locals.el b/.dir-locals.el index 7729c9c0..0d24af65 100644 --- a/.dir-locals.el +++ b/.dir-locals.el @@ -5,4 +5,6 @@ (org-indent-mode . t)) (js-mode (mode . gnome-shell)) + (js2-mode + (js2-basic-offset . 4)) ) diff --git a/Settings.ui b/Settings.ui index f71737ec..d4afc406 100644 --- a/Settings.ui +++ b/Settings.ui @@ -17,7 +17,6 @@ 10 - True False @@ -46,35 +45,49 @@ 1 10 - - True - True + + paperwm_settings + + + crossfade + True - - True - True - 24 - 24 - 24 - 24 - vertical - 12 - - - True - False + + general + General + + + center + 36 + 36 + 36 + 36 + vertical + 12 + + + start + True + Gaps and margins + + + + + - True False none + 24 + - True + False False - True False 12 12 @@ -83,7 +96,6 @@ 32 - True False 1 Gap between windows @@ -96,7 +108,6 @@ - True 2 2 window_gap @@ -115,12 +126,11 @@ - True + False False - True False The minimum margin to the left and right monitor edge 12 @@ -130,7 +140,6 @@ 32 - True False 1 Horizontal margin @@ -143,7 +152,6 @@ - True 2 2 0 @@ -163,11 +171,10 @@ - True + False False - True False 12 12 @@ -176,7 +183,6 @@ 32 - True False 1 Top margin @@ -189,7 +195,6 @@ - True 2 2 0 @@ -209,11 +214,10 @@ - True + False False - True False 12 12 @@ -222,7 +226,6 @@ 32 - True False 1 Bottom margin @@ -235,7 +238,6 @@ - True 2 2 0 @@ -255,38 +257,30 @@ - + - True - False - 6 - 6 - 6 - 6 - Gaps and margins + start + True + Three-finger swipes - + - - - - - True - False - True False none + 24 + - True + False False - True False 12 12 @@ -296,7 +290,6 @@ 32 - True False 1 Horizontal sensitivity @@ -309,7 +302,6 @@ - True True True 2 @@ -328,7 +320,6 @@ - True False 1 Horizontal friction @@ -341,7 +332,6 @@ - True 2 2 0,0 @@ -362,11 +352,10 @@ - True + False False - True False 12 12 @@ -376,7 +365,6 @@ 32 - True False 1 Vertical sensitivity @@ -389,7 +377,6 @@ - True 2 2 0 @@ -406,7 +393,6 @@ - True False 1 Vertical friction @@ -419,7 +405,6 @@ - True 2 2 0,0 @@ -440,36 +425,29 @@ - + - True - False - 6 - 6 - Three finger swipes + start + True + Toggles - + - - - - - True - False - True False none + - True + False False - True False 12 12 @@ -478,7 +456,6 @@ 32 - True False 1 Show scratch windows in overview @@ -491,7 +468,6 @@ - True False 0 @@ -511,11 +487,10 @@ - True + False False - True False 12 12 @@ -524,7 +499,6 @@ 32 - True False 1 Disable hot corner @@ -537,7 +511,6 @@ - True 1 0 @@ -550,11 +523,10 @@ - True + False False - True False 12 12 @@ -563,7 +535,6 @@ 32 - True False 1 Make topbar follow monitor focus @@ -576,7 +547,6 @@ - True 1 0 @@ -589,58 +559,48 @@ + + + + + + + workspaces + Workspaces + + + False + center + 36 + 36 + 36 + 36 + vertical + 12 - True - False - 6 - 6 - 6 - 6 - Toggles + start + True + All workspaces - - - - - - - True - False - _General - 1 - - - - - True - False - 24 - 24 - 24 - 24 - vertical - 24 - - - True - False - True False none + 24 + - True + False False - True False Only supports static backgrounds 12 @@ -650,7 +610,6 @@ 24 - True False 1 Use default GNOME Shell background @@ -663,7 +622,6 @@ - True center 2 @@ -673,7 +631,6 @@ - True 1 Launch gnome background picker org.gnome.Settings-symbolic @@ -691,36 +648,27 @@ - True - False - 6 - 6 - 6 - 6 - All workspaces + start + True + Per workspace - - - - - True - False - True False none + - True + False False - True False 12 12 @@ -729,7 +677,6 @@ 32 - True False 1 Configure workspace @@ -742,7 +689,6 @@ - True False center @@ -757,13 +703,12 @@ - True + False False 100 80 - True False @@ -774,103 +719,76 @@ - - - True - False - 6 - 6 - 6 - 6 - Per workspace - - - - - - - - - - - True - False - _Workspaces - 1 + - - True - False - vertical - - - True - True - - - - - True - never + + keybindings + Keybindings + + + False + vertical - - True - False + + True + + + + + never - - True + False - 24 - 24 - 24 - 24 - 1 - vertical - - - - - - - + + False + 24 + 24 + 24 + 24 + 1 + vertical + + + + + + + + + + - - - - - - True - False - _Keybindings - 1 + + + - True False vertical - 12 + 12 + 12 + 12 + 12 + 6 - True False <b>PaperWM</b> 1 @@ -879,7 +797,6 @@ - True False $VERSION 1 @@ -888,7 +805,6 @@ - True False Tiled scrollable window manager extension for Gnome Shell center @@ -897,7 +813,6 @@ - True Webpage 1 https://github.com/paperwm/PaperWM @@ -905,13 +820,9 @@ - - - True - False - _About - 1 - - + + + about_popover + dialog-information-symbolic diff --git a/prefs.js b/prefs.js index 1ca2244f..9a9c6479 100644 --- a/prefs.js +++ b/prefs.js @@ -65,9 +65,10 @@ class SettingsWidget { this.builder = Gtk.Builder.new_from_file(Extension.path + '/Settings.ui'); - this._notebook = this.builder.get_object('paperwm_settings'); - this.widget = this._notebook; - this._notebook.page = selectedTab; + this.aboutButton = this.builder.get_object('about_button'); + this.switcher = this.builder.get_object('switcher'); + this.widget = this.builder.get_object('paperwm_settings'); + this.widget.pages.select_item(selectedTab, true); this._backgroundFilter = new Gtk.FileFilter(); this._backgroundFilter.add_pixbuf_formats(); @@ -772,7 +773,12 @@ function buildPrefsWidget() { selectedTab = 1; } let settings = new SettingsWidget(selectedTab, selectedWorkspace || 0); - let widget = settings.widget; - widget.show(); - return widget; + GLib.idle_add(GLib.PRIORITY_DEFAULT_IDLE, () => { + let window = settings.widget.get_root(); + let headerbar = window.get_titlebar(); + headerbar.pack_start(settings.aboutButton); + headerbar.set_title_widget(settings.switcher); + return false; + }); + return settings.widget; } From e704f0fb56c9fa9d2ef88e2259ec166af5d4e506 Mon Sep 17 00:00:00 2001 From: Tad Fisher Date: Wed, 22 Sep 2021 13:48:45 -0700 Subject: [PATCH 095/104] prefs: Clean up file chooser handling --- Settings.ui | 34 +++++++++++++++++++++----- prefs.js | 69 ++++++++++++++++++++++++++++++++++++++++++----------- 2 files changed, 83 insertions(+), 20 deletions(-) diff --git a/Settings.ui b/Settings.ui index d4afc406..f02957bb 100644 --- a/Settings.ui +++ b/Settings.ui @@ -64,6 +64,7 @@ 36 vertical 12 + 480 start @@ -577,6 +578,7 @@ 36 vertical 12 + 480 start @@ -701,6 +703,18 @@ + + + False + False + + + horizontal + True + + + + False @@ -732,8 +746,14 @@ False vertical - - True + + True + True + + + True + + @@ -745,11 +765,13 @@ False - 24 - 24 - 24 - 24 + 36 + 36 + 36 + 36 1 + center + 480 vertical diff --git a/prefs.js b/prefs.js index 9a9c6479..ac605477 100644 --- a/prefs.js +++ b/prefs.js @@ -311,19 +311,19 @@ class SettingsWidget { let list = new Gtk.Box({ orientation: Gtk.Orientation.VERTICAL, - can_focus: false, + focusable: false, }); - list.append(new Gtk.LevelBar()); - let nameEntry = new Gtk.Entry(); let colorButton = new Gtk.ColorButton(); // Background - let backgroundBox = new Gtk.Box({spacing: 32}); // same spacing as used in glade for default background + let backgroundBox = new Gtk.Box({spacing: 16}); let background = createFileChooserButton( settings, 'background', + 'image-x-generic', + 'document-open-symbolic', { action: Gtk.FileChooserAction.OPEN, title: 'Select workspace background', @@ -333,15 +333,22 @@ class SettingsWidget { transient_for: this.widget.get_root() } ); - let clearBackground = new Gtk.Button({label: 'Clear', sensitive: settings.get_string('background') != ''}); - - let hideTopBarSwitch = new Gtk.Switch({active: !settings.get_boolean('show-top-bar')}); + let clearBackground = new Gtk.Button({ + icon_name: 'edit-clear-symbolic', + tooltip_text: 'Clear workspace background', + sensitive: settings.get_string('background') != '' + }); backgroundBox.append(background); backgroundBox.append(clearBackground); + let hideTopBarSwitch = new Gtk.Switch({active: !settings.get_boolean('show-top-bar')}); + + let directoryBox = new Gtk.Box({spacing: 16}); let directoryChooser = createFileChooserButton( settings, 'directory', + 'folder', + 'folder-open-symbolic', { action: Gtk.FileChooserAction.SELECT_FOLDER, title: 'Select workspace background', @@ -350,12 +357,19 @@ class SettingsWidget { transient_for: this.widget.get_root() } ); + let clearDirectory = new Gtk.Button({ + icon_name: 'edit-clear-symbolic', + tooltip_text: 'Clear workspace directory', + sensitive: settings.get_string('directory') != '' + }); + directoryBox.append(directoryChooser); + directoryBox.append(clearDirectory); list.append(createRow('Name', nameEntry)); list.append(createRow('Color', colorButton)); list.append(createRow('Background', backgroundBox)); list.append(createRow('Hide top bar', hideTopBarSwitch)); - list.append(createRow('Directory', directoryChooser)); + list.append(createRow('Directory', directoryBox)); let rgba = new Gdk.RGBA(); let color = settings.get_string('color'); @@ -393,6 +407,9 @@ class SettingsWidget { clearBackground.connect('clicked', () => { settings.reset('background'); + }); + + settings.connect('changed::background', () => { clearBackground.sensitive = settings.get_string('background') != ''; }); @@ -400,6 +417,14 @@ class SettingsWidget { settings.set_boolean('show-top-bar', !state); }); + clearDirectory.connect('clicked', () => { + settings.reset('directory'); + }); + + settings.connect('changed::directory', () => { + clearDirectory.sensitive = settings.get_string('directory') != ''; + }); + return list; } @@ -734,22 +759,37 @@ function annotateKeybindings(model, settings) { }); } -function createFileChooserButton(settings, key, properties) { - const button = new Gtk.Button(); - syncStringSetting(settings, key, (path) => { - button.label = path == '' ? '(none)' : GLib.filename_display_basename(path); +function createFileChooserButton(settings, key, iconName, symbolicIconName, properties) { + const buttonIcon = Gtk.Image.new_from_icon_name(iconName); + const buttonLabel = new Gtk.Label(); + const buttonBox = new Gtk.Box({ + orientation: Gtk.Orientation.HORIZONTAL, + spacing: 8 + }); + + buttonBox.append(buttonIcon); + buttonBox.append(buttonLabel); + if (symbolicIconName) { + buttonBox.append(new Gtk.Image({icon_name: symbolicIconName, margin_start: 8})); + } + + const button = new Gtk.Button({child: buttonBox}); + + syncStringSetting(settings, key, path => { + buttonIcon.visible = path != ''; + buttonLabel.label = path == '' ? '(None)' : GLib.filename_display_basename(path); }); button.connect('clicked', () => { const chooser = new Gtk.FileChooserDialog(properties); let path = settings.get_string(key); - if (path !== undefined && path != '') chooser.set_file(Gio.File.new_from_path(value)); + if (path != '') chooser.set_file(Gio.File.new_for_path(path)); chooser.add_button('Open', Gtk.ResponseType.OK); chooser.add_button('Cancel', Gtk.ResponseType.CANCEL); chooser.connect('response', (dialog, response) => { if (response === Gtk.ResponseType.OK) { settings.set_string(key, chooser.get_file().get_path()); } - chooser.close(); + chooser.destroy(); }); chooser.show(); }); @@ -775,6 +815,7 @@ function buildPrefsWidget() { let settings = new SettingsWidget(selectedTab, selectedWorkspace || 0); GLib.idle_add(GLib.PRIORITY_DEFAULT_IDLE, () => { let window = settings.widget.get_root(); + window.modal = false; let headerbar = window.get_titlebar(); headerbar.pack_start(settings.aboutButton); headerbar.set_title_widget(settings.switcher); From e1933e7e1d937067a1a079dd7a3f7289b3108f55 Mon Sep 17 00:00:00 2001 From: smichel17 Date: Fri, 24 Dec 2021 11:43:13 +0000 Subject: [PATCH 096/104] Add note about branches to the readme --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 12648531..ab9e1637 100644 --- a/README.md +++ b/README.md @@ -2,9 +2,9 @@ PaperWM's maintainer(s) have been unresponsive since April, 2021. However, they have not said that they are stepping down from maintainership; it may just be a busy period in their lives, etc. -Meanwhile, Gnome 40 introduced changes which broke PaperWM. Several people independently started fixing the issues for themselves. This repository is for them to coordinate, so they don't end up introducing incompatible patches; **the goal is for this work to eventually be merged back upstream**, not to start an independent development effort. +Meanwhile, Gnome 40 introduced changes which broke PaperWM. Several people independently started fixing the issues for themselves. This repository is for them to coordinate, so they don't end up introducing incompatible patches; **the goal is for this work to eventually be merged back upstream**, not to start an independent development effort. This is happening on the [`next-release` branch](https://github.com/PaperWM-community/PaperWM/tree/next-release), and there is a [draft pull request](https://github.com/paperwm/PaperWM/pull/396) to merge it back upstream. (`develop` is still set as the default branch here, so that people will see this message, which shouldn't be part of the PR 😄) -For details, see https://github.com/paperwm/PaperWM/issues/376#issuecomment-988178131 (and comments below). +For details, see https://github.com/paperwm/PaperWM/issues/376#issuecomment-988178131 (and comments below) and the [discussions](https://github.com/PaperWM-community/PaperWM/discussions) on this repository. # PaperWM # From 59756808b812aef7c9de0e23275ef97d1edcdc26 Mon Sep 17 00:00:00 2001 From: Tad Fisher Date: Wed, 29 Sep 2021 22:08:02 -0700 Subject: [PATCH 097/104] Rewrite keybindings editor --- KeybindingsComboRow.ui | 112 +++++ KeybindingsPane.ui | 44 ++ KeybindingsRow.ui | 124 +++++ Settings.ui | 48 +- keybindings.js | 2 +- prefs.js | 127 ++--- prefsKeybinding.js | 1060 ++++++++++++++++++++++++++++++++++++++++ resources/prefs.css | 43 ++ settings.js | 11 + 9 files changed, 1424 insertions(+), 147 deletions(-) create mode 100644 KeybindingsComboRow.ui create mode 100644 KeybindingsPane.ui create mode 100644 KeybindingsRow.ui create mode 100644 prefsKeybinding.js create mode 100644 resources/prefs.css diff --git a/KeybindingsComboRow.ui b/KeybindingsComboRow.ui new file mode 100644 index 00000000..b299ef38 --- /dev/null +++ b/KeybindingsComboRow.ui @@ -0,0 +1,112 @@ + + + + + + + + + False + vertical + 12 + 12 + 12 + 12 + 8 + + + + Conflicts: + + + + + + + none + + + + + + + + diff --git a/KeybindingsPane.ui b/KeybindingsPane.ui new file mode 100644 index 00000000..04605bab --- /dev/null +++ b/KeybindingsPane.ui @@ -0,0 +1,44 @@ + + + + + diff --git a/KeybindingsRow.ui b/KeybindingsRow.ui new file mode 100644 index 00000000..db1187bf --- /dev/null +++ b/KeybindingsRow.ui @@ -0,0 +1,124 @@ + + + + + diff --git a/Settings.ui b/Settings.ui index f02957bb..06e67545 100644 --- a/Settings.ui +++ b/Settings.ui @@ -742,53 +742,7 @@ keybindings Keybindings - - False - vertical - - - True - True - - - True - - - - - - - never - - - False - - - False - 36 - 36 - 36 - 36 - 1 - center - 480 - vertical - - - - - - - - - - - - - - - - + diff --git a/keybindings.js b/keybindings.js index 0e4d474c..bb756a21 100644 --- a/keybindings.js +++ b/keybindings.js @@ -505,7 +505,7 @@ function enableAction(action) { } else { if (keycomboMap[action.keycombo]) { - Utils.warn("Other action bound to", action.keystr, keycomboMap[action.keycombo].name) + Utils.warn("Other action bound to", action.keystr, keycomboMap[action.keycombo].name); return Meta.KeyBindingAction.NONE; } diff --git a/prefs.js b/prefs.js index ac605477..e26f3091 100644 --- a/prefs.js +++ b/prefs.js @@ -4,11 +4,11 @@ const GLib = imports.gi.GLib; const GObject = imports.gi.GObject; const Gtk = imports.gi.Gtk; const Gdk = imports.gi.Gdk; -const Mainloop = imports.mainloop; const ExtensionUtils = imports.misc.extensionUtils; const Extension = ExtensionUtils.getCurrentExtension(); const Convenience = Extension.imports.convenience; +const { KeybindingsPane } = Extension.imports.prefsKeybinding; const WORKSPACE_KEY = 'org.gnome.Shell.Extensions.PaperWM.Workspace'; const KEYBINDINGS_KEY = 'org.gnome.Shell.Extensions.PaperWM.Keybindings'; @@ -47,7 +47,7 @@ function swapArrayElements(array, i, j) { return array; } -function ok(okValue) { +function getOk(okValue) { if(okValue[0]) { return okValue[1]; } else { @@ -216,91 +216,6 @@ class SettingsWidget { workspaceCombo.set_active(selectedWorkspace); - // Keybindings - - let settings = Convenience.getSettings(KEYBINDINGS_KEY); - let box = this.builder.get_object('keybindings'); - box.spacing = 12; - - let searchEntry = this.builder.get_object('keybinding_search'); - - let windowFrame = new Gtk.Frame({label: _('Windows'), - label_xalign: 0.5}); - let windows = createKeybindingWidget(settings, searchEntry); - box.append(windowFrame); - windowFrame.set_child(windows); - - - ['new-window', 'close-window', 'switch-next', 'switch-previous', - 'switch-left', 'switch-right', 'switch-up', 'switch-down', - 'switch-first', 'switch-last', 'live-alt-tab', 'live-alt-tab-backward', - 'move-left', 'move-right', 'move-up', 'move-down', - 'slurp-in', 'barf-out', 'center-horizontally', - 'paper-toggle-fullscreen', 'toggle-maximize-width', 'resize-h-inc', - 'resize-h-dec', 'resize-w-inc', 'resize-w-dec', 'cycle-width', - 'cycle-height', 'take-window'] - .forEach(k => { - addKeybinding(windows.model.child_model, settings, k); - }); - - annotateKeybindings(windows.model.child_model, settings); - - let workspaceFrame = new Gtk.Frame({label: _('Workspaces'), - label_xalign: 0.5}); - let workspaces = createKeybindingWidget(settings, searchEntry); - box.append(workspaceFrame); - workspaceFrame.set_child(workspaces); - - ['previous-workspace', 'previous-workspace-backward', - 'move-previous-workspace', 'move-previous-workspace-backward', - 'switch-up-workspace', 'switch-down-workspace', - 'move-up-workspace', 'move-down-workspace' - ] - .forEach(k => { - addKeybinding(workspaces.model.child_model, settings, k); - }); - - annotateKeybindings(workspaces.model.child_model, settings); - - let monitorFrame = new Gtk.Frame({label: _('Monitors'), - label_xalign: 0.5}); - let monitors = createKeybindingWidget(settings, searchEntry); - box.append(monitorFrame); - monitorFrame.set_child(monitors); - - ['switch-monitor-right', - 'switch-monitor-left', - 'switch-monitor-above', - 'switch-monitor-below', - 'move-monitor-right', - 'move-monitor-left', - 'move-monitor-above', - 'move-monitor-below', - ].forEach(k => { - addKeybinding(monitors.model.child_model, settings, k); - }); - - annotateKeybindings(monitors.model.child_model, settings); - - - let scratchFrame = new Gtk.Frame({label: _('Scratch layer'), - label_xalign: 0.5}); - let scratch = createKeybindingWidget(settings, searchEntry); - box.append(scratchFrame); - scratchFrame.set_child(scratch); - - ['toggle-scratch-layer', 'toggle-scratch', "toggle-scratch-window"] - .forEach(k => { - addKeybinding(scratch.model.child_model, settings, k); - }); - - annotateKeybindings(scratch.model.child_model, settings); - - searchEntry.connect('changed', () => { - [windows, workspaces, monitors, scratch].map(tw => tw.model).forEach(m => m.refilter()); - }); - - // About let versionLabel = this.builder.get_object('extension_version'); let version = Extension.metadata.version.toString(); @@ -456,6 +371,10 @@ function createRow(text, widget, signal, handler) { return box; } +function createKeybindingSection(settings, searchEntry) { + let model = new Gtk.ListStore(); +} + function createKeybindingWidget(settings, searchEntry) { let model = new Gtk.TreeStore(); let filteredModel = new Gtk.TreeModelFilter({child_model: model}); @@ -463,7 +382,7 @@ function createKeybindingWidget(settings, searchEntry) { (model, iter) => { let desc = model.get_value(iter, COLUMN_DESCRIPTION); - if(ok(model.iter_parent(iter)) || desc === null) { + if(getOk(model.iter_parent(iter)) || desc === null) { return true; } @@ -519,7 +438,7 @@ function createKeybindingWidget(settings, searchEntry) { accelRenderer.connect("accel-edited", (accelRenderer, path, key, mods, hwCode) => { - let iter = ok(filteredModel.get_iter_from_string(path)); + let iter = getOk(filteredModel.get_iter_from_string(path)); if(!iter) return; @@ -538,7 +457,7 @@ function createKeybindingWidget(settings, searchEntry) { if (index === -1) { accels.push(accelString); } else { - accels[index] = accelString + accels[index] = accelString; } settings.set_strv(id, accels); @@ -547,7 +466,7 @@ function createKeybindingWidget(settings, searchEntry) { model.set_value(iter, COLUMN_INDEX, accels.length-1); model.set_value(iter, COLUMN_DESCRIPTION, "..."); - let parent = ok(model.iter_parent(iter)); + let parent = getOk(model.iter_parent(iter)); newEmptyRow = model.insert_after(parent, iter); } else if (index === 0 && !model.iter_has_child(iter)) { newEmptyRow = model.insert(iter, -1); @@ -568,7 +487,7 @@ function createKeybindingWidget(settings, searchEntry) { accelRenderer.connect("accel-cleared", (accelRenderer, path) => { - let iter = ok(filteredModel.get_iter_from_string(path)); + let iter = getOk(filteredModel.get_iter_from_string(path)); if(!iter) return; @@ -593,7 +512,7 @@ function createKeybindingWidget(settings, searchEntry) { if (index === 0) { parent = iter.copy(); } else { - parent = ok(model.iter_parent(iter)); + parent = getOk(model.iter_parent(iter)); } nextSibling = parent.copy(); @@ -627,7 +546,7 @@ function createKeybindingWidget(settings, searchEntry) { resetColumn.add_attribute(resetRenderer, "visible", COLUMN_RESET); resetRenderer.connect('toggled', (renderer, path) => { - let iter = ok(filteredModel.get_iter_from_string(path)); + let iter = getOk(filteredModel.get_iter_from_string(path)); if(!iter) return; iter = filteredModel.convert_iter_to_child_iter(iter); @@ -638,7 +557,7 @@ function createKeybindingWidget(settings, searchEntry) { model.set_value(iter, COLUMN_RESET, false); } - let parent = ok(model.iter_parent(iter)) || iter.copy(); + let parent = getOk(model.iter_parent(iter)) || iter.copy(); let nextSibling = parent.copy(); if(!model.iter_next(nextSibling)) nextSibling = null; @@ -663,7 +582,10 @@ function parseAccelerator(accelerator) { accelerator = accelerator.replace('Above_Tab', 'grave'); } - [key, mods] = Gtk.accelerator_parse(accelerator); + [ok, key, mods] = Gtk.accelerator_parse(accelerator); + + log(`PaperWM: parseAccelerator(${accelerator}) -> [${key}, ${mods}]`); + return [key, mods]; } @@ -671,7 +593,7 @@ function transpose(colValPairs) { let colKeys = [], values = []; colValPairs.forEach(([k, v]) => { colKeys.push(k); values.push(v); - }) + }); return [colKeys, values]; } @@ -740,13 +662,13 @@ function annotateKeybindings(model, settings) { let accels = settings.get_strv(id); let index = model.get_value(iter, COLUMN_INDEX); if (index === -1 || accels.length === 0) - return; + return true; let combo = Settings.keystrToKeycombo(accels[index]); let conflict = warning(id, combo); let tooltip = null; if (conflict.length > 0) { - let keystr = Settings.keycomboToKeystr(combo); + let keystr = Settings.keycomboToKeylab(combo); tooltip = `${keystr} overrides ${conflict[0].conflicts} in ${conflict[0].settings.path}`; model.set_value(iter, COLUMN_TOOLTIP, GLib.markup_escape_text(tooltip, -1)); @@ -804,6 +726,13 @@ function syncStringSetting(settings, key, callback) { } function init() { + const provider = new Gtk.CssProvider(); + provider.load_from_path(Extension.dir.get_path() + '/resources/prefs.css'); + Gtk.StyleContext.add_provider_for_display( + Gdk.Display.get_default(), + provider, + Gtk.STYLE_PROVIDER_PRIORITY_APPLICATION + ); } function buildPrefsWidget() { diff --git a/prefsKeybinding.js b/prefsKeybinding.js new file mode 100644 index 00000000..f0f35c1f --- /dev/null +++ b/prefsKeybinding.js @@ -0,0 +1,1060 @@ +'use strict'; + +const Gio = imports.gi.Gio; +const GLib = imports.gi.GLib; +const GObject = imports.gi.GObject; +const Gtk = imports.gi.Gtk; +const Gdk = imports.gi.Gdk; + +const ExtensionUtils = imports.misc.extensionUtils; +const Extension = ExtensionUtils.getCurrentExtension(); +const Convenience = Extension.imports.convenience; +const Settings = Extension.imports.settings; + +// TODO gettext translations +const _ = s => s; + +const KEYBINDINGS_KEY = 'org.gnome.Shell.Extensions.PaperWM.Keybindings'; + +const sections = { + windows: 'Windows', + workspaces: 'Workspaces', + monitors: 'Monitors', + scratch: 'Scratch layer', +}; + +const actions = { + windows: [ + 'new-window', + 'close-window', + 'switch-next', + 'switch-previous', + 'switch-left', + 'switch-right', + 'switch-up', + 'switch-down', + 'switch-first', + 'switch-last', + 'live-alt-tab', + 'live-alt-tab-backward', + 'move-left', + 'move-right', + 'move-up', + 'move-down', + 'slurp-in', + 'barf-out', + 'center-horizontally', + 'paper-toggle-fullscreen', + 'toggle-maximize-width', + 'resize-h-inc', + 'resize-h-dec', + 'resize-w-inc', + 'resize-w-dec', + 'cycle-width', + 'cycle-height', + 'take-window', + ], + workspaces: [ + 'previous-workspace', + 'previous-workspace-backward', + 'move-previous-workspace', + 'move-previous-workspace-backward', + 'switch-up-workspace', + 'switch-down-workspace', + 'move-up-workspace', + 'move-down-workspace', + ], + monitors: [ + 'switch-monitor-right', + 'switch-monitor-left', + 'switch-monitor-above', + 'switch-monitor-below', + 'move-monitor-right', + 'move-monitor-left', + 'move-monitor-above', + 'move-monitor-below', + ], + scratch: [ + 'toggle-scratch-layer', + 'toggle-scratch', + 'toggle-scratch-window' + ], +}; + +const forbiddenKeyvals = [ + Gdk.KEY_Home, + Gdk.KEY_Left, + Gdk.KEY_Up, + Gdk.KEY_Right, + Gdk.KEY_Down, + Gdk.KEY_Page_Up, + Gdk.KEY_Page_Down, + Gdk.KEY_End, + Gdk.KEY_Tab, + Gdk.KEY_KP_Enter, + Gdk.KEY_Return, + Gdk.KEY_Mode_switch +]; + +function isValidBinding(combo) { + if ((combo.mods == 0 || combo.mods == Gdk.ModifierType.SHIFT_MASK) && combo.keycode != 0) { + const keyval = combo.keyval; + if ((keyval >= Gdk.KEY_a && keyval <= Gdk.KEY_z) + || (keyval >= Gdk.KEY_A && keyval <= Gdk.KEY_Z) + || (keyval >= Gdk.KEY_0 && keyval <= Gdk.KEY_9) + || (keyval >= Gdk.KEY_kana_fullstop && keyval <= Gdk.KEY_semivoicedsound) + || (keyval >= Gdk.KEY_Arabic_comma && keyval <= Gdk.KEY_Arabic_sukun) + || (keyval >= Gdk.KEY_Serbian_dje && keyval <= Gdk.KEY_Cyrillic_HARDSIGN) + || (keyval >= Gdk.KEY_Greek_ALPHAaccent && keyval <= Gdk.KEY_Greek_omega) + || (keyval >= Gdk.KEY_hebrew_doublelowline && keyval <= Gdk.KEY_hebrew_taf) + || (keyval >= Gdk.KEY_Thai_kokai && keyval <= Gdk.KEY_Thai_lekkao) + || (keyval >= Gdk.KEY_Hangul_Kiyeog && keyval <= Gdk.KEY_Hangul_J_YeorinHieuh) + || (keyval == Gdk.KEY_space && combo.mods == 0) + || forbiddenKeyvals.includes(keyval)) { + return false; + } + } + + // Allow Tab in addition to accelerators allowed by GTK + if (!Gtk.accelerator_valid(combo.keyval, combo.mods) && + (combo.keyval != Gdk.KEY_Tab || combo.mods == 0)) { + return false; + } + + return true; +} + +function isEmptyBinding(combo) { + return combo.keyval == 0 && combo.mods == 0 && combo.keycode == 0; +} + +const Combo = GObject.registerClass({ + GTypeName: 'Combo', + Properties: { + keycode: GObject.ParamSpec.uint( + 'keycode', + 'Keycode', + 'Key code', + GObject.ParamFlags.READWRITE | GObject.ParamFlags.CONSTRUCT_ONLY, + 0, + GLib.MAXUINT32, + 0 + ), + keyval: GObject.ParamSpec.uint( + 'keyval', + 'Keyval', + 'Key value', + GObject.ParamFlags.READWRITE | GObject.ParamFlags.CONSTRUCT_ONLY, + 0, + GLib.MAXUINT32, + 0 + ), + mods: GObject.ParamSpec.uint( + 'mods', + 'Mods', + 'Key modifiers', + GObject.ParamFlags.READWRITE | GObject.ParamFlags.CONSTRUCT_ONLY, + 0, + GLib.MAXUINT32, + 0 + ), + keystr: GObject.ParamSpec.string( + 'keystr', + 'Keystr', + 'Key string', + GObject.ParamFlags.READABLE, + null + ), + label: GObject.ParamSpec.string( + 'label', + 'Label', + 'Key label', + GObject.ParamFlags.READABLE, + null + ), + disabled: GObject.ParamSpec.boolean( + 'disabled', + 'Disabled', + 'Disabled sentinel', + GObject.ParamFlags.READABLE, + false + ), + placeholder: GObject.ParamSpec.boolean( + 'placeholder', + 'Placeholder', + 'Placeholder sentinel', + GObject.ParamFlags.READWRITE | GObject.ParamFlags.CONSTRUCT_ONLY, + false + ), + }, +}, class Combo extends GObject.Object { + _init(params) { + super._init(params); + } + + get keycode() { + if (this.disabled) { + return 0; + } else if (!this._keycode) { + const keymap = Gdk.Keymap.get_for_display(Gdk.Display.get_default()); + const [ok, keys] = keymap.get_entries_for_keyval(); + if (ok && keys.length) { + return keys[0].keycode; + } else { + return 0; + } + } else { + return this._keycode; + } + } + + get keystr() { + if (this.disabled) + return ''; + else + return Gtk.accelerator_name(this.keyval, this.mods); + } + + get label() { + if (this.disabled) + return _('Disabled'); + else + return Gtk.accelerator_get_label(this.keyval, this.mods); + } + + get disabled() { + return !this.keyval && !this.mods; + } + + toString() { + return `Combo(keycode=${this.keycode}, keyval=${this.keyval}, mods=${this.mods})`; + } +}); + +const Keybinding = GObject.registerClass({ + GTypeName: 'Keybinding', + Implements: [Gio.ListModel], + Properties: { + section: GObject.ParamSpec.string( + 'section', + 'Section', + 'Keybinding section title', + GObject.ParamFlags.READWRITE | GObject.ParamFlags.CONSTRUCT_ONLY, + null + ), + action: GObject.ParamSpec.string( + 'action', + 'Action', + 'Keybinding action ID', + GObject.ParamFlags.READWRITE | GObject.ParamFlags.CONSTRUCT_ONLY, + null + ), + description: GObject.ParamSpec.string( + 'description', + 'Description', + 'Keybinding action description', + GObject.ParamFlags.READABLE, + null + ), + label: GObject.ParamSpec.string( + 'label', + 'Label', + 'Keybinding combo label', + GObject.ParamFlags.READABLE, + null + ), + combos: GObject.ParamSpec.object( + 'combos', + 'Combos', + 'Key combos', + GObject.ParamFlags.READABLE, + Gio.ListModel.$gtype + ), + modified: GObject.ParamSpec.boolean( + 'modified', + 'Modified', + 'True if the user has modified the shortcut from its default value', + GObject.ParamFlags.READABLE, + false + ), + enabled: GObject.ParamSpec.boolean( + 'enabled', + 'Enabled', + 'True if this keybinding has any shortcuts', + GObject.ParamFlags.READABLE, + false + ) + }, + Signals: { + changed: {} + } +}, class Keybinding extends GObject.Object { + _init(params = {}) { + super._init(params); + + this._settings = Convenience.getSettings(KEYBINDINGS_KEY); + + this._description = _(this._settings.settings_schema.get_key(this.action).get_summary()); + + this._combos = new Gio.ListStore(); + this._combos.connect('items-changed', (_, position, removed, added) => { + this.items_changed(position, removed, added); + this.notify('label'); + }); + + this._settings.connect(`changed::${this.action}`, () => this._load()); + + GLib.idle_add(0, () => this._load()); + } + + get description() { + return this._description; + } + + get label() { + const labels = [...this.combos] + .filter(c => !isEmptyBinding(c)) + .map(c => c.label); + + let label = ''; + if (labels.length == 0) { + label = _('Disabled'); + } else { + label = labels.join(', '); + } + + if (this.modified) { + label = `${label}`; + } + + return label; + } + + get combos() { + return this._combos; + } + + get modified() { + return this._settings.get_user_value(this.action) != null; + } + + get enabled() { + return [...this.combos].some((c) => !c.disabled); + } + + vfunc_get_item_type() { + return Combo.$gtype; + } + + vfunc_get_item(position) { + return this.combos.get_item(position); + } + + vfunc_get_n_items() { + return this.combos.get_n_items(); + } + + add(combo) { + const [found, _] = this.find(combo); + if (found) return; + this.combos.append(combo); + if (!combo.disabled) { + this._store(); + } + } + + remove(combo) { + const [found, pos] = this.find(combo); + if (!found) return; + this.combos.remove(pos); + if (this.combos.get_n_items() == 0) + this.combos.append(new Combo()); + this._store(); + } + + replace(oldCombo, newCombo) { + const [found, _] = this.find(newCombo); + if (found) return; + const [oldFound, pos] = this.find(oldCombo); + if (oldFound) { + this.combos.splice(pos, 1, [newCombo]); + } else { + this.combos.append(newCombo); + } + this._store(); + } + + disable() { + this._settings.set_strv(this.action, ['']); + } + + reset() { + if (this._settings.get_user_value(this.action)) { + this._settings.reset(this.action); + } + } + + find(combo) { + const pos = [...this.combos].findIndex(c => c.keystr === combo.keystr); + if (pos == -1) { + return [false]; + } else { + return [true, pos]; + } + } + + _load() { + const keystrs = this._settings.get_strv(this.action) || []; + let combos = keystrs + .map(this._translateAboveTab) + .map(keystr => { + if (keystr != '') + return Gtk.accelerator_parse(keystr); + else + return [true, 0, 0]; + }) + .map(([, keyval, mods]) => new Combo({keyval: keyval, mods: mods})); + + if (combos.length == 0) { + combos.push(new Combo()); + } + + this.combos.splice(0, this.combos.get_n_items(), combos); + } + + _store() { + let filtered = [...this.combos] + .filter(c => !isEmptyBinding(c)) + .map(c => c.keystr); + if (filtered.length == 0) { + filtered = ['']; + } + this._settings.set_strv(this.action, filtered); + } + + _translateAboveTab(keystr) { + if (!keystr.match(/Above_Tab/)) { + return keystr; + } else { + let keyvals = aboveTabKeyvals(); + if (!keyvals) + return keystr.replace('Above_Tab', 'grave'); + + let keyname = Gdk.keyval_name(keyvals[0]); + return keystr.replace('Above_Tab', keyname); + } + } +}); + +var KeybindingsModel = GObject.registerClass({ + GTypeName: 'KeybindingsModel', + Implements: [Gio.ListModel], + Signals: { + 'collisions-changed': { + flags: GObject.SignalFlags.RUN_LAST | GObject.SignalFlags.DETAILED, + }, + }, +}, class KeybindingsModel extends GObject.Object { + _init(params={}) { + super._init(params); + + this._model = Gio.ListStore.new(Keybinding.$gtype); + this._model.connect('items-changed', (_, position, removed, added) => { + this.items_changed(position, removed, added); + }); + + this._combos = Gtk.FlattenListModel.new(this._model); + this._combos.connect('items-changed', () => { + // Room for optimization here. + this._updateCollisions(); + }); + + this._actionToBinding = new Map(); + + this._settings = Convenience.getSettings(KEYBINDINGS_KEY); + + GLib.idle_add(0, () => { this.load(); }); + } + + vfunc_get_item_type() { + return this._model.get_item_type(); + } + + vfunc_get_item(position) { + return this._model.get_item(position); + } + + vfunc_get_n_items() { + return this._model.get_n_items(); + } + + get collisions() { + if (this._collisions === undefined) { + this._collisions = new Map(); + this._updateCollisions(); + } + return this._collisions; + } + + getKeybinding(action) { + return this._actionToBinding.get(action); + } + + find(binding) { + return this._model.find(binding); + } + + load() { + let bindings = []; + for (const section in actions) { + for (const action of actions[section]) { + const binding = new Keybinding({ + section: section, + action: action + }); + bindings.push(binding); + this._actionToBinding.set(action, binding); + } + } + this._model.splice(0, this._model.get_n_items(), bindings); + } + + _updateCollisions(position, removed, added) { + let map = new Map(); + for (const binding of this._model) { + for (const combo of binding.combos) { + if (combo.disabled) continue; + map.set(combo.keystr, (map.get(combo.keystr) || new Set()).add(binding.action)); + } + } + let changed = new Set(); + for (const [keystr, actions] of map.entries()) { + if (actions.size > 1) { + if (!this.collisions.has(keystr)) { + for (const action of actions) { + changed.add(action); + } + } else { + let old = this.collisions.get(keystr); + for (const action of symmetricDifference(old, actions)) { + changed.add(action); + } + } + this.collisions.set(keystr, actions); + } else { + for (const action of actions) { + changed.add(action); + } + this.collisions.delete(keystr); + } + } + if (changed.size > 0) { + for (const action of changed) { + this.emit(`collisions-changed::${action}`); + } + } + } +}); + +const ComboRow = GObject.registerClass({ + GTypeName: 'ComboRow', + Template: Extension.dir.get_child('KeybindingsComboRow.ui').get_uri(), + InternalChildren: [ + 'stack', + 'shortcutPage', + 'placeholderPage', + 'editPage', + 'shortcutLabel', + 'deleteButton', + 'conflictButton', + 'conflictList', + ], + Properties: { + keybinding: GObject.ParamSpec.object( + 'keybinding', + 'Keybinding', + 'Keybinding', + GObject.ParamFlags.READWRITE | GObject.ParamFlags.CONSTRUCT_ONLY, + Keybinding.$gtype + ), + combo: GObject.ParamSpec.object( + 'combo', + 'Combo', + 'Key combo', + GObject.ParamFlags.READWRITE, + Combo.$gtype + ), + editing: GObject.ParamSpec.boolean( + 'editing', + 'Editing', + 'Editing', + GObject.ParamFlags.READWRITE, + false + ), + }, + Signals: { + 'collision-activated': { + param_types: [Keybinding.$gtype], + }, + }, +}, class ComboRow extends Gtk.ListBoxRow { + _init(params = {}) { + super._init(params); + + let controller; + controller = Gtk.EventControllerKey.new(); + controller.connect('key-pressed', (controller, keyval, keycode, state) => { + this._onKeyPressed(controller, keyval, keycode, state); + }); + this.add_controller(controller); + + controller = Gtk.EventControllerFocus.new(); + controller.connect('leave', () => { + this.editing = false; + }); + this.add_controller(controller); + + this._collisions = Gio.ListStore.new(Keybinding.$gtype); + + this._conflictList.bind_model(this._collisions, binding => this._createConflictRow(binding)); + + GLib.idle_add(0, () => this._updateState()); + } + + get combo() { + if (this._combo === undefined) + this._combo = null; + return this._combo; + } + + set combo(value) { + if (value && this._combo && this._combo.keystr == value.keystr) + return; + this._combo = value; + this.notify('combo'); + this._updateState(); + } + + get editing() { + if (this._editing === undefined) + this._editing = false; + return this._editing; + } + + set editing(value) { + if (this.editing === value) + return; + this._editing = value; + this.notify('editing'); + this._updateState(); + } + + get collisions() { + return [...this._collisions]; + } + + set collisions(value) { + this._collisions.splice(0, this._collisions.get_n_items(), value); + } + + _createConflictRow(binding) { + return new Gtk.Label({ + label: binding.description, + }); + } + + _onConflictRowActivated(list, row) { + const binding = this._collisions.get_item(row.get_index()); + this.emit('collision-activated', binding); + } + + _grabKeyboard() { + this.get_root().get_surface().inhibit_system_shortcuts(null); + } + + _ungrabKeyboard() { + this.get_root().get_surface().restore_system_shortcuts(); + } + + _onDeleteButtonClicked() { + GLib.idle_add(0, () => this.keybinding.remove(this.combo)); + } + + _onKeyPressed(controller, keyval, keycode, state) { + // Adapted from Control Center, cc-keyboard-shortcut-editor.c + + if (!this.editing) return Gdk.EVENT_PROPAGATE; + + let modmask = state & Gtk.accelerator_get_default_mod_mask(); + let keyvalLower = Gdk.keyval_to_lower(keyval); + + // Normalize + if (keyvalLower == Gdk.KEY_ISO_Left_Tab) { + keyvalLower = Gdk.KEY_Tab; + } + + // Put Shift back if it changed the case of the key + if (keyvalLower != keyval) { + modmask |= Gdk.ModifierType.SHIFT_MASK; + } + + if (keyvalLower == Gdk.KEY_Sys_Req && (modmask & Gdk.ModifierType.ALT_MASK) != 0) { + // Don't allow SysRq as a keybinding, but allow Alt+Print + keyvalLower = Gdk.KEY_Print; + } + + const event = controller.get_current_event(); + const isModifier = event.is_modifier(); + + // Escape cancels + if (!isModifier && modmask == 0 && keyvalLower == Gdk.KEY_Escape) { + this.editing = false; + if (this.combo.placeholder) { + this.keybinding.remove(this.combo); + } + return Gdk.EVENT_STOP; + } + + // Backspace deletes + if (!isModifier && modmask == 0 && keyvalLower == Gdk.KEY_BackSpace) { + this._updateKeybinding(new Combo()); + return Gdk.EVENT_STOP; + } + + // Remove CapsLock + modmask &= ~Gdk.ModifierType.LOCK_MASK; + + this._updateKeybinding(new Combo({keycode: keycode, keyval: keyvalLower, mods: modmask})); + + return Gdk.EVENT_STOP; + } + + _updateKeybinding(newCombo) { + let isValid = isValidBinding(newCombo); + let isEmpty = isEmptyBinding(newCombo); + + const oldCombo = this.combo; + if (isEmptyBinding(oldCombo) && isValid) { + this.editing = false; + this.keybinding.add(newCombo); + return; + } + + if (isEmpty) { + this.editing = false; + this.keybinding.remove(oldCombo); + return; + } + + if (isValid) { + this.editing = false; + this.keybinding.replace(oldCombo, newCombo); + } + } + + _updateState() { + if (this.editing) { + this.add_css_class('editing'); + this._stack.visible_child = this._editPage; + this.grab_focus(); + this._grabKeyboard(); + } else { + this.remove_css_class('editing'); + this._stack.visible_child = this._shortcutPage; + this._ungrabKeyboard(); + + if (this._combo && !this._combo.disabled) { + this._shortcutLabel.accelerator = this._combo.keystr; + this._deleteButton.visible = true; + this._conflictButton.visible = this.collisions.length > 0; + } else { + this._shortcutLabel.accelerator = ''; + this._deleteButton.visible = false; + } + } + } +}); + +const KeybindingsRow = GObject.registerClass({ + GTypeName: 'KeybindingsRow', + Template: Extension.dir.get_child('KeybindingsRow.ui').get_uri(), + InternalChildren: [ + 'header', + 'descLabel', + 'accelLabel', + 'conflictIcon', + 'revealer', + 'comboList', + ], + Properties: { + keybindings: GObject.ParamSpec.object( + 'keybindings', + 'Keybindings', + 'Keybindings model', + GObject.ParamFlags.READWRITE | GObject.ParamFlags.CONSTRUCT_ONLY, + KeybindingsModel.$gtype + ), + keybinding: GObject.ParamSpec.object( + 'keybinding', + 'Keybinding', + 'Keybinding', + GObject.ParamFlags.READWRITE | GObject.ParamFlags.CONSTRUCT_ONLY, + Keybinding.$gtype + ), + expanded: GObject.ParamSpec.boolean( + 'expanded', + 'Expanded', + 'Expanded', + GObject.ParamFlags.READWRITE, + false + ), + collisions: GObject.ParamSpec.jsobject( + 'collisions', + 'Collisions', + 'Colliding keybindings', + GObject.ParamFlags.READABLE, + ), + }, + Signals: { + 'collision-activated': { + param_types: [Keybinding.$gtype], + }, + }, +}, class KeybindingsRow extends Gtk.ListBoxRow { + _init(params = {}) { + super._init(params); + + this._actionGroup = new Gio.SimpleActionGroup(); + this.insert_action_group('keybinding', this._actionGroup); + + let action; + action = new Gio.SimpleAction({ + name: 'reset', + enabled: this.keybinding.modified + }); + action.connect('activate', () => this.keybinding.reset()); + this._actionGroup.add_action(action); + + action = new Gio.SimpleAction({name: 'add'}); + action.connect('activate', () => this.keybinding.add(new Combo({placeholder: true}))); + this._actionGroup.add_action(action); + + const gesture = Gtk.GestureClick.new(); + gesture.set_button(Gdk.BUTTON_PRIMARY); + gesture.connect('released', (controller) => { + this.expanded = !this.expanded; + controller.set_state(Gtk.EventSequenceState.CLAIMED); + }); + this._header.add_controller(gesture); + + this._descLabel.label = this.keybinding.description; + this._descLabel.tooltip_text = this.keybinding.description; + + this.keybinding.connect('notify::label', () => this._updateState()); + + this._comboList.bind_model(this.keybinding, (combo) => this._createRow(combo)); + + this.keybindings.connect( + `collisions-changed::${this.keybinding.action}`, + () => { this._onCollisionsChanged(); } + ); + + this._updateState(); + } + + get expanded() { + if (this._expanded === undefined) + this._expanded = false; + return this._expanded; + } + + set expanded(value) { + if (this._expanded === value) + return; + + this._expanded = value; + this.notify('expanded'); + this._updateState(); + } + + get collisions() { + if (this._collisions == undefined) { + this._collisions = new Map(); + } + return this._collisions; + } + + _createRow(combo) { + const row = new ComboRow({ + keybinding: this.keybinding, + combo: combo, + }); + if (combo.placeholder) { + GLib.idle_add(0, () => { row.editing = true; }); + } + this.connect('notify::collisions', () => { + row.collisions = this.collisions.get(combo.keystr) || []; + }); + row.connect('collision-activated', (_, binding) => { + this.emit('collision-activated', binding); + }); + return row; + } + + _onCollisionsChanged() { + const map = new Map(); + const collisions = this.keybindings.collisions; + for (const combo of this.keybinding.combos) { + const actions = collisions.get(combo.keystr); + if (!actions) continue; + map.set( + combo.keystr, + [...actions] + .filter(a => a !== this.keybinding.action) + .map(a => this.keybindings.getKeybinding(a)) + ); + } + this._collisions = map; + this.notify('collisions'); + this._updateState(); + } + + _onRowActivated(list, row) { + if (row.is_focus()) { + row.editing = !row.editing; + } + } + + _updateState() { + GLib.idle_add(0, () => { + this._accelLabel.label = this.keybinding.label; + if (this.expanded) { + this._accelLabel.hide(); + this._conflictIcon.visible = false; + this._revealer.reveal_child = true; + this.add_css_class('expanded'); + } else { + this._accelLabel.show(); + this._conflictIcon.visible = this.collisions.size > 0; + this._revealer.reveal_child = false; + this.remove_css_class('expanded'); + } + }); + } +}); + +var KeybindingsPane = GObject.registerClass({ + GTypeName: 'KeybindingsPane', + Template: Extension.dir.get_child('KeybindingsPane.ui').get_uri(), + InternalChildren: [ + 'search', + 'listbox', + ], +}, class KeybindingsPane extends Gtk.Box { + _init(params = {}) { + super._init(params); + + this._bindings = new KeybindingsModel(); + + this._filter = new Gtk.StringFilter({ + expression: Gtk.PropertyExpression.new(Keybinding.$gtype, null, 'description'), + ignore_case: true, + match_mode: Gtk.StringFilterMatchMode.SUBSTRING + }); + + const filteredBindings = new Gtk.FilterListModel({ + model: this._bindings, + filter: this._filter + }); + + this._listbox.bind_model(filteredBindings, (keybinding) => this._createRow(keybinding)); + this._listbox.set_header_func((row, before, data) => this._onSetHeader(row, before, data)); + + this._expandedRow = null; + } + + _createHeader(row, before) { + const box = new Gtk.Box({orientation: Gtk.Orientation.VERTICAL}); + if (before) + box.append(new Gtk.Separator({orientation: Gtk.Orientation.HORIZONTAL})); + box.append(new Gtk.Label({ + use_markup: true, + label: _(`${sections[row.keybinding.section]}`), + xalign: 0.0, + margin_top: 24, + margin_bottom: 6, + margin_start: 12, + margin_end: 12 + })); + box.append(new Gtk.Separator({orientation: Gtk.Orientation.HORIZONTAL})); + return box; + } + + _createRow(keybinding) { + const row = new KeybindingsRow({keybindings: this._bindings, keybinding: keybinding}); + row.connect('notify::expanded', (row) => this._onRowExpanded(row)); + row.connect('collision-activated', (_, binding) => this._onCollisionActivated(binding)); + return row; + } + + _onCollisionActivated(keybinding) { + const [found, pos] = this._bindings.find(keybinding); + if (found) { + const row = this._listbox.get_row_at_index(pos); + row.activate(); + } + } + + _onSearchChanged() { + this._filter.search = this._search.text || null; + } + + _onRowActivated(list, row) { + if (!row.is_focus()) return; + row.expanded = !row.expanded; + } + + _onRowExpanded(row) { + if (row.expanded) { + if (this._expandedRow) this._expandedRow.expanded = false; + this._expandedRow = row; + } else if (this._expandedRow === row) { + this._expandedRow = null; + } + } + + _onSetHeader(row, before, data) { + const header = row.get_header(); + if (!before || before.keybinding.section != row.keybinding.section) { + if (!header || header instanceof Gtk.Separator) { + row.set_header(this._createHeader(row, before)); + } + } else if (!header || !(header instanceof Gtk.Separator)) { + row.set_header(new Gtk.Separator({orientation: Gtk.Orientation.HORIZONTAL})); + } + } +}); + +let _aboveTabKeyvals = null; + +function aboveTabKeyvals() { + if (!_aboveTabKeyvals) { + const keycode = 0x29 + 8; // KEY_GRAVE + let display = Gdk.Display.get_default(); + let [, , keyvals] = display.map_keycode(keycode); + _aboveTabKeyvals = keyvals; + } + return _aboveTabKeyvals; +} + +function symmetricDifference(setA, setB) { + let _difference = new Set(setA); + for (let elem of setB) { + if (_difference.has(elem)) { + _difference.delete(elem); + } else { + _difference.add(elem); + } + } + return _difference; +} diff --git a/resources/prefs.css b/resources/prefs.css new file mode 100644 index 00000000..cd101eaf --- /dev/null +++ b/resources/prefs.css @@ -0,0 +1,43 @@ +list.keybindings > row { + padding: 0 0; + background-color: transparent; +} + +list.keybindings > row:hover { + background-color: transparent; +} + +list.keybindings > row.expanded { + background-color: alpha(darker(@theme_base_color), 0.33); +} + +list.keybindings > row.expanded:backdrop:not(:hover):not(:active):not(:selected) { + background-color: alpha(darker(@theme_unfocused_base_color), 0.33); +} + +list.keybindings > row .header, +list.combos > row { + padding: 8px 12px; + min-height: 32px; +} + +list.keybindings > row .header:hover { + background-color: alpha(@theme_fg_color, 0.10); +} + +list.keybindings > row .header:hover:backdrop { + background-color: alpha(@theme_unfocused_fg_color, 0.10); +} + +list.keybindings > row.expanded label.description { + font-weight: bold; +} + +list.combos { + background-color: transparent; +} + +list.combos > .editing { + background-color: @theme_selected_bg_color; + color: @theme_selected_fg_color; +} diff --git a/settings.js b/settings.js index b73bca4f..d5c111e3 100644 --- a/settings.js +++ b/settings.js @@ -235,6 +235,17 @@ function keycomboToKeystr(combo) { return keystr; } +function keycomboToKeylab(combo) { + let [mutterKey, mods] = combo.split('|').map(s => Number.parseInt(s)); + let key = mutterKey; + if (mutterKey === META_KEY_ABOVE_TAB) + key = 97; // a + let keylab = Gtk.accelerator_get_label(key, mods); + if (mutterKey === META_KEY_ABOVE_TAB) + keylab = keylab.replace(/a$/, 'Above_Tab'); + return keylab; +} + function generateKeycomboMap(settings) { let map = {}; for (let name of settings.list_keys()) { From c964f3d2f507b834d48f75b362ed2e57fe7228ac Mon Sep 17 00:00:00 2001 From: Mikhail Skorzhinskii Date: Sun, 26 Dec 2021 20:41:04 +0100 Subject: [PATCH 098/104] fix: don't barf with a null metaWindow --- tiling.js | 3 +++ 1 file changed, 3 insertions(+) diff --git a/tiling.js b/tiling.js index b6ff02cb..d59a6032 100644 --- a/tiling.js +++ b/tiling.js @@ -3271,6 +3271,9 @@ function slurp(metaWindow) { } function barf(metaWindow) { + if (!metaWindow) + return; + let space = spaces.spaceOfWindow(metaWindow); let index = space.indexOf(metaWindow); if (index === -1) From 17269946f758d17e8b163c6610bd0112fcddd088 Mon Sep 17 00:00:00 2001 From: Mikhail Skorzhinskii Date: Mon, 27 Dec 2021 08:56:23 +0100 Subject: [PATCH 099/104] fix: panel.actor is deprecated, use panel directly --- topbar.js | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/topbar.js b/topbar.js index 0e2d4d7e..a59d0ba3 100644 --- a/topbar.js +++ b/topbar.js @@ -505,13 +505,13 @@ function enable () { menu = new WorkspaceMenu(); // Work around 'actor' warnings - let panelActor = Main.panel.actor; + let panel = Main.panel; function fixLabel(label) { let point = new Clutter.Vertex({x: 0, y: 0}); - let r = label.apply_relative_transform_to_point(panelActor, point); + let r = label.apply_relative_transform_to_point(panel, point); for (let [workspace, space] of Tiling.spaces) { - space.label.set_position(panelActor.x + Math.round(r.x), panelActor.y + Math.round(r.y)); + space.label.set_position(panel.x + Math.round(r.x), panel.y + Math.round(r.y)); let fontDescription = label.clutter_text.font_description; space.label.clutter_text.set_font_description(fontDescription); } @@ -520,7 +520,7 @@ function enable () { menu.actor.show(); // Force transparency - panelActor.set_style('background-color: rgba(0, 0, 0, 0.35);'); + panel.set_style('background-color: rgba(0, 0, 0, 0.35);'); [Main.panel._rightCorner, Main.panel._leftCorner] .forEach(c => c.actor.opacity = 0); From 11b03491470d2a0a01e481b3b6ce85d4860a985f Mon Sep 17 00:00:00 2001 From: Mikhail Skorzhinskii Date: Mon, 27 Dec 2021 08:57:26 +0100 Subject: [PATCH 100/104] fix: messagetray.actor is deprecated, use directly --- kludges.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/kludges.js b/kludges.js index 63268a5f..0fb0dc62 100644 --- a/kludges.js +++ b/kludges.js @@ -506,7 +506,7 @@ function enable() { MessageTray.prototype._updateState = function () { let hasMonitor = Main.layoutManager.primaryMonitor != null; - this.actor.visible = !this._bannerBlocked && hasMonitor && this._banner != null; + this.visible = !this._bannerBlocked && hasMonitor && this._banner != null; if (this._bannerBlocked || !hasMonitor) return; From 099df55f5730e3d42e70218b8fe0c343f50b6aee Mon Sep 17 00:00:00 2001 From: Christopher Cooper Date: Thu, 17 Feb 2022 21:02:25 -0800 Subject: [PATCH 101/104] fix use of deprecated source.notify() --- extension.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/extension.js b/extension.js index ceda8596..872425bc 100644 --- a/extension.js +++ b/extension.js @@ -194,7 +194,7 @@ function notify(msg, details, params) { Main.messageTray.add(source); let notification = new MessageTray.Notification(source, msg, details, params); notification.setResident(true); // Usually more annoying that the notification disappear than not - source.notify(notification); + source.showNotification(notification); return notification; } From b65e2122f35bbe03b0289a4da297d82d189343c4 Mon Sep 17 00:00:00 2001 From: Gelbana Date: Tue, 8 Mar 2022 19:20:45 +1000 Subject: [PATCH 102/104] Revert change to return values (accelerator_parse) Within the initial preference settings fixes, both Gtk.accelerator_parse() functions were changed to use the new GTK4.0 return values. GTK4 is only used for the preference display so this broke the keybind override function. Reverting the non-pref keybind parser to GTK3 signature fixes this. --- settings.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/settings.js b/settings.js index d5c111e3..0f6bac3d 100644 --- a/settings.js +++ b/settings.js @@ -217,7 +217,7 @@ function keystrToKeycombo(keystr) { keystr = keystr.replace('Above_Tab', 'A'); aboveTab = true; } - let [success, key, mask] = Gtk.accelerator_parse(keystr); + let [key, mask] = Gtk.accelerator_parse(keystr); if (aboveTab) key = META_KEY_ABOVE_TAB; From 79dd46d9ca9c7f73b93dbf59006bc2ba38489526 Mon Sep 17 00:00:00 2001 From: Tad Fisher Date: Thu, 23 Dec 2021 12:20:11 -0800 Subject: [PATCH 103/104] tiling: Remove topBarAdjustment from workArea --- grab.js | 4 ++-- tiling.js | 9 ++------- 2 files changed, 4 insertions(+), 9 deletions(-) diff --git a/grab.js b/grab.js index bd75508e..a87451d1 100644 --- a/grab.js +++ b/grab.js @@ -190,7 +190,7 @@ var MoveGrab = class MoveGrab { const rowZoneMargin = 250 + halfGap; let target = null; - const tilingHeight = space.height - Tiling.panelBox.height; + const tilingHeight = space.height - Main.layoutManager.panelBox.height; let fakeClone = { targetX: null, @@ -237,7 +237,7 @@ var MoveGrab = class MoveGrab { marginB: columnZoneMarginViz, space: space, actorParams: { - y: Tiling.panelBox.height, + y: Main.layoutManager.panelBox.height, height: tilingHeight } }; diff --git a/tiling.js b/tiling.js index d1ee78ba..e0173058 100644 --- a/tiling.js +++ b/tiling.js @@ -46,8 +46,6 @@ var stack_margin = 75; // Some features use this to determine if to sizes is considered equal. ie. `abs(w1 - w2) < sizeSlack` var sizeSlack = 30; -var panelBox = Main.layoutManager.panelBox; - var PreviewMode = {NONE: 0, STACK: 1, SEQUENTIAL: 2}; var inPreview = PreviewMode.NONE; @@ -260,11 +258,8 @@ class Space extends Array { let workArea = Main.layoutManager.getWorkAreaForMonitor(this.monitor.index); workArea.x -= this.monitor.x; workArea.y -= this.monitor.y; - let topBarAdjustment = this.showTopBar && (prefs.topbar_follow_focus || this.monitor === Main.layoutManager.primaryMonitor) ? - panelBox.height : 0; - workArea.height = (workArea.y + workArea.height - - topBarAdjustment - prefs.vertical_margin - prefs.vertical_margin_bottom); - workArea.y = topBarAdjustment + prefs.vertical_margin; + workArea.height -= prefs.vertical_margin + prefs.vertical_margin_bottom; + workArea.y += prefs.vertical_margin; return workArea; } From aeecb5af0831a856462e8d9b1aee2891d49e70e3 Mon Sep 17 00:00:00 2001 From: Stephen Michel Date: Mon, 15 Aug 2022 09:08:42 -0400 Subject: [PATCH 104/104] Remove fork notice In preparation for merging back upstream --- README.md | 8 -------- 1 file changed, 8 deletions(-) diff --git a/README.md b/README.md index ab9e1637..1a339d81 100644 --- a/README.md +++ b/README.md @@ -1,11 +1,3 @@ -# This is a *soft* fork of [PaperWM](https://github.com/paperwm/PaperWM) # - -PaperWM's maintainer(s) have been unresponsive since April, 2021. However, they have not said that they are stepping down from maintainership; it may just be a busy period in their lives, etc. - -Meanwhile, Gnome 40 introduced changes which broke PaperWM. Several people independently started fixing the issues for themselves. This repository is for them to coordinate, so they don't end up introducing incompatible patches; **the goal is for this work to eventually be merged back upstream**, not to start an independent development effort. This is happening on the [`next-release` branch](https://github.com/PaperWM-community/PaperWM/tree/next-release), and there is a [draft pull request](https://github.com/paperwm/PaperWM/pull/396) to merge it back upstream. (`develop` is still set as the default branch here, so that people will see this message, which shouldn't be part of the PR 😄) - -For details, see https://github.com/paperwm/PaperWM/issues/376#issuecomment-988178131 (and comments below) and the [discussions](https://github.com/PaperWM-community/PaperWM/discussions) on this repository. - # PaperWM # [![project chat](https://img.shields.io/badge/zulip-join_chat-brightgreen.svg)](https://paperwm.zulipchat.com)