feat: 🏗️ lean out the shortcuts definitions

This completely changes how are shortcuts are interpreted by the script.
Instead of using the complicated system, where up, down, left and right
keys (hjkl) were used dynamically depended on the mode (directional,
dwm), we just put all the possible shortcuts in the list. This increases
the user's flexibility to bind shortcuts however they want and also
reduces the code complexity.

Since this change breaks the existing shortcuts for the user anyway
and we are currently earlier in the development, it makes sense
to break shortcuts entirely, to we also break the naming scheme.

BREAKING CHANGE: This completely breaks existing shortcuts. Users need
to remove all the existing shortcuts from config file and relogin into
the session immediately. After that, the new set of shortcuts will be
visible in the UI.
This commit is contained in:
Mikhail Zolotukhin 2021-09-21 18:43:29 +03:00
parent dd1212c2b4
commit 92d57cc82b
14 changed files with 632 additions and 645 deletions

View File

@ -381,217 +381,6 @@
</layout>
</widget>
</item>
<item>
<widget class="QGroupBox" name="groupBox_3">
<property name="font">
<font>
<weight>50</weight>
<bold>false</bold>
</font>
</property>
<property name="title">
<string>Directional Keys Behaviors</string>
</property>
<layout class="QGridLayout" name="gridLayout_4">
<item row="0" column="1">
<widget class="QLabel" name="label_2">
<property name="font">
<font>
<weight>50</weight>
<italic>false</italic>
<bold>false</bold>
<underline>true</underline>
</font>
</property>
<property name="text">
<string>Up</string>
</property>
<property name="alignment">
<set>Qt::AlignCenter</set>
</property>
</widget>
</item>
<item row="2" column="0">
<widget class="QRadioButton" name="kcfg_directionalKeyFocus">
<property name="sizePolicy">
<sizepolicy hsizetype="Maximum" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="font">
<font>
<weight>75</weight>
<bold>true</bold>
</font>
</property>
<property name="text">
<string>Directional</string>
</property>
<attribute name="buttonGroup">
<string notr="true">directionalKeyButtonGroup</string>
</attribute>
</widget>
</item>
<item row="1" column="0">
<widget class="QRadioButton" name="kcfg_directionalKeyDwm">
<property name="sizePolicy">
<sizepolicy hsizetype="Maximum" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="font">
<font>
<weight>75</weight>
<bold>true</bold>
</font>
</property>
<property name="text">
<string>DWM style</string>
</property>
<attribute name="buttonGroup">
<string notr="true">directionalKeyButtonGroup</string>
</attribute>
</widget>
</item>
<item row="0" column="3">
<widget class="QLabel" name="label_4">
<property name="font">
<font>
<weight>50</weight>
<italic>false</italic>
<bold>false</bold>
<underline>true</underline>
</font>
</property>
<property name="text">
<string>Left</string>
</property>
<property name="alignment">
<set>Qt::AlignCenter</set>
</property>
</widget>
</item>
<item row="1" column="1">
<widget class="QLabel" name="label">
<property name="text">
<string>Focus Next</string>
</property>
<property name="alignment">
<set>Qt::AlignCenter</set>
</property>
</widget>
</item>
<item row="0" column="2">
<widget class="QLabel" name="label_3">
<property name="font">
<font>
<weight>50</weight>
<italic>false</italic>
<bold>false</bold>
<underline>true</underline>
</font>
</property>
<property name="text">
<string>Down</string>
</property>
<property name="alignment">
<set>Qt::AlignCenter</set>
</property>
</widget>
</item>
<item row="0" column="4">
<widget class="QLabel" name="label_5">
<property name="font">
<font>
<weight>50</weight>
<italic>false</italic>
<bold>false</bold>
<underline>true</underline>
</font>
</property>
<property name="text">
<string>Right</string>
</property>
<property name="alignment">
<set>Qt::AlignCenter</set>
</property>
</widget>
</item>
<item row="1" column="2">
<widget class="QLabel" name="label_6">
<property name="text">
<string>Focus Prev</string>
</property>
<property name="alignment">
<set>Qt::AlignCenter</set>
</property>
</widget>
</item>
<item row="1" column="3">
<widget class="QLabel" name="label_7">
<property name="text">
<string>Adjust Layout</string>
</property>
<property name="alignment">
<set>Qt::AlignCenter</set>
</property>
</widget>
</item>
<item row="1" column="4">
<widget class="QLabel" name="label_8">
<property name="text">
<string>Adjust Layout</string>
</property>
<property name="alignment">
<set>Qt::AlignCenter</set>
</property>
</widget>
</item>
<item row="2" column="1">
<widget class="QLabel" name="label_9">
<property name="text">
<string>Focus Up</string>
</property>
<property name="alignment">
<set>Qt::AlignCenter</set>
</property>
</widget>
</item>
<item row="2" column="2">
<widget class="QLabel" name="label_10">
<property name="text">
<string>Focus Down</string>
</property>
<property name="alignment">
<set>Qt::AlignCenter</set>
</property>
</widget>
</item>
<item row="2" column="3">
<widget class="QLabel" name="label_11">
<property name="text">
<string>Focus Left</string>
</property>
<property name="alignment">
<set>Qt::AlignCenter</set>
</property>
</widget>
</item>
<item row="2" column="4">
<widget class="QLabel" name="label_12">
<property name="text">
<string>Focus Right</string>
</property>
<property name="alignment">
<set>Qt::AlignCenter</set>
</property>
</widget>
</item>
</layout>
</widget>
</item>
<item>
<widget class="QCheckBox" name="kcfg_newWindowAsMaster">
<property name="text">
@ -873,7 +662,4 @@
</widget>
<resources/>
<connections/>
<buttongroups>
<buttongroup name="directionalKeyButtonGroup"/>
</buttongroups>
</ui>

