mirror of
https://github.com/Bismuth-Forge/bismuth.git
synced 2024-09-11 08:25:39 +03:00
fix: 🐛 Monocle Layout "minimize unfocused windows" fixes and improvements
## Summary Fixes Monocle Layout with `Minimize Unfocused Windows` (internally `config.monocleMinimizeRest`) option enabled. Restores previous functionality (before Wayland patches) and adds * minimize inactive windows only on same monitor with multimonitor setup * can switch windows with focus-changing `Actions` * switches to the next window when closing the active window (still a little buggy in multimonitor, but a big improvment) * handles moving window onto a `Surface` with this layout properly ## UI Changes Removed "(WIP)" from "Minimize unfocused windows" option in the config UI. ## Test Plan 1. Reload script 2. Verify all behavior in the Summary ## Related Issues Closes #43, #55
This commit is contained in:
parent
d196853941
commit
5e262141a1
0
.husky/pre-commit
Normal file → Executable file
0
.husky/pre-commit
Normal file → Executable file
@ -115,7 +115,7 @@
|
|||||||
<item>
|
<item>
|
||||||
<widget class="QCheckBox" name="kcfg_monocleMinimizeRest">
|
<widget class="QCheckBox" name="kcfg_monocleMinimizeRest">
|
||||||
<property name="text">
|
<property name="text">
|
||||||
<string>Minimize unfocused windows (WIP)</string>
|
<string>Minimize unfocused windows</string>
|
||||||
</property>
|
</property>
|
||||||
</widget>
|
</widget>
|
||||||
</item>
|
</item>
|
||||||
|
@ -69,7 +69,7 @@ describe("action", () => {
|
|||||||
|
|
||||||
action.execute();
|
action.execute();
|
||||||
|
|
||||||
expect(fakeEngine.focusOrder).toBeCalledWith(1);
|
expect(fakeEngine.focusOrder).toBeCalledWith(1, false);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -79,7 +79,7 @@ describe("action", () => {
|
|||||||
|
|
||||||
action.execute();
|
action.execute();
|
||||||
|
|
||||||
expect(fakeEngine.focusOrder).toBeCalledWith(-1);
|
expect(fakeEngine.focusOrder).toBeCalledWith(-1, false);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
@ -87,7 +87,7 @@ export class FocusNextWindow extends ActionImpl implements Action {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public executeWithoutLayoutOverride(): void {
|
public executeWithoutLayoutOverride(): void {
|
||||||
this.engine.focusOrder(+1);
|
this.engine.focusOrder(+1, false);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -97,7 +97,7 @@ export class FocusPreviousWindow extends ActionImpl implements Action {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public executeWithoutLayoutOverride(): void {
|
public executeWithoutLayoutOverride(): void {
|
||||||
this.engine.focusOrder(-1);
|
this.engine.focusOrder(-1, false);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -9,6 +9,7 @@ import { WindowState } from "../engine/window";
|
|||||||
|
|
||||||
import { DriverContext, KWinDriver } from "../driver";
|
import { DriverContext, KWinDriver } from "../driver";
|
||||||
import { DriverSurface } from "../driver/surface";
|
import { DriverSurface } from "../driver/surface";
|
||||||
|
import MonocleLayout from "../engine/layout/monocle_layout";
|
||||||
|
|
||||||
import Config from "../config";
|
import Config from "../config";
|
||||||
import Debug from "../util/debug";
|
import Debug from "../util/debug";
|
||||||
@ -28,7 +29,6 @@ export interface Controller {
|
|||||||
* A bunch of surfaces, that represent the user's screens.
|
* A bunch of surfaces, that represent the user's screens.
|
||||||
*/
|
*/
|
||||||
readonly screens: DriverSurface[];
|
readonly screens: DriverSurface[];
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Current active window. In other words the window, that has focus.
|
* Current active window. In other words the window, that has focus.
|
||||||
*/
|
*/
|
||||||
@ -144,7 +144,6 @@ export interface Controller {
|
|||||||
export class TilingController implements Controller {
|
export class TilingController implements Controller {
|
||||||
private engine: Engine;
|
private engine: Engine;
|
||||||
private driver: DriverContext;
|
private driver: DriverContext;
|
||||||
|
|
||||||
public constructor(
|
public constructor(
|
||||||
qmlObjects: Bismuth.Qml.Main,
|
qmlObjects: Bismuth.Qml.Main,
|
||||||
kwinApi: KWin.Api,
|
kwinApi: KWin.Api,
|
||||||
@ -206,6 +205,12 @@ export class TilingController implements Controller {
|
|||||||
{ srf: this.currentSurface },
|
{ srf: this.currentSurface },
|
||||||
]);
|
]);
|
||||||
this.engine.arrange();
|
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 {
|
public onWindowAdded(window: Window): void {
|
||||||
@ -237,6 +242,16 @@ export class TilingController implements Controller {
|
|||||||
|
|
||||||
this.engine.unmanage(window);
|
this.engine.unmanage(window);
|
||||||
this.engine.arrange();
|
this.engine.arrange();
|
||||||
|
|
||||||
|
// 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 {
|
public onWindowMoveStart(_window: Window): void {
|
||||||
@ -334,6 +349,25 @@ export class TilingController implements Controller {
|
|||||||
|
|
||||||
public onWindowFocused(window: Window): void {
|
public onWindowFocused(window: Window): void {
|
||||||
window.timestamp = new Date().getTime();
|
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 {
|
public manageWindow(win: Window): void {
|
||||||
|
@ -18,8 +18,10 @@ export interface DriverWindow {
|
|||||||
readonly maximized: boolean;
|
readonly maximized: boolean;
|
||||||
readonly shouldIgnore: boolean;
|
readonly shouldIgnore: boolean;
|
||||||
readonly shouldFloat: boolean;
|
readonly shouldFloat: boolean;
|
||||||
|
readonly screen: number;
|
||||||
|
readonly active: boolean;
|
||||||
surface: DriverSurface;
|
surface: DriverSurface;
|
||||||
|
minimized: boolean;
|
||||||
|
|
||||||
commit(geometry?: Rect, noBorder?: boolean, keepAbove?: boolean): void;
|
commit(geometry?: Rect, noBorder?: boolean, keepAbove?: boolean): void;
|
||||||
visible(srf: DriverSurface): boolean;
|
visible(srf: DriverSurface): boolean;
|
||||||
@ -41,6 +43,10 @@ export class KWinWindow implements DriverWindow {
|
|||||||
return toRect(this.client.geometry);
|
return toRect(this.client.geometry);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public get active(): boolean {
|
||||||
|
return this.client.active;
|
||||||
|
}
|
||||||
|
|
||||||
public get shouldIgnore(): boolean {
|
public get shouldIgnore(): boolean {
|
||||||
const resourceClass = String(this.client.resourceClass);
|
const resourceClass = String(this.client.resourceClass);
|
||||||
const resourceName = String(this.client.resourceName);
|
const resourceName = String(this.client.resourceName);
|
||||||
@ -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 maximized: boolean;
|
||||||
|
|
||||||
public get surface(): DriverSurface {
|
public get surface(): DriverSurface {
|
||||||
|
@ -49,7 +49,13 @@ export interface Engine {
|
|||||||
enforceSize(window: Window): void;
|
enforceSize(window: Window): void;
|
||||||
currentLayoutOnCurrentSurface(): WindowsLayout;
|
currentLayoutOnCurrentSurface(): WindowsLayout;
|
||||||
currentWindow(): Window | null;
|
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;
|
||||||
focusDir(dir: Direction): void;
|
focusDir(dir: Direction): void;
|
||||||
swapOrder(window: Window, step: -1 | 1): void;
|
swapOrder(window: Window, step: -1 | 1): void;
|
||||||
swapDirOrMoveFloat(dir: Direction): void;
|
swapDirOrMoveFloat(dir: Direction): void;
|
||||||
@ -58,7 +64,8 @@ export interface Engine {
|
|||||||
floatAll(srf: DriverSurface): void;
|
floatAll(srf: DriverSurface): void;
|
||||||
cycleLayout(step: 1 | -1): void;
|
cycleLayout(step: 1 | -1): void;
|
||||||
setLayout(layoutClassID: string): void;
|
setLayout(layoutClassID: string): void;
|
||||||
|
minimizeOthers(window: Window): void;
|
||||||
|
isLayoutMonocleAndMinimizeRest(): boolean;
|
||||||
showNotification(text: string): void;
|
showNotification(text: string): void;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -343,42 +350,42 @@ export class TilingEngine implements Engine {
|
|||||||
this.windows.remove(window);
|
this.windows.remove(window);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/** Focus next or previous window
|
||||||
* Focus the next or previous window.
|
* @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;
|
const window = this.controller.currentWindow;
|
||||||
|
let windows;
|
||||||
|
|
||||||
/* if no current window, select the first tile. */
|
if (includeHidden) {
|
||||||
if (window === null) {
|
windows = this.windows.getAllWindows(this.controller.currentSurface);
|
||||||
const tiles = this.windows.getVisibleTiles(
|
} else {
|
||||||
this.controller.currentSurface
|
windows = this.windows.getVisibleWindows(this.controller.currentSurface);
|
||||||
);
|
|
||||||
if (tiles.length > 1) {
|
|
||||||
this.controller.currentWindow = tiles[0];
|
|
||||||
}
|
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const visibles = this.windows.getVisibleWindows(
|
if (windows.length === 0) {
|
||||||
this.controller.currentSurface
|
|
||||||
);
|
|
||||||
if (visibles.length === 0) {
|
|
||||||
// Nothing to focus
|
// Nothing to focus
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const idx = visibles.indexOf(window);
|
/* If no current window, select the first one. */
|
||||||
if (!window || idx < 0) {
|
if (window === null) {
|
||||||
/* unmanaged window -> focus master */
|
this.controller.currentWindow = windows[0];
|
||||||
this.controller.currentWindow = visibles[0];
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const num = visibles.length;
|
const idx = windows.indexOf(window);
|
||||||
|
if (!window || idx < 0) {
|
||||||
|
/* This probably shouldn't happen, but just in case... */
|
||||||
|
this.controller.currentWindow = windows[0];
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const num = windows.length;
|
||||||
const newIndex = (idx + (step % num) + num) % num;
|
const newIndex = (idx + (step % num) + num) % num;
|
||||||
|
|
||||||
this.controller.currentWindow = visibles[newIndex];
|
this.controller.currentWindow = windows[newIndex];
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -548,6 +555,14 @@ export class TilingEngine implements Engine {
|
|||||||
);
|
);
|
||||||
if (layout) {
|
if (layout) {
|
||||||
this.controller.showNotification(layout.description);
|
this.controller.showNotification(layout.description);
|
||||||
|
|
||||||
|
// Minimize inactive windows if Monocle and config.monocleMinimizeRest
|
||||||
|
if (
|
||||||
|
this.isLayoutMonocleAndMinimizeRest() &&
|
||||||
|
this.controller.currentWindow
|
||||||
|
) {
|
||||||
|
this.minimizeOthers(this.controller.currentWindow);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -561,9 +576,42 @@ export class TilingEngine implements Engine {
|
|||||||
);
|
);
|
||||||
if (layout) {
|
if (layout) {
|
||||||
this.controller.showNotification(layout.description);
|
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 {
|
private getNeighborByDirection(basis: Window, dir: Direction): Window | null {
|
||||||
let vertical: boolean;
|
let vertical: boolean;
|
||||||
let sign: -1 | 1;
|
let sign: -1 | 1;
|
||||||
|
@ -8,8 +8,6 @@ import { WindowsLayout } from ".";
|
|||||||
import Window from "../window";
|
import Window from "../window";
|
||||||
import { WindowState } from "../window";
|
import { WindowState } from "../window";
|
||||||
|
|
||||||
import { KWinWindow } from "../../driver/window";
|
|
||||||
|
|
||||||
import {
|
import {
|
||||||
Action,
|
Action,
|
||||||
FocusBottomWindow,
|
FocusBottomWindow,
|
||||||
@ -43,21 +41,9 @@ export default class MonocleLayout implements WindowsLayout {
|
|||||||
tile.state = this.config.monocleMaximize
|
tile.state = this.config.monocleMaximize
|
||||||
? WindowState.Maximized
|
? WindowState.Maximized
|
||||||
: WindowState.Tiled;
|
: WindowState.Tiled;
|
||||||
|
|
||||||
tile.geometry = area;
|
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 {
|
public clone(): this {
|
||||||
@ -71,13 +57,13 @@ export default class MonocleLayout implements WindowsLayout {
|
|||||||
action instanceof FocusLeftWindow ||
|
action instanceof FocusLeftWindow ||
|
||||||
action instanceof FocusPreviousWindow
|
action instanceof FocusPreviousWindow
|
||||||
) {
|
) {
|
||||||
engine.focusOrder(-1);
|
engine.focusOrder(-1, this.config.monocleMinimizeRest);
|
||||||
} else if (
|
} else if (
|
||||||
action instanceof FocusBottomWindow ||
|
action instanceof FocusBottomWindow ||
|
||||||
action instanceof FocusRightWindow ||
|
action instanceof FocusRightWindow ||
|
||||||
action instanceof FocusNextWindow
|
action instanceof FocusNextWindow
|
||||||
) {
|
) {
|
||||||
engine.focusOrder(1);
|
engine.focusOrder(1, this.config.monocleMinimizeRest);
|
||||||
} else {
|
} else {
|
||||||
console.log("Executing from Monocle regular action!");
|
console.log("Executing from Monocle regular action!");
|
||||||
action.executeWithoutLayoutOverride();
|
action.executeWithoutLayoutOverride();
|
||||||
|
@ -57,6 +57,18 @@ export default class Window {
|
|||||||
return this.window.shouldIgnore;
|
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. */
|
/** If this window ***can be*** tiled by layout. */
|
||||||
public get tileable(): boolean {
|
public get tileable(): boolean {
|
||||||
return Window.isTileableState(this.state);
|
return Window.isTileableState(this.state);
|
||||||
|
@ -95,5 +95,17 @@ export default class WindowStore {
|
|||||||
return this.list.filter((win) => win.tileable && win.visible(srf));
|
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
|
//#endregion
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user