4 Commits

Author SHA1 Message Date
Peter Fajdiga
d68494d257 WIP 2025-11-30 11:10:51 +01:00
Peter Fajdiga
679d1d488c Simplify SignalGrouping 2025-11-30 11:10:38 +01:00
Peter Fajdiga
ce4884c48a Implement signal grouping 2025-11-30 09:02:58 +01:00
Peter Fajdiga
1c89391739 MockWorkspace.removeWindow: Randomize the order of signals 2025-11-26 23:54:12 +01:00
20 changed files with 127 additions and 336 deletions

View File

@@ -32,7 +32,7 @@
<item>
<widget class="QCheckBox" name="kcfg_cursorFollowsFocus">
<property name="text">
<string>Cursor follows focus (experimental)</string>
<string>Cursor follows focus</string>
</property>
<property name="toolTip">
<string>When a window gains focus, move the cursor to it</string>
@@ -458,27 +458,6 @@
<string>Window Rules</string>
</attribute>
<layout class="QVBoxLayout">
<item>
<layout class="QHBoxLayout">
<item>
<widget class="QLabel" name="label_desktops">
<property name="text">
<string>Tiled desktops:</string>
</property>
<property name="wordWrap">
<bool>true</bool>
</property>
</widget>
</item>
<item>
<widget class="QLineEdit" name="kcfg_tiledDesktops">
<property name="toolTip">
<string>Regex string to match desktops by desktop name"</string>
</property>
</widget>
</item>
</layout>
</item>
<item>
<widget class="QPlainTextEdit" name="kcfg_windowRules">
<property name="tabChangesFocus">

View File

@@ -16,16 +16,6 @@ Item {
qmlBase.karouselInstance.destroy();
}
Notification {
id: notificationInvalidTiledDesktops
componentName: "plasma_workspace"
eventId: "notification"
title: "Karousel"
text: "Your Tiled Desktops regex is malformed, please review your Karousel configuration"
flags: Notification.Persistent
urgency: Notification.HighUrgency
}
Notification {
id: notificationInvalidWindowRules
componentName: "plasma_workspace"

View File

@@ -9,7 +9,7 @@
"Name": "Peter Fajdiga"
}],
"Id": "karousel",
"Version": "0.15",
"Version": "0.14",
"License": "GPLv3",
"Website": "https://github.com/peterfajdiga/karousel",
"BugReportUrl": "https://github.com/peterfajdiga/karousel/issues"

View File

@@ -2,7 +2,6 @@ declare const Qt: Qt;
declare const KWin: KWin;
declare const Workspace: Workspace;
declare const qmlBase: QmlObject;
declare const notificationInvalidTiledDesktops: Notification;
declare const notificationInvalidWindowRules: Notification;
declare const notificationInvalidPresetWidths: Notification;
declare const moveCursorToFocus: DBusCall;

View File

@@ -25,5 +25,4 @@ interface Config {
tiledKeepBelow: boolean;
floatingKeepAbove: boolean;
windowRules: string;
tiledDesktops: string;
}

View File

@@ -189,9 +189,4 @@ const configDef = [
type: "String",
default: defaultWindowRules,
},
{
name: "tiledDesktops",
type: "String",
default: ".*",
},
];

View File