View File

@ -180,16 +180,6 @@
<default>false</default>
</entry>
<entry name="directionalKeyDwm" type="Bool">
<label>Up, Down, Left, and Right shortcut behaves like dwm</label>
<default>true</default>
</entry>
<entry name="directionalKeyFocus" type="Bool">
<label>Up, Down, Left, and Right shortcut moves focus</label>
<default>false</default>
</entry>
<entry name="limitTileWidth" type="Bool">
<label>Limit the width of tiles</label>
<default>false</default>

View File

@ -45,7 +45,6 @@ export default interface Config {
//#endregion
//#region Behavior
directionalKeyMode: "dwm" | "focus";
newWindowAsMaster: boolean;
//#endregion
@ -101,7 +100,6 @@ export class ConfigImpl implements Config {
//#endregion
//#region Behavior
public directionalKeyMode: "dwm" | "focus";
public newWindowAsMaster: boolean;
//#endregion
@ -203,15 +201,6 @@ export class ConfigImpl implements Config {
this.screenGapTop = this.kwinApi.KWin.readConfig("screenGapTop", 0);
this.tileLayoutGap = this.kwinApi.KWin.readConfig("tileLayoutGap", 0);
const directionalKeyDwm = this.kwinApi.KWin.readConfig(
"directionalKeyDwm",
true
);
// const directionalKeyFocus = this.kwinApi.KWin.readConfig(
// "directionalKeyFocus",
// false
// );
this.directionalKeyMode = directionalKeyDwm ? "dwm" : "focus";
this.newWindowAsMaster = this.kwinApi.KWin.readConfig(
"newWindowAsMaster",
false

View File

@ -3,45 +3,434 @@
//
// SPDX-License-Identifier: MIT
export enum Action {
Left,
Right,
Up,
Down,
import { Engine } from "../engine";
/* Alternate HJKL bindings */
FocusUp,
FocusDown,
FocusLeft,
FocusRight,
/**
* Action that is requested by the user.
*/
export interface Action {
/**
* Action name. It will be displayed it the shortcuts configuration window
*/
readonly name: string;
ShiftLeft,
ShiftRight,
ShiftUp,
ShiftDown,
/**
* The keybinding, that will be assigned to action by default.
*/
readonly defaultKeybinding: string;
SwapUp,
SwapDown,
SwapLeft,
SwapRight,
/**
* Execute action. This is basically a Command Design Pattern.
*/
execute(): void;
GrowWidth,
GrowHeight,
ShrinkWidth,
ShrinkHeight,
Increase,
Decrease,
ShiftIncrease,
ShiftDecrease,
ToggleFloat,
ToggleFloatAll,
SetMaster,
NextLayout,
PreviousLayout,
SetLayout,
Rotate,
RotatePart,
/**
* Execute action, but ignoring any overrides in the process
*/
executeWithoutLayoutOverride(): void;
}
/**
* Action basic implementation. Provides common grounds for other
* actions. Such as a template of action execution.
*/
abstract class ActionImpl implements Action {
constructor(
protected engine: Engine,
public name: string,
public defaultKeybinding: string
) {}
/**
* Action execution pattern. Executes the action override optionally
* defined in the layout and if not found executes the default
* behavior.
*/
public execute(): void {
console.log(`Bismuth: Executing action ${this.name}`);
const currentLayout = this.engine.currentLayoutOnCurrentSurface();
if (currentLayout.executeAction) {
currentLayout.executeAction(this.engine, this);
} else {
this.executeWithoutLayoutOverride();
}
// TODO: Maybe it worth moving this into engine?
this.engine.arrange();
}
/**
* Default action implementation on all layouts
*/
public abstract executeWithoutLayoutOverride(): void;
}
export class FocusNextWindow extends ActionImpl implements Action {
constructor(protected engine: Engine) {
super(engine, "Focus Next Window", "");
}
public executeWithoutLayoutOverride(): void {
this.engine.focusOrder(+1);
}
}
export class FocusPreviousWindow extends ActionImpl implements Action {
constructor(protected engine: Engine) {
super(engine, "Focus Previous Window", "");
}
public executeWithoutLayoutOverride(): void {
this.engine.focusOrder(-1);
}
}
export class FocusUpperWindow extends ActionImpl implements Action {
constructor(protected engine: Engine) {
super(engine, "Focus Upper Window", "Meta+K");
}
public executeWithoutLayoutOverride(): void {
this.engine.focusDir("up");
}
}
export class FocusBottomWindow extends ActionImpl implements Action {
constructor(protected engine: Engine) {
super(engine, "Focus Bottom Window", "Meta+J");
}
public executeWithoutLayoutOverride(): void {
this.engine.focusDir("down");
}
}
export class FocusLeftWindow extends ActionImpl implements Action {
constructor(protected engine: Engine) {
super(engine, "Focus Left Window", "Meta+H");
}
public executeWithoutLayoutOverride(): void {
this.engine.focusDir("left");
}
}
export class FocusRightWindow extends ActionImpl implements Action {
constructor(protected engine: Engine) {
super(engine, "Focus Right Window", "Meta+L");
}
public executeWithoutLayoutOverride(): void {
this.engine.focusDir("right");
}
}
export class MoveActiveWindowToNextPosition
extends ActionImpl
implements Action
{
constructor(protected engine: Engine) {
super(engine, "Move Window to the Next Position", "");
}
public executeWithoutLayoutOverride(): void {
const win = this.engine.currentWindow();
if (win) {
this.engine.swapOrder(win, +1);
}
}
}
export class MoveActiveWindowToPreviousPosition
extends ActionImpl
implements Action
{
constructor(protected engine: Engine) {
super(engine, "Move Window to the Previous Position", "");
}
public executeWithoutLayoutOverride(): void {
const win = this.engine.currentWindow();
if (win) {
this.engine.swapOrder(win, -1);
}
}
}
export class MoveActiveWindowUp extends ActionImpl implements Action {
constructor(protected engine: Engine) {
super(engine, "Move Window Up", "Meta+Shift+K");
}
public executeWithoutLayoutOverride(): void {
this.engine.swapDirOrMoveFloat("up");
}
}
export class MoveActiveWindowDown extends ActionImpl implements Action {
constructor(protected engine: Engine) {
super(engine, "Move Window Down", "Meta+Shift+J");
}
public executeWithoutLayoutOverride(): void {
this.engine.swapDirOrMoveFloat("down");
}
}
export class MoveActiveWindowLeft extends ActionImpl implements Action {
constructor(protected engine: Engine) {
super(engine, "Move Window Left", "Meta+Shift+H");
}
public executeWithoutLayoutOverride(): void {
this.engine.swapDirOrMoveFloat("left");
}
}
export class MoveActiveWindowRight extends ActionImpl implements Action {
constructor(protected engine: Engine) {
super(engine, "Move Window Right", "Meta+Shift+L");
}
public executeWithoutLayoutOverride(): void {
this.engine.swapDirOrMoveFloat("right");
}
}
export class IncreaseActiveWindowWidth extends ActionImpl implements Action {
constructor(protected engine: Engine) {
super(engine, "Increase Window Width", "Meta+Ctrl+L");
}
public executeWithoutLayoutOverride(): void {
const win = this.engine.currentWindow();
if (win) {
this.engine.resizeWindow(win, "east", 1);
}
}
}
export class IncreaseActiveWindowHeight extends ActionImpl implements Action {
constructor(protected engine: Engine) {
super(engine, "Increase Window Height", "Meta+Ctrl+J");
}
public executeWithoutLayoutOverride(): void {
const win = this.engine.currentWindow();
if (win) {
this.engine.resizeWindow(win, "south", 1);
}
}
}
export class DecreaseActiveWindowWidth extends ActionImpl implements Action {
constructor(protected engine: Engine) {
super(engine, "Decrease Window Width", "Meta+Ctrl+H");
}
public executeWithoutLayoutOverride(): void {
const win = this.engine.currentWindow();
if (win) {
this.engine.resizeWindow(win, "east", -1);
}
}
}
export class DecreaseActiveWindowHeight extends ActionImpl implements Action {
constructor(protected engine: Engine) {
super(engine, "Decrease Window Height", "Meta+Ctrl+K");
}
public executeWithoutLayoutOverride(): void {
const win = this.engine.currentWindow();
if (win) {
this.engine.resizeWindow(win, "south", -1);
}
}
}
export class IncreaseMasterAreaWindowCount
extends ActionImpl
implements Action
{
constructor(protected engine: Engine) {
super(engine, "Increase Master Area Window Count", "Meta+I");
}
public executeWithoutLayoutOverride(): void {
this.engine.showNotification("No Master Area");
}
}
export class DecreaseMasterAreaWindowCount
extends ActionImpl
implements Action
{
constructor(protected engine: Engine) {
super(engine, "Decrease Master Area Window Count", "Meta+D");
}
public executeWithoutLayoutOverride(): void {
this.engine.showNotification("No Master Area");
}
}
export class IncreaseLayoutMasterAreaSize extends ActionImpl implements Action {
constructor(protected engine: Engine) {
super(engine, "Increase Master Area Size", "");
}
public executeWithoutLayoutOverride(): void {
this.engine.showNotification("No Master Area");
}
}
export class DecreaseLayoutMasterAreaSize extends ActionImpl implements Action {
constructor(protected engine: Engine) {
super(engine, "Decrease Master Area Size", "");
}
public executeWithoutLayoutOverride(): void {
this.engine.showNotification("No Master Area");
}
}
export class ToggleActiveWindowFloating extends ActionImpl implements Action {
constructor(protected engine: Engine) {
super(engine, "Toggle Active Window Floating", "Meta+F");
}
public executeWithoutLayoutOverride(): void {
const win = this.engine.currentWindow();
if (win) {
this.engine.toggleFloat(win);
}
}
}
export class PushActiveWindowIntoMasterAreaFront
extends ActionImpl
implements Action
{
constructor(protected engine: Engine) {
super(engine, "Push Active Window to Master Area", "Meta+Return");
}
public executeWithoutLayoutOverride(): void {
const win = this.engine.currentWindow();
if (win) {
this.engine.setMaster(win);
}
}
}
export class SwitchToNextLayout extends ActionImpl implements Action {
constructor(protected engine: Engine) {
super(engine, "Switch to the Next Layout", "Meta+\\");
}
public executeWithoutLayoutOverride(): void {
this.engine.cycleLayout(1);
}
}
export class SwitchToPreviousLayout extends ActionImpl implements Action {
constructor(protected engine: Engine) {
super(engine, "Switch to the Previous Layout", "Meta+|");
}
public executeWithoutLayoutOverride(): void {
this.engine.cycleLayout(-1);
}
}
abstract class SetCurrentLayout extends ActionImpl implements Action {
constructor(protected engine: Engine, protected layoutId: string) {
super(engine, "", "");
}
public executeWithoutLayoutOverride(): void {
console.log("Set layout called!");
this.engine.setLayout(this.layoutId);
}
}
export class SetTileLayout extends SetCurrentLayout {
constructor(protected engine: Engine) {
super(engine, "TileLayout");
this.name = "Toggle Tile Layout";
this.defaultKeybinding = "Meta+T";
}
}
export class SetMonocleLayout extends SetCurrentLayout {
constructor(protected engine: Engine) {
super(engine, "MonocleLayout");
this.name = "Toggle Monocle Layout";
this.defaultKeybinding = "Meta+M";
}
}
export class SetThreeColumnLayout extends SetCurrentLayout {
constructor(protected engine: Engine) {
super(engine, "ThreeColumnLayout");
this.name = "Toggle Three Column Layout";
this.defaultKeybinding = "";
}
}
export class SetSpreadLayout extends SetCurrentLayout {
constructor(protected engine: Engine) {
super(engine, "SpreadLayout");
this.name = "Toggle Spread Layout";
this.defaultKeybinding = "";
}
}
export class SetStairLayout extends SetCurrentLayout {
constructor(protected engine: Engine) {
super(engine, "StairLayout");
this.name = "Toggle Stair Layout";
this.defaultKeybinding = "";
}
}
export class SetFloatingLayout extends SetCurrentLayout {
constructor(protected engine: Engine) {
// NOTE: space is intentional (Temporary)
super(engine, "FloatingLayout ");
this.name = "Toggle Stair Layout";
this.defaultKeybinding = "Meta+Shift+F";
}
}
export class SetQuarterLayout extends SetCurrentLayout {
constructor(protected engine: Engine) {
// NOTE: space is intentional (Temporary)
super(engine, "QuarterLayout ");
this.name = "Toggle Quarter Layout";
this.defaultKeybinding = "";
}
}
export class Rotate extends ActionImpl implements Action {
constructor(protected engine: Engine) {
super(engine, "Rotate", "Meta+R");
}
public executeWithoutLayoutOverride(): void {
this.engine.focusDir("left");
}
}
export class RotatePart extends ActionImpl implements Action {
constructor(protected engine: Engine) {
super(engine, "Rotate Part", "Meta+Shift+R");
}
public executeWithoutLayoutOverride(): void {
this.engine.focusDir("left");
}
}

View File

@ -3,8 +3,6 @@
//
// SPDX-License-Identifier: MIT
import { Action } from "./action";
import { Engine, TilingEngine } from "../engine";
import Window from "../engine/window";
import { WindowState } from "../engine/window";
@ -15,6 +13,8 @@ import { DriverSurface } from "../driver/surface";
import Config from "../config";
import Debug from "../util/debug";
import * as Action from "./action";
/**
* Entry point of the script (apart from QML). Handles the user input (shortcuts)
* and the events from the Driver (in other words KWin, the window manager/compositor).
@ -139,13 +139,6 @@ export interface Controller {
*/
onWindowFocused(window: Window): void;
/**
* React to a particular keyboard shortcut action from the user.
* @param input the shortcut action. For example focus the next window, or change the layout.
* @param data the action optional data, that it could held. For example the layout name to which user want to change.
*/
onShortcut(input: Action, data?: any): void;
/**
* Ask engine to manage the window
* @param win the window which needs to be managed.
@ -176,7 +169,7 @@ export class TilingController implements Controller {
this.debug.debug(() => `Config: ${this.config}`);
this.driver.bindEvents();
this.driver.bindShortcuts();
this.bindShortcuts();
this.driver.manageWindows();
@ -348,140 +341,55 @@ export class TilingController implements Controller {
window.timestamp = new Date().getTime();
}
public onShortcut(input: Action, data?: string): void {
if (this.config.directionalKeyMode === "focus") {
switch (input) {
case Action.Up:
input = Action.FocusUp;
break;
case Action.Down:
input = Action.FocusDown;
break;
case Action.Left:
input = Action.FocusLeft;
break;
case Action.Right:
input = Action.FocusRight;
break;
case Action.ShiftUp:
input = Action.SwapUp;
break;
case Action.ShiftDown:
input = Action.SwapDown;
break;
case Action.ShiftLeft:
input = Action.SwapLeft;
break;
case Action.ShiftRight:
input = Action.SwapRight;
break;
}
}
if (this.engine.handleLayoutShortcut(input, data)) {
this.engine.arrange();
return;
}
const window = this.currentWindow;
switch (input) {
case Action.Up:
this.engine.focusOrder(-1);
break;
case Action.Down:
this.engine.focusOrder(+1);
break;
case Action.FocusUp:
this.engine.focusDir("up");
break;
case Action.FocusDown:
this.engine.focusDir("down");
break;
case Action.FocusLeft:
this.engine.focusDir("left");
break;
case Action.FocusRight:
this.engine.focusDir("right");
break;
case Action.GrowWidth:
if (window) {
this.engine.resizeWindow(window, "east", 1);
}
break;
case Action.ShrinkWidth:
if (window) {
this.engine.resizeWindow(window, "east", -1);
}
break;
case Action.GrowHeight:
if (window) {
this.engine.resizeWindow(window, "south", 1);
}
break;
case Action.ShrinkHeight:
if (window) {
this.engine.resizeWindow(window, "south", -1);
}
break;
case Action.ShiftUp:
if (window) {
this.engine.swapOrder(window, -1);
}
break;
case Action.ShiftDown:
if (window) {
this.engine.swapOrder(window, +1);
}
break;
case Action.SwapUp:
this.engine.swapDirOrMoveFloat("up");
break;
case Action.SwapDown:
this.engine.swapDirOrMoveFloat("down");
break;
case Action.SwapLeft:
this.engine.swapDirOrMoveFloat("left");
break;
case Action.SwapRight:
this.engine.swapDirOrMoveFloat("right");
break;
case Action.SetMaster:
if (window) {
this.engine.setMaster(window);
}
break;
case Action.ToggleFloat:
if (window) {
this.engine.toggleFloat(window);
}
break;
case Action.ToggleFloatAll:
this.engine.floatAll(this.currentSurface);
break;
case Action.NextLayout:
this.engine.cycleLayout(1);
break;
case Action.PreviousLayout:
this.engine.cycleLayout(-1);
break;
case Action.SetLayout:
if (typeof data === "string") {
this.engine.setLayout(data);
}
break;
}
this.engine.arrange();
}
public manageWindow(win: Window): void {
this.engine.manage(win);
}
private bindShortcuts(): void {
const allPossibleActions = [
new Action.FocusNextWindow(this.engine),
new Action.FocusPreviousWindow(this.engine),
new Action.FocusUpperWindow(this.engine),
new Action.FocusBottomWindow(this.engine),
new Action.FocusLeftWindow(this.engine),
new Action.FocusRightWindow(this.engine),
new Action.MoveActiveWindowToNextPosition(this.engine),
new Action.MoveActiveWindowToPreviousPosition(this.engine),
new Action.MoveActiveWindowUp(this.engine),
new Action.MoveActiveWindowDown(this.engine),
new Action.MoveActiveWindowLeft(this.engine),
new Action.MoveActiveWindowRight(this.engine),
new Action.IncreaseActiveWindowWidth(this.engine),
new Action.IncreaseActiveWindowHeight(this.engine),
new Action.DecreaseActiveWindowWidth(this.engine),
new Action.DecreaseActiveWindowHeight(this.engine),
new Action.IncreaseMasterAreaWindowCount(this.engine),
new Action.DecreaseMasterAreaWindowCount(this.engine),
new Action.IncreaseLayoutMasterAreaSize(this.engine),
new Action.DecreaseLayoutMasterAreaSize(this.engine),
new Action.ToggleActiveWindowFloating(this.engine),
new Action.PushActiveWindowIntoMasterAreaFront(this.engine),
new Action.SwitchToNextLayout(this.engine),
new Action.SwitchToPreviousLayout(this.engine),
new Action.SetTileLayout(this.engine),
new Action.SetMonocleLayout(this.engine),
new Action.SetThreeColumnLayout(this.engine),
new Action.SetStairLayout(this.engine),
new Action.SetSpreadLayout(this.engine),
new Action.SetFloatingLayout(this.engine),
new Action.SetQuarterLayout(this.engine),
new Action.Rotate(this.engine),
new Action.RotatePart(this.engine),
];
for (const action of allPossibleActions) {
this.driver.bindShortcut(action);
}
}
}

View File

@ -13,17 +13,8 @@ import { Action } from "../controller/action";
import Window from "../engine/window";
import { WindowsLayoutClass } from "../engine/layout";
import { WindowState } from "../engine/window";
import MonocleLayout from "../engine/layout/monocle_layout";
import TileLayout from "../engine/layout/tile_layout";
import ThreeColumnLayout from "../engine/layout/three_column_layout";
import StairLayout from "../engine/layout/stair_layout";
import SpreadLayout from "../engine/layout/spread_layout";
import FloatingLayout from "../engine/layout/floating_layout";
import QuarterLayout from "../engine/layout/quarter_layout";
import Config from "../config";
import Debug from "../util/debug";
import qmlSetTimeout, { TimersPool } from "../util/timer";
@ -38,7 +29,7 @@ export interface DriverContext {
showNotification(text: string): void;
bindEvents(): void;
bindShortcuts(): void;
bindShortcut(action: Action): void;
manageWindows(): void;
}
@ -302,14 +293,6 @@ export class KWinDriver implements DriverContext {
* https://github.com/KDE/kwin/blob/master/scripts/minimizeall/contents/code/main.js */
}
/**
* Register Bismuth shortcuts
*/
public bindShortcuts(): void {
this.bindMainShortcuts();
this.bindLayoutShortcuts();
}
/**
* Manage the windows
*/
@ -343,67 +326,18 @@ export class KWinDriver implements DriverContext {
this.qml.popupDialog.show(text);
}
private bindMainShortcuts(): void {
const bind = (seq: string, title: string, action: Action): void => {
title = "Bismuth: " + title;
seq = "Meta+" + seq;
this.kwinApi.KWin.registerShortcut(title, "", seq, () => {
this.enter(() => this.controller.onShortcut(action));
});
};
public bindShortcut(action: Action): void {
const shortcutUITitle = `Bismuth: ${action.name}`;
bind("J", "Down/Next", Action.Down);
bind("K", "Up/Prev", Action.Up);
bind("H", "Left", Action.Left);
bind("L", "Right", Action.Right);
bind("Shift+J", "Move Down/Next", Action.ShiftDown);
bind("Shift+K", "Move Up/Prev", Action.ShiftUp);
bind("Shift+H", "Move Left", Action.ShiftLeft);
bind("Shift+L", "Move Right", Action.ShiftRight);
bind("Ctrl+J", "Grow Height", Action.GrowHeight);
bind("Ctrl+K", "Shrink Height", Action.ShrinkHeight);
bind("Ctrl+H", "Shrink Width", Action.ShrinkWidth);
bind("Ctrl+L", "Grow Width", Action.GrowWidth);
bind("I", "Increase", Action.Increase);
bind("D", "Decrease", Action.Decrease);
bind("F", "Float", Action.ToggleFloat);
bind("Shift+F", "Float All", Action.ToggleFloatAll);
bind("", "Cycle Layout", Action.NextLayout); // TODO: remove this shortcut
bind("\\", "Next Layout", Action.NextLayout);
bind("|", "Previous Layout", Action.PreviousLayout);
bind("R", "Rotate", Action.Rotate);
bind("Shift+R", "Rotate Part", Action.RotatePart);
bind("Return", "Set master", Action.SetMaster);
}
private bindLayoutShortcuts(): void {
const bind = (
seq: string,
title: string,
layoutClass: WindowsLayoutClass
): void => {
title = "Bismuth: " + title + " Layout";
seq = seq !== "" ? "Meta+" + seq : "";
this.kwinApi.KWin.registerShortcut(title, "", seq, () => {
this.enter(() =>
this.controller.onShortcut(Action.SetLayout, layoutClass.id)
);
});
};
bind("T", "Tile", TileLayout);
bind("M", "Monocle", MonocleLayout);
bind("", "Three Column", ThreeColumnLayout);
bind("", "Spread", SpreadLayout);
bind("", "Stair", StairLayout);
bind("", "Floating", FloatingLayout);
bind("", "Quarter", QuarterLayout);
console.log(`Registering ${shortcutUITitle}`);
this.kwinApi.KWin.registerShortcut(
shortcutUITitle,
"",
action.defaultKeybinding,
(): void => {
this.enter(() => action.execute());
}
);
}
/**

View File

@ -49,7 +49,8 @@ export interface Engine {
step: -1 | 1
): void;
enforceSize(window: Window): void;
handleLayoutShortcut(input: Action, data?: any): boolean;
currentLayoutOnCurrentSurface(): WindowsLayout;
currentWindow(): Window | null;
focusOrder(step: -1 | 1): void;
focusDir(dir: Direction): void;
swapOrder(window: Window, step: -1 | 1): void;
@ -301,6 +302,14 @@ export class TilingEngine implements Engine {
this.debug.debugObj(() => ["arrangeScreen/finished", { screenSurface }]);
}
public currentLayoutOnCurrentSurface(): WindowsLayout {
return this.layouts.getCurrentLayout(this.controller.currentSurface);
}
public currentWindow(): Window | null {
return this.controller.currentWindow;
}
/**
* Re-apply window geometry, computed by layout algorithm.
*
@ -561,21 +570,6 @@ export class TilingEngine implements Engine {
}
}
/**
* Let the current layout override shortcut.
*
* @returns True if the layout overrides the shortcut. False, otherwise.
*/
public handleLayoutShortcut(input: Action, data?: string): boolean {
const layout = this.layouts.getCurrentLayout(
this.controller.currentSurface
);
if (layout.handleShortcut) {
return layout.handleShortcut(this, input, data);
}
return false;
}
private getNeighborByDirection(basis: Window, dir: Direction): Window | null {
let vertical: boolean;
let sign: -1 | 1;

View File

@ -9,7 +9,11 @@ import { Engine } from "..";
import Window from "../window";
import { WindowState } from "../window";
import { Action } from "../../controller/action";
import {
Action,
DecreaseMasterAreaWindowCount,
IncreaseMasterAreaWindowCount,
} from "../../controller/action";
import { Controller } from "../../controller";
import Rect from "../../util/rect";
@ -95,23 +99,15 @@ export default class CascadeLayout implements WindowsLayout {
return new CascadeLayout(this.dir);
}
public handleShortcut(
engine: Engine,
input: Action,
_data?: string
): boolean {
switch (input) {
case Action.Increase:
this.dir = (this.dir + 1 + 8) % 8;
engine.showNotification(this.description);
break;
case Action.Decrease:
this.dir = (this.dir - 1 + 8) % 8;
engine.showNotification(this.description);
break;
default:
return false;
public executeAction(engine: Engine, action: Action): void {
if (action instanceof IncreaseMasterAreaWindowCount) {
this.dir = (this.dir + 1 + 8) % 8;
engine.showNotification(this.description);
} else if (action instanceof DecreaseMasterAreaWindowCount) {
this.dir = (this.dir - 1 + 8) % 8;
engine.showNotification(this.description);
} else {
action.executeWithoutLayoutOverride();
}
return true;
}
}

View File

@ -24,7 +24,7 @@ export interface WindowsLayout {
adjust?(area: Rect, tiles: Window[], basis: Window, delta: RectDelta): void;
apply(controller: Controller, tileables: Window[], area: Rect): void;
handleShortcut?(engine: Engine, input: Action, data?: any): boolean;
executeAction?(engine: Engine, action: Action): void;
toString(): string;
}

View File

@ -10,7 +10,15 @@ import { WindowState } from "../window";
import { KWinWindow } from "../../driver/window";
import { Action } from "../../controller/action";
import {
Action,
FocusBottomWindow,
FocusLeftWindow,
FocusNextWindow,
FocusPreviousWindow,
FocusRightWindow,
FocusUpperWindow,
} from "../../controller/action";
import Rect from "../../util/rect";
import Config from "../../config";
@ -60,26 +68,22 @@ export default class MonocleLayout implements WindowsLayout {
return this;
}
public handleShortcut(
engine: Engine,
input: Action,
_data?: string
): boolean {
switch (input) {
case Action.Up:
case Action.FocusUp:
case Action.Left:
case Action.FocusLeft:
engine.focusOrder(-1);
return true;
case Action.Down:
case Action.FocusDown:
case Action.Right:
case Action.FocusRight:
engine.focusOrder(1);
return true;
default:
return false;
public executeAction(engine: Engine, action: Action): void {
if (
action instanceof FocusUpperWindow ||
action instanceof FocusLeftWindow ||
action instanceof FocusPreviousWindow
) {
engine.focusOrder(-1);
} else if (
action instanceof FocusBottomWindow ||
action instanceof FocusRightWindow ||
action instanceof FocusNextWindow
) {
engine.focusOrder(1);
} else {
console.log("Executing from Monocle regular action!");
action.executeWithoutLayoutOverride();
}
}

View File

@ -8,7 +8,11 @@ import { WindowsLayout } from ".";
import Window from "../window";
import { WindowState } from "../window";
import { Action } from "../../controller/action";
import {
Action,
DecreaseMasterAreaWindowCount,
IncreaseMasterAreaWindowCount,
} from "../../controller/action";
import Rect from "../../util/rect";
import { Controller } from "../../controller";
@ -58,20 +62,16 @@ export default class SpreadLayout implements WindowsLayout {
return other;
}
public handleShortcut(_engine: Engine, input: Action): boolean {
switch (input) {
case Action.Decrease:
// TODO: define arbitrary constants
this.space = Math.max(0.04, this.space - 0.01);
break;
case Action.Increase:
// TODO: define arbitrary constants
this.space = Math.min(0.1, this.space + 0.01);
break;
default:
return false;
public executeAction(_engine: Engine, action: Action): void {
if (action instanceof DecreaseMasterAreaWindowCount) {
// TODO: define arbitrary constants
this.space = Math.max(0.04, this.space - 0.01);
} else if (action instanceof IncreaseMasterAreaWindowCount) {
// TODO: define arbitrary constants
this.space = Math.min(0.1, this.space + 0.01);
} else {
action.executeWithoutLayoutOverride();
}
return true;
}
public toString(): string {

View File

@ -8,7 +8,11 @@ import { WindowsLayout } from ".";
import Window from "../window";
import { WindowState } from "../window";
import { Action } from "../../controller/action";
import {
Action,
DecreaseMasterAreaWindowCount,
IncreaseMasterAreaWindowCount,
} from "../../controller/action";
import Rect from "../../util/rect";
import { Controller } from "../../controller";
@ -54,20 +58,16 @@ export default class StairLayout implements WindowsLayout {
return other;
}
public handleShortcut(_engine: Engine, input: Action): boolean {
switch (input) {
case Action.Decrease:
// TODO: define arbitrary constants
this.space = Math.max(16, this.space - 8);
break;
case Action.Increase:
// TODO: define arbitrary constants
this.space = Math.min(160, this.space + 8);
break;
default:
return false;
public executeAction(_engine: Engine, action: Action): void {
if (action instanceof DecreaseMasterAreaWindowCount) {
// TODO: define arbitrary constants
this.space = Math.max(16, this.space - 8);
} else if (action instanceof IncreaseMasterAreaWindowCount) {
// TODO: define arbitrary constants
this.space = Math.min(160, this.space + 8);
} else {
action.executeWithoutLayoutOverride();
}
return true;
}
public toString(): string {

View File

@ -9,7 +9,13 @@ import LayoutUtils from "./layout_utils";
import Window from "../window";
import { WindowState } from "../window";
import { Action } from "../../controller/action";
import {
Action,
DecreaseLayoutMasterAreaSize,
DecreaseMasterAreaWindowCount,
IncreaseLayoutMasterAreaSize,
IncreaseMasterAreaWindowCount,
} from "../../controller/action";
import { partitionArrayBySizes, clip, slide } from "../../util/func";
import Rect from "../../util/rect";
@ -197,34 +203,25 @@ export default class ThreeColumnLayout implements WindowsLayout {
return other;
}
public handleShortcut(
engine: Engine,
input: Action,
_data?: string
): boolean {
switch (input) {
case Action.Increase:
this.resizeMaster(engine, +1);
return true;
case Action.Decrease:
this.resizeMaster(engine, -1);
return true;
case Action.Left:
this.masterRatio = clip(
slide(this.masterRatio, -0.05),
ThreeColumnLayout.MIN_MASTER_RATIO,
ThreeColumnLayout.MAX_MASTER_RATIO
);
return true;
case Action.Right:
this.masterRatio = clip(
slide(this.masterRatio, +0.05),
ThreeColumnLayout.MIN_MASTER_RATIO,
ThreeColumnLayout.MAX_MASTER_RATIO
);
return true;
default:
return false;
public executeAction(engine: Engine, action: Action): void {
if (action instanceof IncreaseMasterAreaWindowCount) {
this.resizeMaster(engine, +1);
} else if (action instanceof DecreaseMasterAreaWindowCount) {
this.resizeMaster(engine, -1);
} else if (action instanceof DecreaseLayoutMasterAreaSize) {
this.masterRatio = clip(
slide(this.masterRatio, -0.05),
ThreeColumnLayout.MIN_MASTER_RATIO,
ThreeColumnLayout.MAX_MASTER_RATIO
);
} else if (action instanceof IncreaseLayoutMasterAreaSize) {
this.masterRatio = clip(
slide(this.masterRatio, +0.05),
ThreeColumnLayout.MIN_MASTER_RATIO,
ThreeColumnLayout.MAX_MASTER_RATIO
);
} else {
action.executeWithoutLayoutOverride();
}
}

View File

@ -13,7 +13,15 @@ import {
import Window from "../window";
import { WindowState } from "../window";
import { Action } from "../../controller/action";
import {
Action,
DecreaseLayoutMasterAreaSize,
DecreaseMasterAreaWindowCount,
IncreaseLayoutMasterAreaSize,
IncreaseMasterAreaWindowCount,
Rotate,
RotatePart,
} from "../../controller/action";
import { clip, slide } from "../../util/func";
import Rect from "../../util/rect";
@ -96,45 +104,37 @@ export default class TileLayout implements WindowsLayout {
return other;
}
public handleShortcut(engine: Engine, input: Action): boolean {
switch (input) {
case Action.Left:
this.masterRatio = clip(
slide(this.masterRatio, -0.05),
TileLayout.MIN_MASTER_RATIO,
TileLayout.MAX_MASTER_RATIO
);
break;
case Action.Right:
this.masterRatio = clip(
slide(this.masterRatio, +0.05),
TileLayout.MIN_MASTER_RATIO,
TileLayout.MAX_MASTER_RATIO
);
break;
case Action.Increase:
// TODO: define arbitrary constant
if (this.numMaster < 10) {
this.numMaster += 1;
}
engine.showNotification(this.description);
break;
case Action.Decrease:
if (this.numMaster > 0) {
this.numMaster -= 1;
}
engine.showNotification(this.description);
break;
case Action.Rotate:
this.parts.rotate(90);
break;
case Action.RotatePart:
this.parts.inner.primary.rotate(90);
break;
default:
return false;
public executeAction(engine: Engine, action: Action): void {
if (action instanceof DecreaseLayoutMasterAreaSize) {
this.masterRatio = clip(
slide(this.masterRatio, -0.05),
TileLayout.MIN_MASTER_RATIO,
TileLayout.MAX_MASTER_RATIO
);
} else if (action instanceof IncreaseLayoutMasterAreaSize) {
this.masterRatio = clip(
slide(this.masterRatio, +0.05),
TileLayout.MIN_MASTER_RATIO,
TileLayout.MAX_MASTER_RATIO
);
} else if (action instanceof IncreaseMasterAreaWindowCount) {
// TODO: define arbitrary constant
if (this.numMaster < 10) {
this.numMaster += 1;
}
engine.showNotification(this.description);
} else if (action instanceof DecreaseMasterAreaWindowCount) {
if (this.numMaster > 0) {
this.numMaster -= 1;
}
engine.showNotification(this.description);
} else if (action instanceof Rotate) {
this.parts.rotate(90);
} else if (action instanceof RotatePart) {
this.parts.inner.primary.rotate(90);
} else {
action.executeWithoutLayoutOverride();
}
return true;
}
public toString(): string {