From e4f6a32d42dc2d2c74b307d0bad3d2f0c64f7473 Mon Sep 17 00:00:00 2001 From: Peter Fajdiga Date: Fri, 22 Sep 2023 17:23:02 +0200 Subject: [PATCH] make pinning work with kwin-tiled windows of any `frameGeometry` --- src/layout/Desktop.ts | 19 ++- src/world/ClientManager.ts | 4 +- src/world/Clients.ts | 53 -------- src/world/PinManager.ts | 211 +++++++++--------------------- src/world/clientState/Floating.ts | 6 +- src/world/clientState/Pinned.ts | 2 - src/world/clientState/Tiled.ts | 8 +- 7 files changed, 76 insertions(+), 227 deletions(-) diff --git a/src/layout/Desktop.ts b/src/layout/Desktop.ts index ab5df8f..acbffbb 100644 --- a/src/layout/Desktop.ts +++ b/src/layout/Desktop.ts @@ -39,17 +39,16 @@ class Desktop { } private static getTilingArea(clientArea: QRect, desktopNumber: number, pinManager: PinManager, config: Desktop.Config) { - const pinMargins = pinManager.getMargins(desktopNumber, clientArea); - const marginTop = config.marginTop + pinMargins.top; - const marginBottom = config.marginBottom + pinMargins.bottom; - const marginLeft = config.marginLeft + pinMargins.left; - const marginRight = config.marginRight + pinMargins.right; - + const availableSpace = pinManager.getAvailableSpace(desktopNumber, clientArea); + const top = availableSpace.top + config.marginTop; + const bottom = availableSpace.bottom - config.marginBottom; + const left = availableSpace.left + config.marginLeft; + const right = availableSpace.right - config.marginRight; return Qt.rect( - clientArea.x + marginLeft, - clientArea.y + marginTop, - clientArea.width - marginLeft - marginRight, - clientArea.height - marginTop - marginBottom, + left, + top, + right - left + 1, + bottom - top + 1, ) } diff --git a/src/world/ClientManager.ts b/src/world/ClientManager.ts index 9259b64..c7230a0 100644 --- a/src/world/ClientManager.ts +++ b/src/world/ClientManager.ts @@ -113,13 +113,13 @@ class ClientManager { } } - public pinClient(kwinClient: KwinClient, mode: Clients.QuickTileMode) { + public pinClient(kwinClient: KwinClient) { const client = this.clientMap.get(kwinClient); if (client === undefined) { return; } client.stateManager.setState(() => new ClientState.Pinned(this.world, this.pinManager, this.desktopManager, kwinClient, this.config), false); - this.pinManager.setClient(kwinClient, mode); + this.pinManager.addClient(kwinClient); for (const desktop of this.desktopManager.getDesktopsForClient(kwinClient)) { desktop.onPinsChanged(); } diff --git a/src/world/Clients.ts b/src/world/Clients.ts index f8c76d1..e7888e1 100644 --- a/src/world/Clients.ts +++ b/src/world/Clients.ts @@ -32,57 +32,4 @@ namespace Clients { export function isOnVirtualDesktop(kwinClient: KwinClient, desktopNumber: number) { return kwinClient.desktop === desktopNumber || kwinClient.desktop === -1; } - - export function guessQuickTileMode(kwinClient: KwinClient) { - const clientArea = workspace.clientArea(ClientAreaOption.PlacementArea, 0, kwinClient.desktop); - const frame = kwinClient.frameGeometry; - const top = frame.top === clientArea.top; - const bottom = frame.bottom === clientArea.bottom; - const left = frame.left === clientArea.left; - const right = frame.right === clientArea.right; - - if (left && right) { - if (top && !bottom) { - return QuickTileMode.Top; - } else if (bottom && !top) { - return QuickTileMode.Bottom; - } else { - return QuickTileMode.Untiled; - } - } else if (left) { - if (top && bottom) { - return QuickTileMode.Left; - } else if (top) { - return QuickTileMode.TopLeft; - } else if (bottom) { - return QuickTileMode.BottomLeft; - } else { - return QuickTileMode.Untiled; - } - } else if (right) { - if (top && bottom) { - return QuickTileMode.Right; - } else if (top) { - return QuickTileMode.TopRight; - } else if (bottom) { - return QuickTileMode.BottomRight; - } else { - return QuickTileMode.Untiled; - } - } else { - return QuickTileMode.Untiled; - } - } - - export enum QuickTileMode { - Untiled, - Top, - Bottom, - Left, - Right, - TopLeft, - TopRight, - BottomLeft, - BottomRight, - } } diff --git a/src/world/PinManager.ts b/src/world/PinManager.ts index bbfcffc..d502346 100644 --- a/src/world/PinManager.ts +++ b/src/world/PinManager.ts @@ -1,177 +1,86 @@ class PinManager { - private readonly pinnedClients: Map; + private readonly pinnedClients: Set; constructor() { - this.pinnedClients = new Map(); + this.pinnedClients = new Set(); } - public setClient(kwinClient: KwinClient, mode: Clients.QuickTileMode) { - this.pinnedClients.set(kwinClient, mode); + public addClient(kwinClient: KwinClient) { + this.pinnedClients.add(kwinClient); } public removeClient(kwinClient: KwinClient) { this.pinnedClients.delete(kwinClient); } - public getMargins(desktopNumber: number, screen: QRect) { - let margins = { top: 0, bottom: 0, left: 0, right: 0 }; - - const occupied = { - top: false, - bottom: false, - left: false, - right: false, - topLeft: false, - topRight: false, - bottomLeft: false, - bottomRight: false, - } - - for (const [client, mode] of this.pinnedClients.entries()) { + public getAvailableSpace(desktopNumber: number, screen: QRect) { + const baseLot = new PinManager.Lot(screen.top, screen.bottom, screen.left, screen.right); + let lots = [baseLot]; + for (const client of this.pinnedClients) { if (!Clients.isOnVirtualDesktop(client, desktopNumber)) { continue; } - const clientFrame = client.frameGeometry; - switch (mode) { - case Clients.QuickTileMode.Top: { - occupied.top = true; - occupied.left = true; - occupied.right = true; - occupied.topLeft = true; - occupied.topRight = true; - break; - } - case Clients.QuickTileMode.Bottom: { - occupied.bottom = true; - occupied.left = true; - occupied.right = true; - occupied.bottomLeft = true; - occupied.bottomRight = true; - break; - } - case Clients.QuickTileMode.Left: { - occupied.top = true; - occupied.bottom = true; - occupied.left = true; - occupied.topLeft = true; - occupied.bottomLeft = true; - break; - } - case Clients.QuickTileMode.Right: { - occupied.top = true; - occupied.bottom = true; - occupied.right = true; - occupied.topRight = true; - occupied.bottomRight = true; - break; - } - case Clients.QuickTileMode.TopLeft: { - occupied.topLeft = true; - occupied.top = true; - occupied.left = true; - break; - } - case Clients.QuickTileMode.TopRight: { - occupied.topRight = true; - occupied.top = true; - occupied.right = true; - break; - } - case Clients.QuickTileMode.BottomLeft: { - occupied.bottomLeft = true; - occupied.bottom = true; - occupied.left = true; - break; - } - case Clients.QuickTileMode.BottomRight: { - occupied.bottomRight = true; - occupied.bottom = true; - occupied.right = true; - break; - } + const newLots: PinManager.Lot[] = []; + for (const lot of lots) { + lot.split(newLots, client.frameGeometry); } + lots = newLots; + } - switch (mode) { - case Clients.QuickTileMode.Top: - case Clients.QuickTileMode.TopLeft: - case Clients.QuickTileMode.TopRight: { - margins.top = Math.max(margins.top, clientFrame.height); - break; - } - case Clients.QuickTileMode.Bottom: - case Clients.QuickTileMode.BottomLeft: - case Clients.QuickTileMode.BottomRight: { - margins.bottom = Math.max(margins.bottom, clientFrame.height); - break; - } - } - switch (mode) { - case Clients.QuickTileMode.Left: - case Clients.QuickTileMode.TopLeft: - case Clients.QuickTileMode.BottomLeft: { - margins.left = Math.max(margins.left, clientFrame.width); - break; - } - case Clients.QuickTileMode.Right: - case Clients.QuickTileMode.TopRight: - case Clients.QuickTileMode.BottomRight: { - margins.right = Math.max(margins.right, clientFrame.width); - break; - } + let largestLot = baseLot; + let largestArea = 0; + for (const lot of lots) { + const area = lot.area(); + if (area > largestArea) { + largestArea = area; + largestLot = lot; } } - - const largestVacantZone = { - margins: { top: 0, bottom: 0, left: 0, right: 0 }, - area: 0, - } - - if (!occupied.top) PinManager.considerVacantZone(largestVacantZone, screen, { top: 0, bottom: margins.bottom, left: 0, right: 0 }); - if (!occupied.bottom) PinManager.considerVacantZone(largestVacantZone, screen, { top: margins.top, bottom: 0, left: 0, right: 0 }); - if (!occupied.left) PinManager.considerVacantZone(largestVacantZone, screen, { top: 0, bottom: 0, left: 0, right: margins.right }); - if (!occupied.right) PinManager.considerVacantZone(largestVacantZone, screen, { top: 0, bottom: 0, left: margins.left, right: 0 }); - if (!occupied.topLeft) PinManager.considerVacantZone(largestVacantZone, screen, { top: 0, bottom: margins.bottom, left: 0, right: margins.right }); - if (!occupied.topRight) PinManager.considerVacantZone(largestVacantZone, screen, { top: 0, bottom: margins.bottom, left: margins.left, right: 0 }); - if (!occupied.bottomLeft) PinManager.considerVacantZone(largestVacantZone, screen, { top: margins.top, bottom: 0, left: 0, right: margins.right }); - if (!occupied.bottomRight) PinManager.considerVacantZone(largestVacantZone, screen, { top: margins.top, bottom: 0, left: margins.left, right: 0 }); - - return largestVacantZone.margins; - } - - private static considerVacantZone(largestVacantZone: PinManager.Zone, screen: QRect, margins: PinManager.Margins) { - let zoneWidth = screen.width; - if (margins.left > 0) { - zoneWidth -= margins.left; - } else if (margins.right > 0) { - zoneWidth -= margins.right; - } - - let zoneHeight = screen.height; - if (margins.top > 0) { - zoneHeight -= margins.top; - } else if (margins.bottom > 0) { - zoneHeight -= margins.bottom; - } - - const area = zoneWidth * zoneHeight; - if (area > largestVacantZone.area) { - largestVacantZone.margins = margins; - largestVacantZone.area = area; - } + return largestLot; } } namespace PinManager { - export type Margins = { - top: number, - bottom: number, - left: number, - right: number, - } + export class Lot { + private static readonly minWidth = 200; + private static readonly minHeight = 200; - export type Zone = { - margins: Margins; - area: number; + constructor( + public readonly top: number, + public readonly bottom: number, + public readonly left: number, + public readonly right: number, + ) {} + + public split(destLots: Lot[], obstacle: QRect) { + if (!this.contains(obstacle)) { + // don't split + destLots.push(this); + return; + } + + if (obstacle.top - this.top >= Lot.minHeight) { + destLots.push(new Lot(this.top, obstacle.top, this.left, this.right)); + } + if (this.bottom - obstacle.bottom >= Lot.minHeight) { + destLots.push(new Lot(obstacle.bottom, this.bottom, this.left, this.right)); + } + if (obstacle.left - this.left >= Lot.minWidth) { + destLots.push(new Lot(this.top, this.bottom, this.left, obstacle.left)); + } + if (this.right - obstacle.right >= Lot.minWidth) { + destLots.push(new Lot(this.top, this.bottom, obstacle.right, this.right)); + } + } + + private contains(obstacle: QRect) { + return obstacle.right >= this.left && obstacle.left <= this.right && + obstacle.bottom >= this.top && obstacle.top <= this.bottom; + } + + public area() { + return (this.bottom - this.top) * (this.right - this.left); + } } } diff --git a/src/world/clientState/Floating.ts b/src/world/clientState/Floating.ts index 7f68bcf..c2bb32a 100644 --- a/src/world/clientState/Floating.ts +++ b/src/world/clientState/Floating.ts @@ -42,8 +42,7 @@ namespace ClientState { // on X11, this fires after `frameGeometryChanged` if (kwinClient.tile !== null) { world.do((clientManager, desktopManager) => { - const quickTileMode = Clients.guessQuickTileMode(kwinClient); - clientManager.pinClient(kwinClient, quickTileMode); + clientManager.pinClient(kwinClient); }); } }); @@ -52,8 +51,7 @@ namespace ClientState { // on Wayland, this fires after `tileChanged` if (kwinClient.tile !== null) { world.do((clientManager, desktopManager) => { - const quickTileMode = Clients.guessQuickTileMode(kwinClient); - clientManager.pinClient(kwinClient, quickTileMode); + clientManager.pinClient(kwinClient); }); } }) diff --git a/src/world/clientState/Pinned.ts b/src/world/clientState/Pinned.ts index 9ca9286..a9baf48 100644 --- a/src/world/clientState/Pinned.ts +++ b/src/world/clientState/Pinned.ts @@ -50,8 +50,6 @@ namespace ClientState { } world.do((clientManager, desktopManager) => { - const quickTileMode = Clients.guessQuickTileMode(kwinClient); - pinManager.setClient(kwinClient, quickTileMode); for (const desktop of desktopManager.getDesktopsForClient(kwinClient)) { desktop.onPinsChanged(); } diff --git a/src/world/clientState/Tiled.ts b/src/world/clientState/Tiled.ts index 43f0342..43fc5b8 100644 --- a/src/world/clientState/Tiled.ts +++ b/src/world/clientState/Tiled.ts @@ -85,8 +85,7 @@ namespace ClientState { // on Wayland, this fires after `tileChanged` if (kwinClient.tile !== null) { world.do((clientManager, desktopManager) => { - const quickTileMode = Clients.guessQuickTileMode(kwinClient); - clientManager.pinClient(kwinClient, quickTileMode); + clientManager.pinClient(kwinClient); }); return; } @@ -119,10 +118,9 @@ namespace ClientState { manager.connect(kwinClient.tileChanged, () => { // on X11, this fires after `frameGeometryChanged` - if (kwinClient.tile === null) { + if (kwinClient.tile !== null) { world.do((clientManager, desktopManager) => { - const quickTileMode = Clients.guessQuickTileMode(kwinClient); - clientManager.pinClient(kwinClient, quickTileMode); + clientManager.pinClient(kwinClient); }); } });