diff --git a/extension.js b/extension.js index 4b0979b2..a38a0c1f 100644 --- a/extension.js +++ b/extension.js @@ -52,6 +52,7 @@ export default class PaperWM extends Extension { Gestures, Keybindings, LiveAltTab, Navigator, Stackoverlay, Scratch, Workspace, Tiling, Topbar, App, ]; + Tiling = Tiling; #userStylesheet = null; @@ -67,6 +68,8 @@ export default class PaperWM extends Extension { m.enable(this); } }); + + global.paperwm = this; } disable() { @@ -79,6 +82,8 @@ export default class PaperWM extends Extension { }); this.disableUserStylesheet(); + + global.paperwm = undefined; } /** diff --git a/patches.js b/patches.js index c2468532..e941c390 100644 --- a/patches.js +++ b/patches.js @@ -14,7 +14,7 @@ import * as WindowManager from 'resource:///org/gnome/shell/ui/windowManager.js' import * as WindowPreview from 'resource:///org/gnome/shell/ui/windowPreview.js'; import * as Params from 'resource:///org/gnome/shell/misc/params.js'; -import { Utils, Tiling, Scratch, Settings, OverviewLayout } from './imports.js'; +import { Utils, Tiling, Scratch, Settings, Topbar, OverviewLayout } from './imports.js'; /** Some of Gnome Shell's default behavior is really sub-optimal when using @@ -36,9 +36,11 @@ export function enable(extension) { enableOverrides(); setupRuntimeDisables(); setupActions(); + setupFullscreenAvoiderSupport(); } export function disable() { + undoFullscreenAvoiderSupport(); disableOverrides(); restoreRuntimeDisables(); actions.forEach(a => global.stage.add_action(a)); @@ -636,3 +638,77 @@ export function _checkWorkspaces() { this._checkWorkspacesId = 0; return false; } + +function setupFullscreenAvoiderSupport() { + // Patch monitor objects prototype to check our space for the inFullscreen + // property. + const monitor1 = Main.layoutManager.monitors[0]; + Object.defineProperties( + Object.getPrototypeOf(monitor1), + { + inFullscreen: { + // NOTE: Needs to be non-arrow function so `this` is bound + // correctly on call. This is necessary because we modify the + // prototype of multiple objects here. + get: function() { + // NOTE: This is wrapped in try-catch because an error here + // makes windows unclickable. + try { + // Find active space for monitor (this) + // NOTE: Indexing spaces.monitors[this] does not work + if (Tiling.spaces?.monitors) { + for (const [monitor, space] of Tiling.spaces.monitors) { + if (monitor.index == this.index) { + return space.hasFullScreenWindow(); + } + } + } + // Check for scratch windows separately since they don't + // belong to a workspace + if (Scratch.getScratchWindows().some(w => w.get_monitor() == this.index && w.fullscreen)) { + return true; + } + } catch (e) { + console.error(e); + } + // should not be reached, just here in case there is an + // error above + console.error(new Error(`Failed to find space for monitor`)); + return false; + }, + enumerable: true, + } + }); + + signals.connect(Main.layoutManager.panelBox, "notify::position", () => { + try { + if (Tiling.spaces.monitors) { + for (const [_monitor, space] of Tiling.spaces.monitors) { + // console.debug(`Updating space ${space.name}`); + space.setSpaceTopbarElementsVisible(); + Topbar.updateWorkspaceIndicator(space.index); + Topbar.fixTopBar(); + } + } + } catch (e) { + console.error(e); + } + }); +} + +function undoFullscreenAvoiderSupport() { + const monitor1 = Main.layoutManager.monitors[0]; + // Reset value to false. This might be incorrect, but will be updated by + // gnome again after some time. + Object.defineProperties( + Object.getPrototypeOf(monitor1), + { + inFullscreen: { + value: false, + writable: true, + enumerable: true, + } + } + ); + signals.disconnect(Main.layoutManager.panelBox); +} diff --git a/tiling.js b/tiling.js index 8e05e12d..cf0a85b0 100644 --- a/tiling.js +++ b/tiling.js @@ -170,6 +170,10 @@ export function enable(extension) { }); }; + signals.connect(global.display, 'workareas-changed', () => { + spaces.layout(false, {ensure: false}); + }); + if (Main.layoutManager._startingUp) { // Defer workspace initialization until existing windows are accessible. // Otherwise we're unable to restore the tiling-order. (when restarting @@ -623,10 +627,9 @@ export class Space extends Array { this.setSpaceTopbarElementsVisible(false); } // compensate to keep window position bar on all monitors - else if (Settings.prefs.show_window_position_bar) { + else if (Settings.prefs.show_window_position_bar && this.showTopBar) { const panelBoxHeight = Topbar.panelBox.height; - const monitor = Main.layoutManager.primaryMonitor; - if (monitor !== this.monitor) { + if (!this.hasTopBar) { workArea.y += panelBoxHeight; workArea.height -= panelBoxHeight; } @@ -736,6 +739,8 @@ export class Space extends Array { if (this._layoutQueued) return; + console.log(new Error(this.name)); + this._layoutQueued = true; Utils.later_add(Meta.LaterType.RESIZE, () => { this._layoutQueued = false; @@ -826,6 +831,8 @@ export class Space extends Array { } addWindow(metaWindow, index, row) { + const previousHasFullScreenWindow = this.hasFullScreenWindow(); + if (!this.selectedWindow) this.selectedWindow = metaWindow; if (this.indexOf(metaWindow) !== -1) @@ -893,6 +900,16 @@ export class Space extends Array { this.targetX = workArea.x + Math.round((workArea.width - this.cloneContainer.width) / 2); } this.emit('window-added', metaWindow, index, row); + if (previousHasFullScreenWindow !== this.hasFullScreenWindow()) { + // This means a window started in fullscreen. + // + // We (re-)emit this signal because the in-fullscreen-changed event + // was most likely already triggered before the window was + // registered with PaperWM. This mean space.hasFullScreenWindow() + // returned a (possibly) wrong value because the space did not know + // about the window. + global.display.emit("in-fullscreen-changed"); + } return true; } @@ -964,11 +981,15 @@ export class Space extends Array { return true; } + getFloatingWindows() { + return this._floating; + } + /** * Returns true iff this space has a currently fullscreened window. */ hasFullScreenWindow() { - return this.getWindows().some(w => w.fullscreen); + return this.getWindows().some(w => w.fullscreen) || this.getFloatingWindows().some(w => w.fullscreen); } swap(direction, metaWindow) { @@ -1471,7 +1492,12 @@ export class Space extends Array { * Returns true if this space has the topbar. */ get hasTopBar() { - return this.monitor && this.monitor === Topbar.panelMonitor(); + if (inPreview) { + // always show topbar in overview + return true; + } + + return Topbar.isOnMonitor(this.monitor); } updateColor() { @@ -1539,7 +1565,8 @@ border-radius: ${borderWidth}px; enableWindowPositionBar(enable = true) { const add = enable && - Settings.prefs.show_window_position_bar; + Settings.prefs.show_window_position_bar && + this.showTopBar; if (add) { [this.windowPositionBarBackdrop, this.windowPositionBar] .forEach(i => { @@ -1622,7 +1649,7 @@ border-radius: ${borderWidth}px; } // if windowPositionBar is disabled ==> don't show elements - if (!Settings.prefs.show_window_position_bar) { + if (!Settings.prefs.show_window_position_bar || !this.showTopBar) { setVisible(false); return; } @@ -2061,6 +2088,10 @@ export const Spaces = class Spaces extends Map { this.stack = this.mru(); } + layout(animate = true, options = {}) { + this.forEach(space => space.layout(animate, options)); + } + /** The monitors-changed signal can trigger _many_ times when connection/disconnecting monitors. @@ -2381,7 +2412,17 @@ export const Spaces = class Spaces extends Map { }); // ensure after swapping that the space elements are shown correctly + // layout needed to properly fix topbar + let oldSpace = this.monitors.get(monitor); + let newMonitor = Main.layoutManager.monitors[i]; + let newSpace = this.monitors.get(newMonitor); + oldSpace.layout(); + newSpace.layout(); + Topbar.fixTopBar(); this.setSpaceTopbarElementsVisible(true, { force: true }); + if (oldSpace.hasFullScreenWindow() || newSpace.hasFullScreenWindow()) { + global.display.emit("in-fullscreen-changed"); + } } switchWorkspace(wm, fromIndex, toIndex, animate = false) { @@ -2434,7 +2475,11 @@ export const Spaces = class Spaces extends Map { this.animateToSpace( toSpace, fromSpace, - doAnimate); + doAnimate, + // do a layout after the animation is complete + // (i.e. when everything is at the final position) + () => toSpace.layout(), + ); // Update panel to handle target workspace signals.disconnect(Main.panel, this.touchSignal); @@ -2447,7 +2492,7 @@ export const Spaces = class Spaces extends Map { * See Space.setSpaceTopbarElementsVisible function for what this does. * @param {boolean} visible */ - setSpaceTopbarElementsVisible(visible = false, options = {}) { + setSpaceTopbarElementsVisible(visible = true, options = {}) { this.forEach(s => { s.setSpaceTopbarElementsVisible(visible, options); }); @@ -2600,8 +2645,7 @@ export const Spaces = class Spaces extends Map { newSpace = monitorSpaces[to]; this.selectedSpace = newSpace; - // if active (source space) is panelMonitor update indicator - if (currentSpace.monitor === Topbar.panelMonitor()) { + if (Topbar.isOnMonitor(currentSpace.monitor)) { Topbar.updateWorkspaceIndicator(newSpace.index); } @@ -2943,6 +2987,15 @@ export const Spaces = class Spaces extends Map { return [...this.values()].find(s => uuid === s.uuid); } + spaceWithTopBar() { + for (const monitor in this.monitors) { + if (this.monitors[monitor].hasTopBar) { + return this.monitors[monitor]; + } + } + return null; + } + get selectedSpace() { return this._selectedSpace ?? this.activeSpace; } diff --git a/topbar.js b/topbar.js index d8644b9a..409c420f 100644 --- a/topbar.js +++ b/topbar.js @@ -65,8 +65,6 @@ export function enable (extension) { signals.connect(Main.overview, 'showing', fixTopBar); signals.connect(Main.overview, 'hidden', () => { - if (Tiling.spaces.selectedSpace.showTopBar) - return; fixTopBar(); }); @@ -720,7 +718,7 @@ export function fixStyle() { } export function fixTopBar() { - let space = Tiling?.spaces?.monitors?.get(panelMonitor()) ?? false; + let space = Tiling?.spaces?.spaceWithTopBar() ?? false; if (!space) return; @@ -732,17 +730,23 @@ export function fixTopBar() { // check if is currently fullscreened (check focused-floating, focused-scratch, and selected/tiled window) let fullscreen = focusIsFloatOrScratch ? focused.fullscreen : selected && selected.fullscreen; - if (normal && !space.showTopBar) { - panelBox.scale_y = 0; // Update the workarea to support hide top bar - panelBox.hide(); - } - else if (normal && fullscreen) { - panelBox.hide(); - } - else { - panelBox.scale_y = 1; - panelBox.show(); + if (normal && space.hasTopBar) { + if (!space.showTopBar) { + panelBox.scale_y = 0; // Update the workarea to support hide top bar + panelBox.hide(); + } else { + panelBox.scale_y = 1; + panelBox.show(); + } } + + // if (normal && !fullscreen && !space.showTopBar) { + // panelBox.scale_y = 0; // Update the workarea to support hide top bar + // panelBox.hide(); + // } else { + // panelBox.scale_y = 1; + // panelBox.show(); + // } } export function fixWorkspaceIndicator() { @@ -769,7 +773,7 @@ export function fixFocusModeIcon() { export function updateWorkspaceIndicator(index) { let spaces = Tiling.spaces; let space = spaces?.spaceOf(workspaceManager.get_workspace_by_index(index)); - if (space && space.monitor === panelMonitor()) { + if (space && isOnMonitor(space.monitor)) { setWorkspaceName(space.name); // also update focus mode @@ -788,3 +792,11 @@ export function refreshWorkspaceIndicator() { export function setWorkspaceName (name) { menu && menu.setName(name); } + +export function isOnMonitor(monitor) { + // Check if panel position is the same as the monitor position (because + // it is always at position 0,0 relative to the monitor). This is useful + // when an extension moves the panel to another monitor. + const [panelBoxX, panelBoxY] = Main.layoutManager.panelBox.get_transformed_position(); + return monitor && monitor.x == panelBoxX && monitor.y == panelBoxY; +}