Compare commits

..

10 Commits

Author SHA1 Message Date
LGUG2Z
80dd07fcde chore(release): v0.1.6 2021-10-15 07:49:55 -07:00
LGUG2Z
09d1d69668 fix(wm): apply container padding in monocle mode
This commit ensures that any configured container padding is also
applied when monocle mode is enabled for containers in any/all layouts.

fix #49
2021-10-14 16:03:56 -07:00
LGUG2Z
786f5e846a feat(wm): add vertical & horizontal stack layouts
This commit ports the CenterMain, MainAndVertStack, and
MainAndHorizontalStack layouts from LeftWM to komorebi as
UltrawideVerticalStack, VerticalStack and HorizontalStack.

These layouts are fixed-size layouts, meaning that individual containers
cannot be resized. The VerticalStack and UltrawideVerticalStack layouts
support horizontal flipping, whereas the HorizontalStack layout supports
vertical flipping.

resolve #48
2021-10-14 11:28:44 -07:00
LGUG2Z
65bc1a966e feat(wm): add cmd to specify work area offsets
This commit adds a new komorebic command to specify offsets for work
areas to be applied across all monitors. The areas covered by these
offsets will be excluded from the tiling area, and can be used for
custom task bars, Rainmeter desktop widgets etc.

When setting an offset at the top, the same offset will need to be
applied to the bottom to ensure that the tiling area is not pushed off
of the screen, but this is not necessary when applying an offset to the
bottom as the top of the work area will never go lower than 0.

resolve #46
2021-10-14 11:08:25 -07:00
LGUG2Z
ddafe599a2 feat(wm): cycle through monitors and workspaces
This commit adds commands to navigate monitors and workspaces using
cycle directions.

resolve #47
2021-10-12 07:44:47 -07:00
LGUG2Z
7ed6df511f feat(wm): allow focusing and moving by cycle direction
This commit adds focusing and moving window containers using cycle
directions when the layout has not been flipped on any axis.

This naive implementation simply increments or decrements the index
number in the desired direction and does not accomodate for axis
flipping.

When the current index number is either at the beginning or the end of
the collection, further operations will loop around.

Ideally I would like an implementation which works coherently on any
LayoutFlip state, but this can be implemented at a later date if
specifically requested in the future.

