make pinning work with kwin-tiled windows of any frameGeometry

This commit is contained in:
Peter Fajdiga
2023-09-22 17:23:02 +02:00
parent 2882bb8d5d
commit e4f6a32d42
7 changed files with 76 additions and 227 deletions

View File

@@ -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,
)
}

View File

@@ -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();
}

View File

@@ -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,
}
}

View File

@@ -1,177 +1,86 @@
class PinManager {
private readonly pinnedClients: Map<KwinClient, Clients.QuickTileMode>;
private readonly pinnedClients: Set<KwinClient>;
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);
}
}
}

View File

@@ -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);
});
}
})

View File

@@ -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();
}

View File

@@ -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);
});
}
});