Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Monocle Layout "minimize unfocused windows" fix and improvements #45

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
Empty file modified .husky/pre-commit 100644 → 100755
Empty file.
2 changes: 1 addition & 1 deletion res/config.ui
Expand Up @@ -115,7 +115,7 @@
<item>
<widget class="QCheckBox" name="kcfg_monocleMinimizeRest">
<property name="text">
<string>Minimize unfocused windows (WIP)</string>
<string>Minimize unfocused windows</string>
</property>
</widget>
</item>
Expand Down
4 changes: 2 additions & 2 deletions src/controller/action.test.ts
Expand Up @@ -69,7 +69,7 @@ describe("action", () => {

action.execute();

expect(fakeEngine.focusOrder).toBeCalledWith(1);
expect(fakeEngine.focusOrder).toBeCalledWith(1, false);
});
});

Expand All @@ -79,7 +79,7 @@ describe("action", () => {

action.execute();

expect(fakeEngine.focusOrder).toBeCalledWith(-1);
expect(fakeEngine.focusOrder).toBeCalledWith(-1, false);
});
});
});
Expand Down
4 changes: 2 additions & 2 deletions src/controller/action.ts
Expand Up @@ -87,7 +87,7 @@ export class FocusNextWindow extends ActionImpl implements Action {
}

public executeWithoutLayoutOverride(): void {
this.engine.focusOrder(+1);
this.engine.focusOrder(+1, false);
}
}

Expand All @@ -97,7 +97,7 @@ export class FocusPreviousWindow extends ActionImpl implements Action {
}

public executeWithoutLayoutOverride(): void {
this.engine.focusOrder(-1);
this.engine.focusOrder(-1, false);
}
}

Expand Down
38 changes: 36 additions & 2 deletions src/controller/index.ts
Expand Up @@ -9,6 +9,7 @@ import { WindowState } from "../engine/window";

import { DriverContext, KWinDriver } from "../driver";
import { DriverSurface } from "../driver/surface";
import MonocleLayout from "../engine/layout/monocle_layout";