@@ -102,7 +102,6 @@ interface KwinDesktop {
__brand: "KwinDesktop";
readonly id: string;
readonly name: string;
}
interface ShortcutHandler extends QmlObject {

View File

@@ -62,11 +62,7 @@ class Actions {
};
public readonly focusStart = (cm: ClientManager, dm: DesktopManager) => {
const desktop = dm.getCurrentDesktop();
if (desktop === undefined) {
return;
}
const grid = desktop.grid;
const grid = dm.getCurrentDesktop().grid;
const firstColumn = grid.getFirstColumn();
if (firstColumn === null) {
return;
@@ -75,11 +71,7 @@ class Actions {
};
public readonly focusEnd = (cm: ClientManager, dm: DesktopManager) => {
const desktop = dm.getCurrentDesktop();
if (desktop === undefined) {
return;
}
const grid = desktop.grid;
const grid = dm.getCurrentDesktop().grid;
const lastColumn = grid.getLastColumn();
if (lastColumn === null) {
return;
@@ -204,9 +196,6 @@ class Actions {
public readonly columnsWidthEqualize = (cm: ClientManager, dm: DesktopManager) => {
const desktop = dm.getCurrentDesktop();
if (desktop === undefined) {
return;
}
const visibleRange = desktop.getCurrentVisibleRange();
const visibleColumns = Array.from(desktop.grid.getVisibleColumns(visibleRange, true));
@@ -308,18 +297,11 @@ class Actions {
};
private readonly gridScroll = (desktopManager: DesktopManager, amount: number) => {
const desktop = desktopManager.getCurrentDesktop();
if (desktop !== undefined) {
desktop.adjustScroll(amount, false);
}
desktopManager.getCurrentDesktop().adjustScroll(amount, false);
};
public readonly gridScrollStart = (cm: ClientManager, dm: DesktopManager) => {
const desktop = dm.getCurrentDesktop();
if (desktop === undefined) {
return;
}
const grid = desktop.grid;
const grid = dm.getCurrentDesktop().grid;
const firstColumn = grid.getFirstColumn();
if (firstColumn === null) {
return;
@@ -328,11 +310,7 @@ class Actions {
};
public readonly gridScrollEnd = (cm: ClientManager, dm: DesktopManager) => {
const desktop = dm.getCurrentDesktop();
if (desktop === undefined) {
return;
}
const grid = desktop.grid;
const grid = dm.getCurrentDesktop().grid;
const lastColumn = grid.getLastColumn();
if (lastColumn === null) {
return;
@@ -350,11 +328,7 @@ class Actions {
};
public readonly gridScrollLeftColumn = (cm: ClientManager, dm: DesktopManager) => {
const desktop = dm.getCurrentDesktop();
if (desktop === undefined) {
return;
}
const grid = desktop.grid;
const grid = dm.getCurrentDesktop().grid;
const column = grid.getLeftmostVisibleColumn(grid.desktop.getCurrentVisibleRange(), true);
if (column === null) {
return;
@@ -369,11 +343,7 @@ class Actions {
};
public readonly gridScrollRightColumn = (cm: ClientManager, dm: DesktopManager) => {
const desktop = dm.getCurrentDesktop();
if (desktop === undefined) {
return;
}
const grid = desktop.grid;
const grid = dm.getCurrentDesktop().grid;
const column = grid.getRightmostVisibleColumn(grid.desktop.getCurrentVisibleRange(), true);
if (column === null) {
return;
@@ -392,11 +362,7 @@ class Actions {
};
public readonly focus = (columnIndex: number, cm: ClientManager, dm: DesktopManager) => {
const desktop = dm.getCurrentDesktop();
if (desktop === undefined) {
return;
}
const grid = desktop.grid;
const grid = dm.getCurrentDesktop().grid;
const targetColumn = grid.getColumnAtIndex(columnIndex);
if (targetColumn === null) {
return;
@@ -430,11 +396,7 @@ class Actions {
if (kwinDesktop === undefined) {
return;
}
const newDesktop = dm.getDesktopInCurrentActivity(kwinDesktop);
if (newDesktop === undefined) {
return;
}
const newGrid = newDesktop.grid;
const newGrid = dm.getDesktopInCurrentActivity(kwinDesktop).grid;
if (newGrid === null || newGrid === oldGrid) {
return;
}
@@ -446,11 +408,7 @@ class Actions {
if (kwinDesktop === undefined) {
return;
}
const newDesktop = dm.getDesktopInCurrentActivity(kwinDesktop);
if (newDesktop === undefined) {
return;
}
const newGrid = newDesktop.grid;
const newGrid = dm.getDesktopInCurrentActivity(kwinDesktop).grid;
if (newGrid === null || newGrid === oldGrid) {
return;
}

View File

@@ -1,30 +0,0 @@
class DesktopFilter {
private readonly desktopRegex: RegExp | null; // null means all desktops
constructor(desktopsConfig: string) {
this.desktopRegex = DesktopFilter.parseDesktopConfig(desktopsConfig);
}
public shouldWorkOnDesktop(kwinDesktop: KwinDesktop): boolean {
if (this.desktopRegex === null) {
return true; // Work on all desktops
}
return this.desktopRegex.test(kwinDesktop.name);
}
private static parseDesktopConfig(config: string): RegExp | null {
const trimmed = config.trim();
if (trimmed.length === 0) {
return null; // Empty config means work on all desktops
}
try {
return new RegExp(`^${trimmed}$`);
} catch (e) {
notificationInvalidTiledDesktops.sendEvent();
log(`Invalid regex pattern in tiledDesktops config: ${trimmed}. Working on all desktops.`);
return null; // Invalid regex means work on all desktops as fallback
}
}
}

View File

@@ -0,0 +1,62 @@
namespace SignalGrouping {
export class Group {
private argsBySignal: Map<QSignal<[any]>, [[any]]> = new Map();
private delayer = new Delayer(50, () => this.fire());
constructor(
private handlers: Handler<any>[], // in order of decreasing priority
) {}
public connect(manager: SignalManager) {
const signals = collectSignals(this.handlers);
for (const signal of signals) {
manager.connect(signal, (...args: any) => {
this.argsBySignal.set(signal, args);
this.delayer.run();
});
}
}
private fire() {
for (const handler of this.handlers) {
const args = this.getGroupArgs(handler.signals);
if (args !== null) {
handler.f(args);
break;
}
}
this.argsBySignal.clear();
}
private getGroupArgs(signals: QSignal<[any]>[]) {
const groupArgs = signals.map(signal => this.argsBySignal.get(signal));
if (groupArgs.some(args => args === undefined)) {
return null;
}
return groupArgs;
}
}
function collectSignals<S extends QSignal<any>[]>(handlers: Handler<S>[]) {
const signals: S[number][] = [];
for (const handler of handlers) {
for (const signal of handler.signals) {
if (!signals.includes(signal)) {
signals.push(signal);
}
}
}
return signals;
}
export class Handler<S extends QSignal<any>[]> {
constructor(
public signals: [...S],
public f: (...args: [...GroupArgs<S>]) => void,
) {}
}
type GroupArgs<S extends QSignal<any>[]> = {
[K in keyof S]: S[K] extends QSignal<infer A> ? A : never;
};
}

View File

@@ -7,22 +7,40 @@ function initWorkspaceSignalHandlers(world: World, focusPasser: FocusPassing.Pas
});
});
manager.connect(Workspace.windowRemoved, (kwinClient: KwinClient) => {
world.do((clientManager, desktopManager) => {
clientManager.removeClient(kwinClient, FocusPassing.Type.Immediate);
});
});
manager.connect(Workspace.windowActivated, (kwinClient: KwinClient|null) => {
if (kwinClient === null) {
focusPasser.activate();
} else {
focusPasser.clearIfDifferent(kwinClient);
world.do((clientManager, desktopManager) => {
clientManager.onClientFocused(kwinClient);
});
}
});
new SignalGrouping.Group([
new SignalGrouping.Handler(
[Workspace.windowRemoved, Workspace.windowActivated] as const,
(windowRemovedArgs, windowActivatedArgs) => {
const kwinClient = windowRemovedArgs[0];
world.do((clientManager, desktopManager) => {
clientManager.removeClient(kwinClient, FocusPassing.Type.Immediate);
});
},
),
new SignalGrouping.Handler(
[Workspace.windowRemoved],
(windowRemovedArgs) => {
const kwinClient = windowRemovedArgs[0];
world.do((clientManager, desktopManager) => {
clientManager.removeClient(kwinClient, FocusPassing.Type.Immediate);
});
},
),
new SignalGrouping.Handler(
[Workspace.windowActivated],
(windowActivatedArgs) => {
const kwinClient = windowActivatedArgs[0];
if (kwinClient === null) {
focusPasser.activate();
} else {
focusPasser.clearIfDifferent(kwinClient);
world.do((clientManager, desktopManager) => {
clientManager.onClientFocused(kwinClient);
});
}
},
),
]).connect(manager);
manager.connect(Workspace.currentDesktopChanged, () => {
world.do(() => {}); // re-arrange desktop

View File

@@ -31,16 +31,16 @@ class ClientManager {
console.assert(!this.hasClient(kwinClient));
let constructState: (client: ClientWrapper) => ClientState.State;
let desktop: Desktop | undefined;
if (kwinClient.dock) {
constructState = () => new ClientState.Docked(this.world, kwinClient);
} else if (
Clients.canTileEver(kwinClient) &&
this.windowRuleEnforcer.shouldTile(kwinClient) &&
(desktop = this.desktopManager.getDesktopForClient(kwinClient)) !== undefined
this.windowRuleEnforcer.shouldTile(kwinClient)
) {
Clients.makeTileable(kwinClient);
console.assert(Clients.canTileNow(kwinClient));
const desktop = this.desktopManager.getDesktopForClient(kwinClient);
console.assert(desktop !== undefined);
constructState = (client: ClientWrapper) => new ClientState.Tiled(this.world, client, desktop!.grid);
} else {
constructState = (client: ClientWrapper) => new ClientState.Floating(this.world, client, this.config, false);

View File

@@ -9,7 +9,8 @@ class DesktopManager {
private readonly config: Desktop.Config,
private readonly layoutConfig: LayoutConfig,
private readonly focusPasser: FocusPassing.Passer,
private readonly desktopFilter: DesktopFilter,
currentActivity: string,
currentDesktop: KwinDesktop,
) {
this.pinManager = pinManager;
this.config = config;
@@ -18,12 +19,10 @@ class DesktopManager {
this.selectedScreen = Workspace.activeScreen;
this.kwinActivities = new Set(Workspace.activities);
this.kwinDesktops = new Set(Workspace.desktops);
this.addDesktop(currentActivity, currentDesktop);
}
public getDesktop(activity: string, kwinDesktop: KwinDesktop) {
if (!this.desktopFilter.shouldWorkOnDesktop(kwinDesktop)) {
return undefined;
}
const desktopKey = DesktopManager.getDesktopKey(activity, kwinDesktop);
const desktop = this.desktops.get(desktopKey);
if (desktop !== undefined) {

View File

@@ -70,7 +70,8 @@ class World {
},
layoutConfig,
focusPasser,
new DesktopFilter(config.tiledDesktops),
Workspace.currentActivity,
Workspace.currentDesktop,
);
this.clientManager = new ClientManager(config, this, this.desktopManager, this.pinManager);
this.addExistingClients();
@@ -97,20 +98,12 @@ class World {
}
private update() {
const currentDesktop = this.desktopManager.getCurrentDesktop();
if (currentDesktop !== undefined) {
currentDesktop.arrange();
this.moveCursorToFocus();
}
this.desktopManager.getCurrentDesktop().arrange();
this.moveCursorToFocus();
}
private moveCursorToFocus() {
if (this.cursorFollowsFocus && Workspace.activeWindow !== null) {
// Only move cursor for tiled windows
const tiledWindow = this.clientManager.findTiledWindow(Workspace.activeWindow);
if (tiledWindow === null) {
return;
}
const cursorAlreadyInFocus = rectContainsPoint(Workspace.activeWindow.frameGeometry, Workspace.cursorPos);
if (cursorAlreadyInFocus) {
return;
@@ -148,21 +141,11 @@ class World {
}
public gestureScroll(amount: number) {
this.do((clientManager, desktopManager) => {
const currentDesktop = desktopManager.getCurrentDesktop();
if (currentDesktop !== undefined) {
currentDesktop.gestureScroll(amount);
}
});
this.do((clientManager, desktopManager) => desktopManager.getCurrentDesktop().gestureScroll(amount));
}
public gestureScrollFinish() {
this.do((clientManager, desktopManager) => {
const currentDesktop = desktopManager.getCurrentDesktop();
if (currentDesktop !== undefined) {
currentDesktop.gestureScrollFinish();
}
});
this.do((clientManager, desktopManager) => desktopManager.getCurrentDesktop().gestureScrollFinish());
}
public destroy() {

View File

@@ -34,49 +34,3 @@ tests.register("Drag tiled window, untile", 10, () => {
Workspace.activeWindow = null;
Assert.assert(pointEquals(Workspace.cursorPos, lastCursorPos), { message: "Cursor should not have been moved" });
});
tests.register("Cursor follows focus only on matched desktops", 1, () => {
// Test that cursor follow focus only works for windows on matched desktops (tiled windows)
const config = getDefaultConfig();
config.cursorFollowsFocus = true;
config.tiledDesktops = "^Desktop 1$"; // Only work on Desktop 1
const { workspaceMock, world } = init(config);
// Create a client on Desktop 1 (matched desktop) - should be tiled
const client1 = new MockKwinClient();
client1.desktops = [workspaceMock.desktops[0]]; // Desktop 1
workspaceMock.createWindows(client1);
// Create a client on Desktop 2 (non-matched desktop) - should be floating
const client2 = new MockKwinClient();
client2.desktops = [workspaceMock.desktops[1]]; // Desktop 2
workspaceMock.createWindows(client2);
// Set initial cursor position outside both windows
const initialCursorPos = new MockQmlPoint(10, 10);
workspaceMock.cursorPos = initialCursorPos.clone();
// Test 1: Focus client1 on matched desktop (Desktop 1) - cursor should move
workspaceMock.currentDesktop = workspaceMock.desktops[0]; // Switch to Desktop 1
Workspace.activeWindow = client1;
world.do(() => {});
Assert.assert(rectContainsPoint(client1.frameGeometry, Workspace.cursorPos),
{ message: "Cursor should have moved to tiled window on matched desktop" });
// Test 2: Switch to non-matched desktop (Desktop 2) and focus client2 - cursor should NOT move
workspaceMock.cursorPos = initialCursorPos.clone();
workspaceMock.currentDesktop = workspaceMock.desktops[1]; // Switch to Desktop 2
Workspace.activeWindow = client2;
world.do(() => {});
Assert.assert(pointEquals(Workspace.cursorPos, initialCursorPos),
{ message: "Cursor should NOT move on non-matched desktop" });
// Test 3: Even if we focus client1 (tiled) while on Desktop 2, cursor should NOT move
// because the current desktop is not matched
workspaceMock.cursorPos = initialCursorPos.clone();
workspaceMock.currentDesktop = workspaceMock.desktops[1]; // Stay on Desktop 2
Workspace.activeWindow = client1;
world.do(() => {});
Assert.assert(pointEquals(Workspace.cursorPos, initialCursorPos),
{ message: "Cursor should NOT move even for tiled window when current desktop is not matched" });
});

View File

@@ -1,73 +0,0 @@
tests.register("Desktop filtering", 1, () => {
// Test 1: Default config should work on all desktops
const config1 = getDefaultConfig();
const { workspaceMock: wm1, world: world1 } = init(config1);
const client1 = new MockKwinClient();
client1.desktops = [wm1.desktops[0]];
wm1.createWindows(client1);
world1.do((clientManager) => {
Assert.tiledClient(clientManager, client1, { message: "Client should be tiled on desktop1 with default config (*)" });
});
});
tests.register("Desktop filtering - specific desktop", 1, () => {
// Test 2: Specific desktop name - should work only on matching desktop
const config2 = getDefaultConfig();
config2.tiledDesktops = "^Desktop 1$";
const { workspaceMock: wm2, world: world2 } = init(config2);
const client1 = new MockKwinClient();
client1.desktops = [wm2.desktops[0]]; // Desktop 1
wm2.createWindows(client1);
world2.do((clientManager) => {
Assert.tiledClient(clientManager, client1, { message: "Client should be tiled on Desktop 1" });
});
wm2.removeWindow(client1);
const client2 = new MockKwinClient();
client2.desktops = [wm2.desktops[1]]; // Desktop 2
wm2.createWindows(client2);
world2.do((clientManager) => {
Assert.notTiledClient(clientManager, client2, { message: "Client should NOT be tiled on Desktop 2" });
});
});
tests.register("Desktop filtering - multiple desktops", 1, () => {
// Test 3: Multiple desktop names using regex alternation
const config3 = getDefaultConfig();
config3.tiledDesktops = "^Desktop [12]$";
const { workspaceMock: wm3, world: world3 } = init(config3);
const client1 = new MockKwinClient();
client1.desktops = [wm3.desktops[0]]; // Desktop 1
wm3.createWindows(client1);
world3.do((clientManager) => {
Assert.tiledClient(clientManager, client1, { message: "Client should be tiled on Desktop 1" });
});
wm3.removeWindow(client1);
const client2 = new MockKwinClient();
client2.desktops = [wm3.desktops[1]]; // Desktop 2
wm3.createWindows(client2);
world3.do((clientManager) => {
Assert.tiledClient(clientManager, client2, { message: "Client should be tiled on Desktop 2" });
});
});
tests.register("Desktop filtering - windows on multiple desktops", 1, () => {
// Test 4: Windows on multiple desktops should not be tiled (fallback to floating)
const config4 = getDefaultConfig();
config4.tiledDesktops = ".*";
const { workspaceMock: wm4, world: world4 } = init(config4);
const client1 = new MockKwinClient();
client1.desktops = [wm4.desktops[0], wm4.desktops[1]]; // Multiple desktops
wm4.createWindows(client1);
world4.do((clientManager) => {
Assert.notTiledClient(clientManager, client1, { message: "Client on multiple desktops should not be tiled" });
});
});

View File

@@ -1,41 +0,0 @@
tests.register("DesktopFilter", 1, () => {
const desktop1 = { __brand: "KwinDesktop" as const, id: "1", name: "Desktop 1" };
const desktop2 = { __brand: "KwinDesktop" as const, id: "2", name: "Work" };
const desktop3 = { __brand: "KwinDesktop" as const, id: "3", name: "Desktop 2" };
// Test 1: Empty config means all desktops
let filter = new DesktopFilter("");
Assert.assert(filter.shouldWorkOnDesktop(desktop1), { message: "Empty config should work on desktop1" });
Assert.assert(filter.shouldWorkOnDesktop(desktop2), { message: "Empty config should work on desktop2" });
// Test 2: Whitespace only means all desktops
filter = new DesktopFilter(" \n \n ");
Assert.assert(filter.shouldWorkOnDesktop(desktop1), { message: "Whitespace only should work on desktop1" });
Assert.assert(filter.shouldWorkOnDesktop(desktop2), { message: "Whitespace only should work on desktop2" });
// Test 3: Match all regex pattern
filter = new DesktopFilter(".*");
Assert.assert(filter.shouldWorkOnDesktop(desktop1), { message: "Regex '.*' should work on desktop1" });
Assert.assert(filter.shouldWorkOnDesktop(desktop2), { message: "Regex '.*' should work on desktop2" });
// Test 4: Partial match without anchors
filter = new DesktopFilter("Work");
Assert.assert(!filter.shouldWorkOnDesktop(desktop1), { message: "Should not work on desktop1" });
Assert.assert(filter.shouldWorkOnDesktop(desktop2), { message: "Should work on desktop2 containing 'Work'" });
// Test 5: Regex alternation for multiple desktops
filter = new DesktopFilter("Desktop 1|Work");
Assert.assert(filter.shouldWorkOnDesktop(desktop1), { message: "Should work on desktop1" });
Assert.assert(filter.shouldWorkOnDesktop(desktop2), { message: "Should work on desktop2" });
Assert.assert(!filter.shouldWorkOnDesktop(desktop3), { message: "Should not work on desktop3" });
// Test 6: Regex pattern with character class
filter = new DesktopFilter("Desktop [12]");
Assert.assert(filter.shouldWorkOnDesktop(desktop1), { message: "Should work on desktop1" });
Assert.assert(!filter.shouldWorkOnDesktop(desktop2), { message: "Should not work on desktop2" });
Assert.assert(filter.shouldWorkOnDesktop(desktop3), { message: "Should work on desktop3" });
// Test 7: Case-sensitive matching
filter = new DesktopFilter("work");
Assert.assert(!filter.shouldWorkOnDesktop(desktop2), { message: "Should not work on desktop2 (case mismatch)" });
});

View File

@@ -2,7 +2,6 @@ let Qt: Qt;
let KWin: KWin;
let Workspace: Workspace;
let qmlBase: QmlObject;
let notificationInvalidTiledDesktops: Notification;
let notificationInvalidWindowRules: Notification;
let notificationInvalidPresetWidths: Notification;
let moveCursorToFocus: DBusCall;

View File

@@ -52,7 +52,6 @@ class MockKwinClient {
this.windowedFrameGeometry = _frameGeometry.clone();
this.transient = transientFor !== null;
this._desktops = [Workspace.currentDesktop];
this.activities = [Workspace.currentActivity];
}
setMaximize(vertically: boolean, horizontally: boolean) {

View File

@@ -3,8 +3,8 @@ class MockWorkspace {
public activities = ["test-activity"];
public desktops: KwinDesktop[] = [
{ __brand: "KwinDesktop", id: "desktop1", name: "Desktop 1" },
{ __brand: "KwinDesktop", id: "desktop2", name: "Desktop 2" },
{ __brand: "KwinDesktop", id: "desktop1" },
{ __brand: "KwinDesktop", id: "desktop2" },
];
public currentActivity = this.activities[0];
public activeScreen: Output = { __brand: "Output" };
@@ -54,12 +54,14 @@ class MockWorkspace {
public removeWindow(window: MockKwinClient) {
this.activeWindow = null;
runReorder(
() => {
if (this.activeWindow === null) {
activateRandomWindowOnDesktop(this.currentDesktop);
};
},
() => this.windows.splice(this.windows.indexOf(window), 1),
() => this.windowRemoved.fire(window),
);
if (this.activeWindow === null) {
activateRandomWindowOnDesktop(this.currentDesktop);
};
}
public moveWindow(window: MockKwinClient, ...deltas: QmlPoint[]) {