mirror of
https://github.com/Bismuth-Forge/bismuth.git
synced 2024-08-15 19:30: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>
|
||||
<widget class="QCheckBox" name="kcfg_monocleMinimizeRest">
|
||||
<property name="text">
|
||||
<string>Minimize unfocused windows (WIP)</string>
|
||||
<string>Minimize unfocused windows</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
|
@ -69,7 +69,7 @@ describe("action", () => {
|
||||
|
||||
action.execute();
|
||||
|
||||
expect(fakeEngine.focusOrder).toBeCalledWith(1);
|
||||
expect(fakeEngine.focusOrder).toBeCalledWith(1, false);
|
||||
});
|
||||
});
|
||||
|
||||
@ -79,7 +79,7 @@ describe("action", () => {
|
||||
|
||||
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 {
|
||||
this.engine.focusOrder(+1);
|
||||
this.engine.focusOrder(+1, false);
|
||||
}
|
||||
}
|
||||
|
||||
@ -97,7 +97,7 @@ export class FocusPreviousWindow extends ActionImpl implements Action {
|
||||
}
|
||||
|
||||
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 { DriverSurface } from "../driver/surface";
|
||||
import MonocleLayout from "../engine/layout/monocle_layout";
|
||||
|
||||
import Config from "../config";
|
||||
import Debug from "../util/debug";
|
||||
@ -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.
|
||||
*/
|
||||
@ -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,
|
||||
@ -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 {
|
||||
@ -237,6 +242,16 @@ export class TilingController implements Controller {
|
||||
|
||||
this.engine.unmanage(window);
|
||||
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 {
|
||||
@ -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 {
|
||||
|
@ -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;
|
||||
@ -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);
|
||||
@ -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 {
|
||||
|
@ -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;
|
||||
focusDir(dir: Direction): void;
|
||||
swapOrder(window: Window, step: -1 | 1): void;
|
||||
swapDirOrMoveFloat(dir: Direction): void;
|
||||
@ -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;
|
||||
}
|
||||
|
||||
@ -343,42 +350,42 @@ export class TilingEngine implements Engine {
|
||||
this.windows.remove(window);
|
||||
}
|
||||
|
||||
/**
|
||||
* Focus the next or previous window.
|
||||
/** Focus 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;
|
||||
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 (!window || idx < 0) {
|
||||
/* unmanaged window -> focus master */
|
||||
this.controller.currentWindow = visibles[0];
|
||||
/* If no current window, select the first one. */
|
||||
if (window === null) {
|
||||
this.controller.currentWindow = windows[0];
|
||||
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;
|
||||
|
||||
this.controller.currentWindow = visibles[newIndex];
|
||||
this.controller.currentWindow = windows[newIndex];
|
||||
}
|
||||
|
||||
/**
|
||||
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -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;
|
||||
|
@ -8,8 +8,6 @@ import { WindowsLayout } from ".";
|
||||
import Window from "../window";
|
||||
import { WindowState } from "../window";
|
||||
|
||||
import { KWinWindow } from "../../driver/window";
|
||||
|
||||
import {
|
||||
Action,
|
||||
FocusBottomWindow,
|
||||
@ -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 {
|
||||
@ -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();
|
||||
|
@ -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);
|
||||
|
@ -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
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user