make pinning work with kwin-tiled windows of any frameGeometry
This commit is contained in:
@@ -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,
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
});
|
||||
}
|
||||
})
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user