re #47
2021-10-12 07:44:33 -07:00
dependabot[bot]
f9c4dbd447 chore(deps): bump windows from 0.21.0 to 0.21.1
Bumps [windows](https://github.com/microsoft/windows-rs) from 0.21.0 to 0.21.1.
- [Release notes](https://github.com/microsoft/windows-rs/releases)
- [Changelog](https://github.com/microsoft/windows-rs/blob/master/docs/changelog.md)
- [Commits](https://github.com/microsoft/windows-rs/commits)

---
updated-dependencies:
- dependency-name: windows
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2021-10-05 07:15:51 -07:00
dependabot[bot]
b344888b72 chore(deps): bump sysinfo from 0.20.3 to 0.20.4
Bumps [sysinfo](https://github.com/GuillaumeGomez/sysinfo) from 0.20.3 to 0.20.4.
- [Release notes](https://github.com/GuillaumeGomez/sysinfo/releases)
- [Changelog](https://github.com/GuillaumeGomez/sysinfo/blob/master/CHANGELOG.md)
- [Commits](https://github.com/GuillaumeGomez/sysinfo/commits)

---
updated-dependencies:
- dependency-name: sysinfo
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2021-10-05 07:15:41 -07:00
dependabot[bot]
a62ed682de chore(deps): bump dirs from 3.0.2 to 4.0.0
Bumps [dirs](https://github.com/soc/dirs-rs) from 3.0.2 to 4.0.0.
- [Release notes](https://github.com/soc/dirs-rs/releases)
- [Commits](https://github.com/soc/dirs-rs/commits)

---
updated-dependencies:
- dependency-name: dirs
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
2021-10-05 07:15:01 -07:00
LGUG2Z
94e9bb8e9e chore(deps): bump windows-rs, syn and instant 2021-09-25 09:13:43 -07:00
17 changed files with 515 additions and 90 deletions

107
Cargo.lock generated
View File

@@ -73,9 +73,9 @@ checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a"
[[package]]
name = "cc"
version = "1.0.70"
version = "1.0.71"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d26a6ce4b6a484fa3edb70f7efa6fc430fd2b87285fe8b84304fd0936faa0dc0"
checksum = "79c2681d6594606957bbb8631c4b90a7fcaaa72cdb714743a437b156d6a7eedd"
[[package]]
name = "cfg-if"
@@ -199,9 +199,9 @@ checksum = "fb58b6451e8c2a812ad979ed1d83378caa5e927eef2622017a45f251457c2c9d"
[[package]]
name = "core-foundation-sys"
version = "0.8.2"
version = "0.8.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ea221b5284a47e40033bf9b66f35f984ec0ea2931eb03505246cd27a963f981b"
checksum = "5827cebf4670468b8772dd191856768aedcb1b0278a04f989f7766351917b9dc"
[[package]]
name = "crossbeam-channel"
@@ -249,9 +249,9 @@ dependencies = [
[[package]]
name = "ctrlc"
version = "3.2.0"
version = "3.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "377c9b002a72a0b2c1a18c62e2f3864bdfea4a015e3683a96e24aa45dd6c02d1"
checksum = "a19c6cedffdc8c03a3346d723eb20bd85a13362bb96dc2ac000842c6381ec7bf"
dependencies = [
"nix",
"winapi 0.3.9",
@@ -268,9 +268,9 @@ dependencies = [
[[package]]
name = "dirs"
version = "3.0.2"
version = "4.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "30baa043103c9d0c2a57cf537cc2f35623889dc0d405e6c3cccfadbc81c71309"
checksum = "ca3aa72a6f96ea37bbc5aa912f6788242832f75369bdfdadcb0e38423f100059"
dependencies = [
"dirs-sys",
]
@@ -425,9 +425,9 @@ dependencies = [
[[package]]
name = "hotwatch"
version = "0.4.5"
version = "0.4.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d61ee702e77f237b41761361a82e5c4bf6277dbb4bc8b6b7d745cb249cc82b31"
checksum = "39301670a6f5798b75f36a1b149a379a50df5aa7c71be50f4b41ec6eab445cb8"
dependencies = [
"log",
"notify",
@@ -471,9 +471,9 @@ dependencies = [
[[package]]
name = "instant"
version = "0.1.10"
version = "0.1.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bee0328b1209d157ef001c94dd85b4f8f64139adb0eac2659f4b08382b2f474d"
checksum = "716d3d89f35ac6a34fd0eed635395f4c3b76fa889338a4632e5231a8684216bd"
dependencies = [
"cfg-if 1.0.0",
]
@@ -505,7 +505,7 @@ dependencies = [
[[package]]
name = "komorebi"
version = "0.1.5"
version = "0.1.6"
dependencies = [
"bindings",
"bitflags",
@@ -537,7 +537,7 @@ dependencies = [
[[package]]
name = "komorebi-core"
version = "0.1.5"
version = "0.1.6"
dependencies = [
"bindings",
"clap",
@@ -549,7 +549,7 @@ dependencies = [
[[package]]
name = "komorebic"
version = "0.1.5"
version = "0.1.6"
dependencies = [
"bindings",
"clap",
@@ -580,9 +580,9 @@ checksum = "830d08ce1d1d941e6b30645f1a0eb5643013d835ce3779a5fc208261dbe10f55"
[[package]]
name = "libc"
version = "0.2.102"
version = "0.2.103"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a2a5ac8f984bfcf3a823267e5fde638acc3325f6496633a5da6bb6eb2171e103"
checksum = "dd8f7255a17a627354f321ef0055d63b898c6fb27eff628af4d1b66b7331edf6"
[[package]]
name = "lock_api"
@@ -701,9 +701,9 @@ dependencies = [
[[package]]
name = "nix"
version = "0.22.0"
version = "0.23.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cf1e25ee6b412c2a1e3fcb6a4499a5c1bfe7f43e014bdce9a6b6666e5aa2d187"
checksum = "f305c2c2e4c39a82f7bf0bf65fb557f9070ce06781d4f2454295cc34b1c43188"
dependencies = [
"bitflags",
"cc",
@@ -853,9 +853,9 @@ checksum = "36d62894f5590e88d99d0d82918742ba8e5bff1985af15d4906b6a65f635adb2"
[[package]]
name = "ppv-lite86"
version = "0.2.10"
version = "0.2.14"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ac74c624d6b2d21f425f752262f42188365d7b8ff1aff74c82e45136510a4857"
checksum = "c3ca011bd0129ff4ae15cd04c4eef202cadf6c51c21e47aba319b4e0501db741"
[[package]]
name = "proc-macro-error"
@@ -883,18 +883,18 @@ dependencies = [
[[package]]
name = "proc-macro2"
version = "1.0.29"
version = "1.0.30"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b9f5105d4fdaab20335ca9565e106a5d9b82b6219b5ba735731124ac6711d23d"
checksum = "edc3358ebc67bc8b7fa0c007f945b0b18226f78437d61bec735a9eb96b61ee70"
dependencies = [
"unicode-xid",
]
[[package]]
name = "quote"
version = "1.0.9"
version = "1.0.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c3d0b9745dc2debf507c8422de05d7226cc1f0644216dfdfead988f9b1ab32a7"
checksum = "38bc8cc6a5f2e3655e0899c1b848643b2562f853f114bfec7be120678e3ace05"
dependencies = [
"proc-macro2",
]
@@ -1113,24 +1113,24 @@ dependencies = [
[[package]]
name = "sharded-slab"
version = "0.1.3"
version = "0.1.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "740223c51853f3145fe7c90360d2d4232f2b62e3449489c207eccde818979982"
checksum = "900fba806f70c630b0a382d0d825e17a0f19fcd059a2ade1ff237bcddf446b31"
dependencies = [
"lazy_static",
]
[[package]]
name = "slab"
version = "0.4.4"
version = "0.4.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c307a32c1c5c437f38c7fd45d753050587732ba8628319fbdf12a7e289ccc590"
checksum = "9def91fd1e018fe007022791f865d0ccc9b3a0d5001e01aabb8b40e46000afb5"
[[package]]
name = "smallvec"
version = "1.6.1"
version = "1.7.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fe0f37c9e8f3c5a4a66ad655a93c74daac4ad00c441533bf5c6e7990bb42604e"
checksum = "1ecab6c735a6bb4139c0caafd0cc3635748bbb3acf4550e8138122099251f309"
[[package]]
name = "strsim"
@@ -1161,9 +1161,9 @@ dependencies = [
[[package]]
name = "syn"
version = "1.0.76"
version = "1.0.80"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c6f107db402c2c2055242dbf4d2af0e69197202e9faacbef9571bbe47f5a1b84"
checksum = "d010a1623fbd906d51d650a9916aaefc05ffa0e4053ff7fe601167f3e715d194"
dependencies = [
"proc-macro2",
"quote",
@@ -1172,9 +1172,9 @@ dependencies = [
[[package]]
name = "sysinfo"
version = "0.20.3"
version = "0.20.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "92d77883450d697c0010e60db3d940ed130b0ed81d27485edee981621b434e52"
checksum = "e223c65cd36b485a34c2ce6e38efa40777d31c4166d9076030c74cdcf971679f"
dependencies = [
"cfg-if 1.0.0",
"core-foundation-sys",
@@ -1245,9 +1245,9 @@ dependencies = [
[[package]]
name = "tracing"
version = "0.1.28"
version = "0.1.29"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "84f96e095c0c82419687c20ddf5cb3eadb61f4e1405923c9dc8e53a1adacbda8"
checksum = "375a639232caf30edfc78e8d89b2d4c375515393e7af7e16f01cd96917fb2105"
dependencies = [
"cfg-if 1.0.0",
"pin-project-lite",
@@ -1268,9 +1268,9 @@ dependencies = [
[[package]]
name = "tracing-attributes"
version = "0.1.16"
version = "0.1.18"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "98863d0dd09fa59a1b79c6750ad80dbda6b75f4e71c437a6a1a8cb91a8bcbd77"
checksum = "f4f480b8f81512e825f337ad51e94c1eb5d3bbdf2b363dcd01e2b19a9ffe3f8e"
dependencies = [
"proc-macro2",
"quote",
@@ -1279,9 +1279,9 @@ dependencies = [
[[package]]
name = "tracing-core"
version = "0.1.20"
version = "0.1.21"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "46125608c26121c81b0c6d693eab5a420e416da7e43c426d2e8f7df8da8a3acf"
checksum = "1f4ed65637b8390770814083d20756f87bfa2c21bf2f110babdc5438351746e4"
dependencies = [
"lazy_static",
]
@@ -1319,9 +1319,9 @@ dependencies = [
[[package]]
name = "tracing-subscriber"
version = "0.2.24"
version = "0.2.25"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fdd0568dbfe3baf7048b7908d2b32bca0d81cd56bec6d2a8f894b01d74f86be3"
checksum = "0e0d2eaa99c3c2e41547cfa109e910a68ea03823cccad4a0525dcbc9b01e8c71"
dependencies = [
"ansi_term",
"chrono",
@@ -1452,20 +1452,21 @@ checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"
[[package]]
name = "windows"
version = "0.20.1"
version = "0.21.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d7524f6f9074f6326a1c167cd3dc2ed4e6916648a1a55116d029620af9b65fb1"
checksum = "b8f5f8d2ea79bf690bbee453fd4a1516ae426e5d5c7215d96cc0c3dc134fc4a0"
dependencies = [
"const-sha1",
"windows_gen",
"windows_macros",
"windows_reader",
]
[[package]]
name = "windows_gen"
version = "0.20.1"
version = "0.21.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4be44a189bde96fc0e0cdd5b152b2d21c635c0c94c7d256aab4425477b2a2f37"
checksum = "7e6994f42f8481387778cc608407d6703410672d57f32a66009419d7a18aa912"
dependencies = [
"windows_quote",
"windows_reader",
@@ -1473,9 +1474,9 @@ dependencies = [
[[package]]
name = "windows_macros"
version = "0.20.1"
version = "0.21.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cc1d78ce8a43d45b8da282383a2cb2ffcd5587cc3a9c341125d3181d2b701ede"
checksum = "81cc2357b1b03c19f056cb0e6d06011f80f54beadb4e36aee2ca98493c7cfc3c"
dependencies = [
"syn",
"windows_gen",
@@ -1485,15 +1486,15 @@ dependencies = [
[[package]]
name = "windows_quote"
version = "0.20.1"
version = "0.21.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "51fa2185b18a6164a3fa3ea2b6c92ebc1b60f532ae5a85c57408ba6a5a064913"
checksum = "7cf987b5288c15e1997226848f78f3ed3ef8b78dcfd71a201c8c8684163a7e4d"
[[package]]
name = "windows_reader"
version = "0.20.1"
version = "0.21.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3daa5bd758f2f8f20cd93a79aedca20759779f43785fc77b08a4e8e1e5876bbb"
checksum = "237b53e8b40766ea7db5da0d8c6c1442d21d0429f0ee7500d7b5688967bd9d7b"
[[package]]
name = "winput"

View File

@@ -18,6 +18,10 @@ Translations of this document can be found in the project wiki:
- [komorebi 中文用户指南](https://github.com/LGUG2Z/komorebi/wiki/README-zh) (by [@crosstyan](https://github.com/crosstyan))
There is a [Discord server](https://discord.gg/vzBmPm6RkQ) available for _komorebi_-related discussion, help,
troubleshooting etc. If you have any specific feature requests or bugs to report, please create an issue in this
repository.
## Description
_komorebi_ only responds to [WinEvents](https://docs.microsoft.com/en-us/windows/win32/winauto/event-constants) and the
@@ -226,6 +230,8 @@ save Save the current resize layout dimensions to a fil
load Load the resize layout dimensions from a file
focus Change focus to the window in the specified direction
move Move the focused window in the specified direction
cycle-focus Change focus to the window in the specified cycle direction
cycle-move Move the focused window in the specified cycle direction
stack Stack the focused window in the specified direction
resize Resize the focused window in the specified direction
unstack Unstack the focused window
@@ -236,8 +242,11 @@ send-to-monitor Send the focused window to the specified monitor
send-to-workspace Send the focused window to the specified workspace
focus-monitor Focus the specified monitor
focus-workspace Focus the specified workspace on the focused monitor
cycle-monitor Focus the monitor in the given cycle direction
cycle-workspace Focus the workspace in the given cycle direction
new-workspace Create and append a new workspace on the focused monitor
invisible-borders Set the invisible border dimensions around each window
work-area-offset Set offsets to exclude parts of the work area from tiling
adjust-container-padding Adjust container padding on the focused workspace
adjust-workspace-padding Adjust workspace padding on the focused workspace
change-layout Set the layout on the focused workspace
@@ -301,14 +310,19 @@ used [is available here](komorebi.sample.with.lib.ahk).
- [x] Mouse drag to swap window container position
- [x] Mouse drag to resize window container
- [x] Configurable workspace and container gaps
- [x] BSP tree layout
- [x] BSP tree layout (`bsp`)
- [x] Flip BSP tree layout horizontally or vertically
- [x] Equal-width, max-height column layout
- [x] Equal-width, max-height column layout (`columns`)
- [x] Equal-height, max-width row layout (`rows`)
- [x] Main half-height window with vertical stack layout (`horizontal-stack`)
- [x] Main half-width window with horizontal stack layout (`vertical-stack`)
- [x] 2x Main window (half and quarter-width) with horizontal stack layout (`ultrawide-vertical-stack`)
- [x] Floating rules based on exe name, window title and class
- [x] Workspace rules based on exe name and window class
- [x] Additional manage rules based on exe name and window class
- [x] Identify applications which overflow their borders by exe name and class
- [x] Identify 'close/minimize to tray' applications by exe name and class
- [x] Configure work area offsets to preserve space for custom taskbars
- [x] Configure and compensate for the size of Windows 10's invisible borders
- [x] Toggle floating windows
- [x] Toggle monocle window

View File

@@ -7,7 +7,7 @@ edition = "2018"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
windows = "0.20"
windows = "0.21"
[build-dependencies]
windows = "0.20"
windows = "0.21"

View File

@@ -1,6 +1,6 @@
[package]
name = "komorebi-core"
version = "0.1.5"
version = "0.1.6"
edition = "2018"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html

View File

@@ -1,3 +1,5 @@
use std::num::NonZeroUsize;
use clap::ArgEnum;
use serde::Deserialize;
use serde::Serialize;
@@ -13,17 +15,17 @@ pub enum CycleDirection {
impl CycleDirection {
#[must_use]
pub const fn next_idx(&self, idx: usize, len: usize) -> usize {
pub const fn next_idx(&self, idx: usize, len: NonZeroUsize) -> usize {
match self {
CycleDirection::Previous => {
Self::Previous => {
if idx == 0 {
len - 1
len.get() - 1
} else {
idx - 1
}
}
CycleDirection::Next => {
if idx == len - 1 {
Self::Next => {
if idx == len.get() - 1 {
0
} else {
idx + 1

View File

@@ -16,6 +16,9 @@ pub enum Layout {
BSP,
Columns,
Rows,
VerticalStack,
HorizontalStack,
UltrawideVerticalStack,
}
#[derive(Clone, Copy, Debug, Serialize, Deserialize, Display, EnumString, ArgEnum)]
@@ -131,7 +134,11 @@ impl Layout {
}
#[must_use]
#[allow(clippy::cast_possible_truncation, clippy::cast_possible_wrap)]
#[allow(
clippy::cast_possible_truncation,
clippy::cast_possible_wrap,
clippy::too_many_lines
)]
pub fn calculate(
&self,
area: &Rect,
@@ -185,6 +192,176 @@ impl Layout {
layouts
}
Layout::VerticalStack => {
let mut layouts: Vec<Rect> = vec![];
layouts.resize(len, Rect::default());
let primary_right = match len {
1 => area.right,
_ => area.right / 2,
};
let mut main_left = area.left;
let mut stack_left = area.left + primary_right;
match layout_flip {
Some(Flip::Horizontal | Flip::HorizontalAndVertical) if len > 1 => {
main_left = main_left + area.right - primary_right;
stack_left = area.left;
}
_ => {}
}
let mut iter = layouts.iter_mut();
{
if let Some(first) = iter.next() {
first.left = main_left;
first.top = area.top;
first.right = primary_right;
first.bottom = area.bottom;
}
}
let bottom = area.bottom / (len - 1) as i32;
let mut top = 0;
for next in iter {
next.left = stack_left;
next.top = area.top + top;
next.right = area.right - primary_right;
next.bottom = bottom;
top += bottom;
}
layouts
}
Layout::HorizontalStack => {
let mut layouts: Vec<Rect> = vec![];
layouts.resize(len, Rect::default());
let bottom = match len {
1 => area.bottom,
_ => area.bottom / 2,
};
let mut main_top = area.top;
let mut stack_top = area.top + bottom;
match layout_flip {
Some(Flip::Vertical | Flip::HorizontalAndVertical) if len > 1 => {
main_top = main_top + area.bottom - bottom;
stack_top = area.top;
}
_ => {}
}
let mut iter = layouts.iter_mut();
{
if let Some(first) = iter.next() {
first.left = area.left;
first.top = main_top;
first.right = area.right;
first.bottom = bottom;
}
}
let right = area.right / (len - 1) as i32;
let mut left = 0;
for next in iter {
next.left = area.left + left;
next.top = stack_top;
next.right = right;
next.bottom = area.bottom - bottom;
left += right;
}
layouts
}
Layout::UltrawideVerticalStack => {
let mut layouts: Vec<Rect> = vec![];
layouts.resize(len, Rect::default());
let primary_right = match len {
1 => area.right,
_ => area.right / 2,
};
let secondary_right = match len {
1 => 0,
2 => area.right - primary_right,
_ => (area.right - primary_right) / 2,
};
let (primary_left, secondary_left, stack_left) = match len {
1 => (area.left, 0, 0),
2 => {
let mut primary = area.left + secondary_right;
let mut secondary = area.left;
match layout_flip {
Some(Flip::Horizontal | Flip::HorizontalAndVertical) if len > 1 => {
primary = area.left;
secondary = area.left + primary_right;
}
_ => {}
}
(primary, secondary, 0)
}
_ => {
let primary = area.left + secondary_right;
let mut secondary = area.left;
let mut stack = area.left + primary_right + secondary_right;
match layout_flip {
Some(Flip::Horizontal | Flip::HorizontalAndVertical) if len > 1 => {
secondary = area.left + primary_right + secondary_right;
stack = area.left;
}
_ => {}
}
(primary, secondary, stack)
}
};
let mut iter = layouts.iter_mut();
{
if let Some(first) = iter.next() {
first.left = primary_left;
first.top = area.top;
first.right = primary_right;
first.bottom = area.bottom;
}
}
{
if let Some(second) = iter.next() {
second.left = secondary_left;
second.top = area.top;
second.right = secondary_right;
second.bottom = area.bottom;
}
}
if len > 2 {
let height = area.bottom / (len - 2) as i32;
let mut y = 0;
for next in iter {
next.left = stack_left;
next.top = area.top + y;
next.right = secondary_right;
next.bottom = height;
y += height;
}
}
layouts
}
};
dimensions

View File

@@ -27,6 +27,8 @@ pub enum SocketMessage {
// Window / Container Commands
FocusWindow(OperationDirection),
MoveWindow(OperationDirection),
CycleFocusWindow(CycleDirection),
CycleMoveWindow(CycleDirection),
StackWindow(OperationDirection),
ResizeWindow(OperationDirection, Sizing),
UnstackWindow,
@@ -57,6 +59,8 @@ pub enum SocketMessage {
QuickLoad,
Save(PathBuf),
Load(PathBuf),
CycleFocusMonitor(CycleDirection),
CycleFocusWorkspace(CycleDirection),
FocusMonitorNumber(usize),
FocusWorkspaceNumber(usize),
ContainerPadding(usize, usize, i32),
@@ -68,6 +72,7 @@ pub enum SocketMessage {
ReloadConfiguration,
WatchConfiguration(bool),
InvisibleBorders(Rect),
WorkAreaOffset(Rect),
WorkspaceRule(ApplicationIdentifier, String, usize, usize),
FloatRule(ApplicationIdentifier, String),
ManageRule(ApplicationIdentifier, String),

View File

@@ -57,25 +57,39 @@ impl OperationDirection {
len: usize,
) -> bool {
match Self::flip_direction(self, layout_flip) {
OperationDirection::Up => match layout {
Self::Up => match layout {
Layout::BSP => len > 2 && idx != 0 && idx != 1,
Layout::Columns => false,
Layout::Rows => idx != 0,
Layout::Rows | Layout::HorizontalStack => idx != 0,
Layout::VerticalStack => idx != 0 && idx != 1,
Layout::UltrawideVerticalStack => idx > 2,
},
OperationDirection::Down => match layout {
Self::Down => match layout {
Layout::BSP => len > 2 && idx != len - 1 && idx % 2 != 0,
Layout::Columns => false,
Layout::Rows => idx != len - 1,
Layout::VerticalStack => idx != 0 && idx != len - 1,
Layout::HorizontalStack => idx == 0,
Layout::UltrawideVerticalStack => idx > 1 && idx != len - 1,
},
OperationDirection::Left => match layout {
Self::Left => match layout {
Layout::BSP => len > 1 && idx != 0,
Layout::Columns => idx != 0,
Layout::Columns | Layout::VerticalStack => idx != 0,
Layout::Rows => false,
Layout::HorizontalStack => idx != 0 && idx != 1,
Layout::UltrawideVerticalStack => len > 1 && idx != 1,
},
OperationDirection::Right => match layout {
Self::Right => match layout {
Layout::BSP => len > 1 && idx % 2 == 0 && idx != len - 1,
Layout::Columns => idx != len - 1,
Layout::Rows => false,
Layout::VerticalStack => idx == 0,
Layout::HorizontalStack => idx != 0 && idx != len - 1,
Layout::UltrawideVerticalStack => match len {
0 | 1 => false,
2 => idx != 0,
_ => idx < 2,
},
},
}
}
@@ -92,11 +106,16 @@ impl OperationDirection {
}
}
Layout::Columns => unreachable!(),
Layout::Rows => idx - 1,
Layout::Rows | Layout::VerticalStack | Layout::UltrawideVerticalStack => idx - 1,
Layout::HorizontalStack => 0,
},
Self::Down => match layout {
Layout::BSP | Layout::Rows => idx + 1,
Layout::BSP
| Layout::Rows
| Layout::VerticalStack
| Layout::UltrawideVerticalStack => idx + 1,
Layout::Columns => unreachable!(),
Layout::HorizontalStack => 1,
},
Self::Left => match layout {
Layout::BSP => {
@@ -106,12 +125,24 @@ impl OperationDirection {
idx - 1
}
}
Layout::Columns => idx - 1,
Layout::Columns | Layout::HorizontalStack => idx - 1,
Layout::Rows => unreachable!(),
Layout::VerticalStack => 0,
Layout::UltrawideVerticalStack => match idx {
0 => 1,
1 => unreachable!(),
_ => 0,
},
},
Self::Right => match layout {
Layout::BSP | Layout::Columns => idx + 1,
Layout::BSP | Layout::Columns | Layout::HorizontalStack => idx + 1,
Layout::Rows => unreachable!(),
Layout::VerticalStack => 1,
Layout::UltrawideVerticalStack => match idx {
1 => 0,
0 => 2,
_ => unreachable!(),
},
},
}
}

View File

@@ -1,6 +1,6 @@
[package]
name = "komorebi"
version = "0.1.5"
version = "0.1.6"
authors = ["Jade Iqbal <jadeiqbal@fastmail.com>"]
description = "A tiling window manager for Windows"
categories = ["tiling-window-manager", "windows"]
@@ -20,7 +20,7 @@ color-eyre = "0.5"
crossbeam-channel = "0.5"
crossbeam-utils = "0.8"
ctrlc = "3"
dirs = "3"
dirs = "4"
getset = "0.1"
hotwatch = "0.4"
lazy_static = "1"

View File

@@ -146,12 +146,16 @@ impl Monitor {
self.workspaces().len()
}
pub fn update_focused_workspace(&mut self, invisible_borders: &Rect) -> Result<()> {
pub fn update_focused_workspace(
&mut self,
offset: Option<Rect>,
invisible_borders: &Rect,
) -> Result<()> {
let work_area = *self.work_area_size();
self.focused_workspace_mut()
.ok_or_else(|| anyhow!("there is no workspace"))?
.update(&work_area, invisible_borders)?;
.update(&work_area, offset, invisible_borders)?;
Ok(())
}

View File

@@ -3,6 +3,7 @@ use std::fs::OpenOptions;
use std::io::BufRead;
use std::io::BufReader;
use std::io::Write;
use std::num::NonZeroUsize;
use std::str::FromStr;
use std::sync::atomic::Ordering;
use std::sync::Arc;
@@ -66,6 +67,12 @@ impl WindowManager {
SocketMessage::MoveWindow(direction) => {
self.move_container_in_direction(direction)?;
}
SocketMessage::CycleFocusWindow(direction) => {
self.focus_container_in_cycle_direction(direction)?;
}
SocketMessage::CycleMoveWindow(direction) => {
self.move_container_in_cycle_direction(direction)?;
}
SocketMessage::StackWindow(direction) => self.add_window_to_container(direction)?,
SocketMessage::UnstackWindow => self.remove_window_from_container()?,
SocketMessage::CycleStack(direction) => {
@@ -130,6 +137,16 @@ impl WindowManager {
SocketMessage::ToggleTiling => {
self.toggle_tiling()?;
}
SocketMessage::CycleFocusMonitor(direction) => {
let monitor_idx = direction.next_idx(
self.focused_monitor_idx(),
NonZeroUsize::new(self.monitors().len())
.ok_or_else(|| anyhow!("there must be at least one monitor"))?,
);
self.focus_monitor(monitor_idx)?;
self.update_focused_workspace(true)?;
}
SocketMessage::FocusMonitorNumber(monitor_idx) => {
self.focus_monitor(monitor_idx)?;
self.update_focused_workspace(true)?;
@@ -143,6 +160,31 @@ impl WindowManager {
SocketMessage::WorkspaceLayout(monitor_idx, workspace_idx, layout) => {
self.set_workspace_layout(monitor_idx, workspace_idx, layout)?;
}
SocketMessage::CycleFocusWorkspace(direction) => {
// This is to ensure that even on an empty workspace on a secondary monitor, the
// secondary monitor where the cursor is focused will be used as the target for
// the workspace switch op
let monitor_idx = self.monitor_idx_from_current_pos().ok_or_else(|| {
anyhow!("there is no monitor associated with the current cursor position")
})?;
self.focus_monitor(monitor_idx)?;
let focused_monitor = self
.focused_monitor()
.ok_or_else(|| anyhow!("there is no monitor"))?;
let focused_workspace_idx = focused_monitor.focused_workspace_idx();
let workspaces = focused_monitor.workspaces().len();
let workspace_idx = direction.next_idx(
focused_workspace_idx,
NonZeroUsize::new(workspaces)
.ok_or_else(|| anyhow!("there must be at least one workspace"))?,
);
self.focus_workspace(workspace_idx)?;
}
SocketMessage::FocusWorkspaceNumber(workspace_idx) => {
// This is to ensure that even on an empty workspace on a secondary monitor, the
// secondary monitor where the cursor is focused will be used as the target for
@@ -329,6 +371,10 @@ impl WindowManager {
self.invisible_borders = rect;
self.retile_all()?;
}
SocketMessage::WorkAreaOffset(rect) => {
self.work_area_offset = Option::from(rect);
self.retile_all()?;
}
SocketMessage::QuickSave => {
let workspace = self.focused_workspace()?;
let resize = workspace.resize_dimensions();

View File

@@ -66,13 +66,14 @@ impl WindowManager {
}
let invisible_borders = self.invisible_borders;
let offset = self.work_area_offset;
for (i, monitor) in self.monitors_mut().iter_mut().enumerate() {
let work_area = *monitor.work_area_size();
for (j, workspace) in monitor.workspaces_mut().iter_mut().enumerate() {
let reaped_orphans = workspace.reap_orphans()?;
if reaped_orphans.0 > 0 || reaped_orphans.1 > 0 {
workspace.update(&work_area, &invisible_borders)?;
workspace.update(&work_area, offset, &invisible_borders)?;
tracing::info!(
"reaped {} orphan window(s) and {} orphaned container(s) on monitor: {}, workspace: {}",
reaped_orphans.0,

View File

@@ -45,6 +45,7 @@ pub struct WindowManager {
pub command_listener: UnixListener,
pub is_paused: bool,
pub invisible_borders: Rect,
pub work_area_offset: Option<Rect>,
pub focus_follows_mouse: Option<FocusFollowsMouseImplementation>,
pub hotwatch: Hotwatch,
pub virtual_desktop_id: Option<usize>,
@@ -56,6 +57,7 @@ pub struct State {
pub monitors: Ring<Monitor>,
pub is_paused: bool,
pub invisible_borders: Rect,
pub work_area_offset: Option<Rect>,
pub focus_follows_mouse: Option<FocusFollowsMouseImplementation>,
pub has_pending_raise_op: bool,
pub float_identifiers: Vec<String>,
@@ -72,6 +74,7 @@ impl From<&mut WindowManager> for State {
monitors: wm.monitors.clone(),
is_paused: wm.is_paused,
invisible_borders: wm.invisible_borders,
work_area_offset: wm.work_area_offset,
focus_follows_mouse: wm.focus_follows_mouse.clone(),
has_pending_raise_op: wm.has_pending_raise_op,
float_identifiers: FLOAT_IDENTIFIERS.lock().clone(),
@@ -143,6 +146,7 @@ impl WindowManager {
right: 14,
bottom: 7,
},
work_area_offset: None,
focus_follows_mouse: None,
hotwatch: Hotwatch::new()?,
virtual_desktop_id,
@@ -265,6 +269,7 @@ impl WindowManager {
self.monitors_mut().retain(|m| !invalid.contains(&m.id()));
let invisible_borders = self.invisible_borders;
let offset = self.work_area_offset;
for monitor in self.monitors_mut() {
let mut should_update = false;
@@ -293,7 +298,7 @@ impl WindowManager {
}
if should_update {
monitor.update_focused_workspace(&invisible_borders)?;
monitor.update_focused_workspace(offset, &invisible_borders)?;
}
}
@@ -423,6 +428,8 @@ impl WindowManager {
#[tracing::instrument(skip(self))]
pub fn retile_all(&mut self) -> Result<()> {
let invisible_borders = self.invisible_borders;
let offset = self.work_area_offset;
for monitor in self.monitors_mut() {
let work_area = *monitor.work_area_size();
let workspace = monitor
@@ -434,7 +441,7 @@ impl WindowManager {
*resize = None;
}
workspace.update(&work_area, &invisible_borders)?;
workspace.update(&work_area, offset, &invisible_borders)?;
}
Ok(())
@@ -534,10 +541,11 @@ impl WindowManager {
tracing::info!("updating");
let invisible_borders = self.invisible_borders;
let offset = self.work_area_offset;
self.focused_monitor_mut()
.ok_or_else(|| anyhow!("there is no monitor"))?
.update_focused_workspace(&invisible_borders)?;
.update_focused_workspace(offset, &invisible_borders)?;
if mouse_follows_focus {
if let Some(window) = self.focused_workspace()?.maximized_window() {
@@ -660,6 +668,7 @@ impl WindowManager {
tracing::info!("moving container");
let invisible_borders = self.invisible_borders;
let offset = self.work_area_offset;
let monitor = self
.focused_monitor_mut()
@@ -685,7 +694,7 @@ impl WindowManager {
target_monitor.add_container(container)?;
target_monitor.load_focused_workspace()?;
target_monitor.update_focused_workspace(&invisible_borders)?;
target_monitor.update_focused_workspace(offset, &invisible_borders)?;
if follow {
self.focus_monitor(idx)?;
@@ -739,18 +748,52 @@ impl WindowManager {
self.update_focused_workspace(true)
}
#[tracing::instrument(skip(self))]
pub fn focus_container_in_cycle_direction(&mut self, direction: CycleDirection) -> Result<()> {
tracing::info!("focusing container");
let workspace = self.focused_workspace_mut()?;
let new_idx = workspace
.new_idx_for_cycle_direction(direction)
.ok_or_else(|| anyhow!("this is not a valid direction from the current position"))?;
workspace.focus_container(new_idx);
self.focused_window_mut()?.focus()?;
Ok(())
}
#[tracing::instrument(skip(self))]
pub fn move_container_in_cycle_direction(&mut self, direction: CycleDirection) -> Result<()> {
tracing::info!("moving container");
let workspace = self.focused_workspace_mut()?;
let current_idx = workspace.focused_container_idx();
let new_idx = workspace
.new_idx_for_cycle_direction(direction)
.ok_or_else(|| anyhow!("this is not a valid direction from the current position"))?;
workspace.swap_containers(current_idx, new_idx);
workspace.focus_container(new_idx);
self.update_focused_workspace(true)
}
#[tracing::instrument(skip(self))]
pub fn cycle_container_window_in_direction(&mut self, direction: CycleDirection) -> Result<()> {
tracing::info!("cycling container windows");
let container = self.focused_container_mut()?;
if container.windows().len() == 1 {
let len = NonZeroUsize::new(container.windows().len())
.ok_or_else(|| anyhow!("there must be at least one window in a container"))?;
if len.get() == 1 {
return Err(anyhow!("there is only one window in this container"));
}
let current_idx = container.focused_window_idx();
let next_idx = direction.next_idx(current_idx, container.windows().len());
let next_idx = direction.next_idx(current_idx, len);
container.focus_window(next_idx);
container.load_focused_window();
@@ -1041,6 +1084,7 @@ impl WindowManager {
tracing::info!("setting workspace layout");
let invisible_borders = self.invisible_borders;
let offset = self.work_area_offset;
let focused_monitor_idx = self.focused_monitor_idx();
let monitor = self
@@ -1060,7 +1104,7 @@ impl WindowManager {
// If this is the focused workspace on a non-focused screen, let's update it
if focused_monitor_idx != monitor_idx && focused_workspace_idx == workspace_idx {
workspace.update(&work_area, &invisible_borders)?;
workspace.update(&work_area, offset, &invisible_borders)?;
Ok(())
} else {
Ok(self.update_focused_workspace(false)?)

View File

@@ -9,6 +9,7 @@ use getset::MutGetters;
use getset::Setters;
use serde::Serialize;
use komorebi_core::CycleDirection;
use komorebi_core::Flip;
use komorebi_core::Layout;
use komorebi_core::OperationDirection;
@@ -137,8 +138,26 @@ impl Workspace {
Ok(())
}
pub fn update(&mut self, work_area: &Rect, invisible_borders: &Rect) -> Result<()> {
let mut adjusted_work_area = *work_area;
pub fn update(
&mut self,
work_area: &Rect,
offset: Option<Rect>,
invisible_borders: &Rect,
) -> Result<()> {
let container_padding = self.container_padding();
let mut adjusted_work_area = offset.map_or_else(
|| *work_area,
|offset| {
let mut with_offset = *work_area;
with_offset.left += offset.left;
with_offset.top += offset.top;
with_offset.right -= offset.right;
with_offset.bottom -= offset.bottom;
with_offset
},
);
adjusted_work_area.add_padding(self.workspace_padding());
self.enforce_resize_constraints();
@@ -146,6 +165,7 @@ impl Workspace {
if *self.tile() {
if let Some(container) = self.monocle_container_mut() {
if let Some(window) = container.focused_window_mut() {
adjusted_work_area.add_padding(container_padding);
window.set_position(&adjusted_work_area, invisible_borders, true)?;
};
} else if let Some(window) = self.maximized_window_mut() {
@@ -457,6 +477,12 @@ impl Workspace {
None
}
}
pub fn new_idx_for_cycle_direction(&self, direction: CycleDirection) -> Option<usize> {
Option::from(direction.next_idx(
self.focused_container_idx(),
NonZeroUsize::new(self.containers().len())?,
))
}
pub fn move_window_to_container(&mut self, target_container_idx: usize) -> Result<()> {
let focused_idx = self.focused_container_idx();

View File

@@ -44,6 +44,14 @@ Move(operation_direction) {
Run, komorebic.exe move %operation_direction%, , Hide
}
CycleFocus(cycle_direction) {
Run, komorebic.exe cycle-focus %cycle_direction%, , Hide
}
CycleMove(cycle_direction) {
Run, komorebic.exe cycle-move %cycle_direction%, , Hide
}
Stack(operation_direction) {
Run, komorebic.exe stack %operation_direction%, , Hide
}
@@ -84,6 +92,14 @@ FocusWorkspace(target) {
Run, komorebic.exe focus-workspace %target%, , Hide
}
CycleMonitor(cycle_direction) {
Run, komorebic.exe cycle-monitor %cycle_direction%, , Hide
}
CycleWorkspace(cycle_direction) {
Run, komorebic.exe cycle-workspace %cycle_direction%, , Hide
}
NewWorkspace() {
Run, komorebic.exe new-workspace, , Hide
}
@@ -92,6 +108,10 @@ InvisibleBorders(left, top, right, bottom) {
Run, komorebic.exe invisible-borders %left% %top% %right% %bottom%, , Hide
}
WorkAreaOffset(left, top, right, bottom) {
Run, komorebic.exe work-area-offset %left% %top% %right% %bottom%, , Hide
}
AdjustContainerPadding(sizing, adjustment) {
Run, komorebic.exe adjust-container-padding %sizing% %adjustment%, , Hide
}

View File

@@ -1,6 +1,6 @@
[package]
name = "komorebic"
version = "0.1.5"
version = "0.1.6"
authors = ["Jade Iqbal <jadeiqbal@fastmail.com>"]
description = "The command-line interface for Komorebi, a tiling window manager for Windows"
categories = ["cli", "tiling-window-manager", "windows"]
@@ -17,7 +17,7 @@ komorebi-core = { path = "../komorebi-core" }
clap = "3.0.0-beta.4"
color-eyre = "0.5"
dirs = "3"
dirs = "4"
fs-tail = "0.1"
heck = "0.3"
paste = "1"

View File

@@ -79,6 +79,10 @@ macro_rules! gen_enum_subcommand_args {
gen_enum_subcommand_args! {
Focus: OperationDirection,
Move: OperationDirection,
CycleFocus: CycleDirection,
CycleMove: CycleDirection,
CycleMonitor: CycleDirection,
CycleWorkspace: CycleDirection,
Stack: OperationDirection,
CycleStack: CycleDirection,
FlipLayout: Flip,
@@ -163,6 +167,18 @@ struct InvisibleBorders {
bottom: i32,
}
#[derive(Clap, AhkFunction)]
struct WorkAreaOffset {
/// Size of the left work area offset (set right to left * 2 to maintain right padding)
left: i32,
/// Size of the top work area offset (set bottom to the same value to maintain bottom padding)
top: i32,
/// Size of the right work area offset
right: i32,
/// Size of the bottom work area offset
bottom: i32,
}
#[derive(Clap, AhkFunction)]
struct EnsureWorkspaces {
/// Monitor index (zero-indexed)
@@ -316,6 +332,12 @@ enum SubCommand {
/// Move the focused window in the specified direction
#[clap(setting = AppSettings::ArgRequiredElseHelp)]
Move(Move),
/// Change focus to the window in the specified cycle direction
#[clap(setting = AppSettings::ArgRequiredElseHelp)]
CycleFocus(CycleFocus),
/// Move the focused window in the specified cycle direction
#[clap(setting = AppSettings::ArgRequiredElseHelp)]
CycleMove(CycleMove),
/// Stack the focused window in the specified direction
#[clap(setting = AppSettings::ArgRequiredElseHelp)]
Stack(Stack),
@@ -345,11 +367,20 @@ enum SubCommand {
/// Focus the specified workspace on the focused monitor
#[clap(setting = AppSettings::ArgRequiredElseHelp)]
FocusWorkspace(FocusWorkspace),
/// Focus the monitor in the given cycle direction
#[clap(setting = AppSettings::ArgRequiredElseHelp)]
CycleMonitor(CycleMonitor),
/// Focus the workspace in the given cycle direction
#[clap(setting = AppSettings::ArgRequiredElseHelp)]
CycleWorkspace(CycleWorkspace),
/// Create and append a new workspace on the focused monitor
NewWorkspace,
/// Set the invisible border dimensions around each window
#[clap(setting = AppSettings::ArgRequiredElseHelp)]
InvisibleBorders(InvisibleBorders),
/// Set offsets to exclude parts of the work area from tiling
#[clap(setting = AppSettings::ArgRequiredElseHelp)]
WorkAreaOffset(WorkAreaOffset),
/// Adjust container padding on the focused workspace
#[clap(setting = AppSettings::ArgRequiredElseHelp)]
AdjustContainerPadding(AdjustContainerPadding),
@@ -493,6 +524,12 @@ fn main() -> Result<()> {
SubCommand::Move(arg) => {
send_message(&*SocketMessage::MoveWindow(arg.operation_direction).as_bytes()?)?;
}
SubCommand::CycleFocus(arg) => {
send_message(&*SocketMessage::CycleFocusWindow(arg.cycle_direction).as_bytes()?)?;
}
SubCommand::CycleMove(arg) => {
send_message(&*SocketMessage::CycleMoveWindow(arg.cycle_direction).as_bytes()?)?;
}
SubCommand::MoveToMonitor(arg) => {
send_message(&*SocketMessage::MoveContainerToMonitorNumber(arg.target).as_bytes()?)?;
}
@@ -516,6 +553,17 @@ fn main() -> Result<()> {
.as_bytes()?,
)?;
}
SubCommand::WorkAreaOffset(arg) => {
send_message(
&*SocketMessage::WorkAreaOffset(Rect {
left: arg.left,
top: arg.top,
right: arg.right,
bottom: arg.bottom,
})
.as_bytes()?,
)?;
}
SubCommand::ContainerPadding(arg) => {
send_message(
&*SocketMessage::ContainerPadding(arg.monitor, arg.workspace, arg.size)
@@ -654,6 +702,12 @@ fn main() -> Result<()> {
SubCommand::FocusWorkspace(arg) => {
send_message(&*SocketMessage::FocusWorkspaceNumber(arg.target).as_bytes()?)?;
}
SubCommand::CycleMonitor(arg) => {
send_message(&*SocketMessage::CycleFocusMonitor(arg.cycle_direction).as_bytes()?)?;
}
SubCommand::CycleWorkspace(arg) => {
send_message(&*SocketMessage::CycleFocusWorkspace(arg.cycle_direction).as_bytes()?)?;
}
SubCommand::NewWorkspace => {
send_message(&*SocketMessage::NewWorkspace.as_bytes()?)?;
}