import Config from "../config";
import Debug from "../util/debug";
Expand All @@ -28,7 +29,6 @@ export interface Controller {
* A bunch of surfaces, that represent the user's screens.
*/
readonly screens: DriverSurface[];

/**
* Current active window. In other words the window, that has focus.
*/
Expand Down Expand Up @@ -144,7 +144,6 @@ export interface Controller {
export class TilingController implements Controller {
private engine: Engine;
private driver: DriverContext;

public constructor(
qmlObjects: Bismuth.Qml.Main,
kwinApi: KWin.Api,
Expand Down Expand Up @@ -206,6 +205,12 @@ export class TilingController implements Controller {
{ srf: this.currentSurface },
]);
this.engine.arrange();
/* HACK: minimize others and change geometry with Monocle Layout and
* config.monocleMinimizeRest
*/
if (this.currentWindow) {
this.onWindowFocused(this.currentWindow);
}
}

public onWindowAdded(window: Window): void {
Expand Down Expand Up @@ -237,6 +242,16 @@ export class TilingController implements Controller {

this.engine.unmanage(window);
this.engine.arrange();
bladedvox marked this conversation as resolved.
Show resolved Hide resolved

// Switch to next window if monocle with config.monocleMinimizeRest
if (!this.currentWindow && this.engine.isLayoutMonocleAndMinimizeRest()) {
this.engine.focusOrder(1, true);
/* HACK: force window to maximize if it isn't already
* This is ultimately to trigger onWindowFocused() at the right time
*/
this.engine.focusOrder(1, true);
this.engine.focusOrder(-1, true);
}
}

public onWindowMoveStart(_window: Window): void {
Expand Down Expand Up @@ -334,6 +349,25 @@ export class TilingController implements Controller {

public onWindowFocused(window: Window): void {
window.timestamp = new Date().getTime();
this.currentWindow = window;
// Minimize other windows if Moncole and config.monocleMinimizeRest
if (
this.engine.isLayoutMonocleAndMinimizeRest() &&
this.engine.windows.getVisibleTiles(window.surface).includes(window)
) {
/* If a window hasn't been foucsed in this layout yet, ensure its geometry
* gets maximized.
*/
this.engine
.currentLayoutOnCurrentSurface()
.apply(
this,
this.engine.windows.getAllTileables(window.surface),
window.surface.workingArea
);

this.engine.minimizeOthers(window);
}
}

public manageWindow(win: Window): void {
Expand Down
20 changes: 19 additions & 1 deletion src/driver/window.ts
Expand Up @@ -18,8 +18,10 @@ export interface DriverWindow {
readonly maximized: boolean;
readonly shouldIgnore: boolean;
readonly shouldFloat: boolean;

readonly screen: number;
readonly active: boolean;
surface: DriverSurface;
minimized: boolean;

commit(geometry?: Rect, noBorder?: boolean, keepAbove?: boolean): void;
visible(srf: DriverSurface): boolean;
Expand All @@ -41,6 +43,10 @@ export class KWinWindow implements DriverWindow {
return toRect(this.client.geometry);
}

public get active(): boolean {
return this.client.active;
}

public get shouldIgnore(): boolean {
const resourceClass = String(this.client.resourceClass);
const resourceName = String(this.client.resourceName);
Expand Down Expand Up @@ -69,6 +75,18 @@ export class KWinWindow implements DriverWindow {
);
}

public get screen(): number {
return this.client.screen;
}

public get minimized(): boolean {
return this.client.minimized;
}

public set minimized(min: boolean) {
this.client.minimized = min;
}

public maximized: boolean;

public get surface(): DriverSurface {
Expand Down
94 changes: 71 additions & 23 deletions src/engine/index.ts
Expand Up @@ -49,7 +49,13 @@ export interface Engine {
enforceSize(window: Window): void;
currentLayoutOnCurrentSurface(): WindowsLayout;
currentWindow(): Window | null;
focusOrder(step: -1 | 1): void;

/**
* Focus next or previous window
* @param step Direction to step in (1=forward, -1=backward)
* @param includeHidden Whether to step through (true) or skip over (false) minimized windows
*/
focusOrder(step: -1 | 1, includeHidden: boolean): void;
bladedvox marked this conversation as resolved.
Show resolved Hide resolved
focusDir(dir: Direction): void;
swapOrder(window: Window, step: -1 | 1): void;
swapDirOrMoveFloat(dir: Direction): void;
Expand All @@ -58,7 +64,8 @@ export interface Engine {
floatAll(srf: DriverSurface): void;
cycleLayout(step: 1 | -1): void;
setLayout(layoutClassID: string): void;

minimizeOthers(window: Window): void;
isLayoutMonocleAndMinimizeRest(): boolean;
showNotification(text: string): void;
}

Expand Down Expand Up @@ -343,42 +350,42 @@ export class TilingEngine implements Engine {
this.windows.remove(window);
}

/**
* Focus the next or previous window.
/** Focus next or previous window
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You don't have to duplicate JSDoc here.

* @param step direction to step in (1 for forward, -1 for back)
* @param includeHidden whether to switch to or skip minimized windows
*/
public focusOrder(step: -1 | 1): void {
public focusOrder(step: -1 | 1, includeHidden = false): void {
const window = this.controller.currentWindow;
let windows;

/* if no current window, select the first tile. */
if (window === null) {
const tiles = this.windows.getVisibleTiles(
this.controller.currentSurface
);
if (tiles.length > 1) {
this.controller.currentWindow = tiles[0];
}
return;
if (includeHidden) {
windows = this.windows.getAllWindows(this.controller.currentSurface);
} else {
windows = this.windows.getVisibleWindows(this.controller.currentSurface);
}

const visibles = this.windows.getVisibleWindows(
this.controller.currentSurface
);
if (visibles.length === 0) {
if (windows.length === 0) {
// Nothing to focus
return;
}

const idx = visibles.indexOf(window);
/* If no current window, select the first one. */
if (window === null) {
this.controller.currentWindow = windows[0];
return;
}

const idx = windows.indexOf(window);
if (!window || idx < 0) {
/* unmanaged window -> focus master */
this.controller.currentWindow = visibles[0];
/* This probably shouldn't happen, but just in case... */
this.controller.currentWindow = windows[0];
return;
}

const num = visibles.length;
const num = windows.length;
const newIndex = (idx + (step % num) + num) % num;

this.controller.currentWindow = visibles[newIndex];
this.controller.currentWindow = windows[newIndex];
}

/**
Expand Down Expand Up @@ -548,6 +555,14 @@ export class TilingEngine implements Engine {
);
if (layout) {
this.controller.showNotification(layout.description);

// Minimize inactive windows if Monocle and config.monocleMinimizeRest
if (
this.isLayoutMonocleAndMinimizeRest() &&
this.controller.currentWindow
) {
this.minimizeOthers(this.controller.currentWindow);
}
}
}

Expand All @@ -561,9 +576,42 @@ export class TilingEngine implements Engine {
);
if (layout) {
this.controller.showNotification(layout.description);

// Minimize inactive windows if Monocle and config.monocleMinimizeRest
if (
this.isLayoutMonocleAndMinimizeRest() &&
this.controller.currentWindow
) {
this.minimizeOthers(this.controller.currentWindow);
}
}
}

/**
* Minimize all windows on the surface except the given window.
* Used mainly in Monocle mode with config.monocleMinimizeRest
*/
public minimizeOthers(window: Window): void {
for (const tile of this.windows.getVisibleTiles(window.surface)) {
if (
tile.screen == window.screen &&
tile.id !== window.id &&
this.windows.getVisibleTiles(window.surface).includes(window)
) {
tile.minimized = true;
} else {
tile.minimized = false;
}
}
}

public isLayoutMonocleAndMinimizeRest(): boolean {
return (
this.currentLayoutOnCurrentSurface() instanceof MonocleLayout &&
this.config.monocleMinimizeRest
);
}

private getNeighborByDirection(basis: Window, dir: Direction): Window | null {
let vertical: boolean;
let sign: -1 | 1;
Expand Down
20 changes: 3 additions & 17 deletions src/engine/layout/monocle_layout.ts
Expand Up @@ -8,8 +8,6 @@ import { WindowsLayout } from ".";
import Window from "../window";
import { WindowState } from "../window";

import { KWinWindow } from "../../driver/window";

import {
Action,
FocusBottomWindow,
Expand Down Expand Up @@ -43,21 +41,9 @@ export default class MonocleLayout implements WindowsLayout {
tile.state = this.config.monocleMaximize
? WindowState.Maximized
: WindowState.Tiled;

tile.geometry = area;
});

/* KWin-specific `monocleMinimizeRest` option */
if (this.config.monocleMinimizeRest) {
const tiles = [...tileables];
const current = controller.currentWindow;
if (current && current.tiled) {
tiles.forEach((window) => {
if (window !== current) {
(window.window as KWinWindow).client.minimized = true;
}
});
}
}
}

public clone(): this {
Expand All @@ -71,13 +57,13 @@ export default class MonocleLayout implements WindowsLayout {
action instanceof FocusLeftWindow ||
action instanceof FocusPreviousWindow
) {
engine.focusOrder(-1);
engine.focusOrder(-1, this.config.monocleMinimizeRest);
} else if (
action instanceof FocusBottomWindow ||
action instanceof FocusRightWindow ||
action instanceof FocusNextWindow
) {
engine.focusOrder(1);
engine.focusOrder(1, this.config.monocleMinimizeRest);
} else {
console.log("Executing from Monocle regular action!");
action.executeWithoutLayoutOverride();
Expand Down
12 changes: 12 additions & 0 deletions src/engine/window.ts
Expand Up @@ -57,6 +57,18 @@ export default class Window {
return this.window.shouldIgnore;
}

public get screen(): number {
return this.window.screen;
}

public get minimized(): boolean {
return this.window.minimized;
}

public set minimized(min: boolean) {
this.window.minimized = min;
}

/** If this window ***can be*** tiled by layout. */
public get tileable(): boolean {
return Window.isTileableState(this.state);
Expand Down
12 changes: 12 additions & 0 deletions src/engine/window_store.ts
Expand Up @@ -95,5 +95,17 @@ export default class WindowStore {
return this.list.filter((win) => win.tileable && win.visible(srf));
}

/**
* Return all "tileable" windows on the given surface, including hidden
*/
public getAllTileables(srf: DriverSurface): Window[] {
return this.list.filter((win) => win.tileable && win.surface.id === srf.id);
}

/** Return all windows on this surface, including minimized windows */
public getAllWindows(srf: DriverSurface): Window[] {
return this.list.filter((win) => win.surface.id === srf.id);
}

//#endregion
}