mirror of
https://gitlab.com/hyask/swaysome.git
synced 2025-12-10 07:44:43 +01:00
Compare commits
90 commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
31ed37a70c | ||
|
|
59333d6804 | ||
|
|
9dd0fd6831 | ||
|
|
343998a09d | ||
|
|
317e463da1 | ||
|
|
2410c1164d | ||
|
|
3514b9cd5b | ||
|
|
a83c9d45aa | ||
|
|
11396b677e | ||
|
|
75806d081f | ||
|
|
351cb5dd66 | ||
|
|
3146c2f186 | ||
|
|
4b205bf2e9 | ||
|
|
eb2dc1de1d | ||
|
|
f9a0c22e9b | ||
|
|
d6eb1b62a6 | ||
|
|
a8a80eb514 | ||
|
|
3f61dbda90 | ||
|
|
f7bcdf1296 | ||
|
|
ffc73fd832 | ||
|
|
ab32878de5 | ||
|
|
e5c61a8694 | ||
|
|
191a5ab001 | ||
|
|
bc80f6a650 | ||
|
|
c5930274e5 | ||
|
|
df9b26335f | ||
|
|
41227bac8b | ||
|
|
1f8ccb0207 | ||
|
|
a399d827de | ||
|
|
83c454cd6a | ||
|
|
f336a437c2 | ||
|
|
b36ba07d06 | ||
|
|
5f2cd2b300 | ||
|
|
aa3b47fbad | ||
|
|
9da99893c4 | ||
|
|
36c1ef68da | ||
|
|
3ccf1d6eda | ||
|
|
90ec4a2566 | ||
|
|
99a99648b1 | ||
|
|
6d8c19e8a7 | ||
|
|
6d26286583 | ||
|
|
cdd5a24dc6 | ||
|
|
91f697cb42 | ||
|
|
c24d02e284 | ||
|
|
05ca3b8f4a | ||
|
|
9772931af9 | ||
|
|
7b3de58cd9 | ||
|
|
7daf8ffd77 | ||
|
|
bc8ac73ddb | ||
|
|
4e7eed7b99 | ||
|
|
cf86c9bd22 | ||
|
|
2541b0aa2c | ||
|
|
faf309ce81 | ||
|
|
13b9687c0a | ||
|
|
dd22264536 | ||
|
|
0db1faa1c5 | ||
|
|
1234264d75 | ||
|
|
e8fc73c5da | ||
|
|
0566cf106e | ||
|
|
5380131ee9 | ||
|
|
29a0afe619 | ||
|
|
dfb6e79a3a | ||
|
|
1c8ab4246a | ||
|
|
1334bde4bd | ||
|
|
a0dc360e37 | ||
|
|
997f50c759 | ||
|
|
b57fb3d7b2 | ||
|
|
d700e562ce | ||
|
|
25b92a7d26 | ||
|
|
685c75d889 | ||
|
|
4620e8ab2c | ||
|
|
4ec989ed72 | ||
|
|
72c34cb5e5 | ||
|
|
b6f81aa7bb | ||
|
|
286760ade6 | ||
|
|
dd848dfe04 | ||
|
|
6fe7ca7bc3 | ||
|
|
f6278fcef2 | ||
|
|
b39b51ad58 | ||
|
|
90e1e46fb2 | ||
|
|
d57d98697b | ||
|
|
0d19f3f651 | ||
|
|
2497f0519f | ||
|
|
06eb9d29c2 | ||
|
|
1e6986558d | ||
|
|
465a4aff84 | ||
|
|
9af265efbe | ||
|
|
80b791fa2d | ||
|
|
57272671ed | ||
|
|
aa2a232320 |
15 changed files with 2476 additions and 440 deletions
|
|
@ -1,17 +1,29 @@
|
|||
image: "rust:latest"
|
||||
|
||||
lint:cargo:
|
||||
lint:
|
||||
stage: build
|
||||
script:
|
||||
- rustc --version && cargo --version
|
||||
- rustup component add rustfmt
|
||||
- cargo fmt
|
||||
|
||||
build:cargo:
|
||||
build:
|
||||
stage: build
|
||||
script:
|
||||
- rustc --version && cargo --version
|
||||
- cargo build --release
|
||||
artifacts:
|
||||
paths:
|
||||
- target/release/swaysome
|
||||
- target/release/swaysome
|
||||
|
||||
test:integration:
|
||||
stage: test
|
||||
script:
|
||||
- apt update && apt install -y --no-install-recommends sway foot
|
||||
- adduser test
|
||||
- chown -R test:test .
|
||||
- su test <<EOSU # sway won't run as root
|
||||
- set -e
|
||||
# This is weird syntax to only run integration tests (the only ones swaysome has)
|
||||
- cargo test --verbose --test '*'
|
||||
- EOSU
|
||||
|
|
|
|||
262
Cargo.lock
generated
262
Cargo.lock
generated
|
|
@ -1,92 +1,178 @@
|
|||
# This file is automatically @generated by Cargo.
|
||||
# It is not intended for manual editing.
|
||||
version = 3
|
||||
version = 4
|
||||
|
||||
[[package]]
|
||||
name = "ansi_term"
|
||||
version = "0.11.0"
|
||||
name = "anstream"
|
||||
version = "0.6.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ee49baf6cb617b853aa8d93bf420db2383fab46d314482ca2803b40d5fde979b"
|
||||
checksum = "2ab91ebe16eb252986481c5b62f6098f3b698a45e34b5b98200cf20dd2484a44"
|
||||
dependencies = [
|
||||
"winapi",
|
||||
"anstyle",
|
||||
"anstyle-parse",
|
||||
"anstyle-query",
|
||||
"anstyle-wincon",
|
||||
"colorchoice",
|
||||
"utf8parse",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "atty"
|
||||
version = "0.2.14"
|
||||
name = "anstyle"
|
||||
version = "1.0.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d9b39be18770d11421cdb1b9947a45dd3f37e93092cbf377614828a319d5fee8"
|
||||
checksum = "7079075b41f533b8c61d2a4d073c4676e1f8b249ff94a393b0595db304e0dd87"
|
||||
|
||||
[[package]]
|
||||
name = "anstyle-parse"
|
||||
version = "0.2.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "317b9a89c1868f5ea6ff1d9539a69f45dffc21ce321ac1fd1160dfa48c8e2140"
|
||||
dependencies = [
|
||||
"hermit-abi",
|
||||
"libc",
|
||||
"winapi",
|
||||
"utf8parse",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "bitflags"
|
||||
version = "1.3.2"
|
||||
name = "anstyle-query"
|
||||
version = "1.0.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a"
|
||||
checksum = "5ca11d4be1bab0c8bc8734a9aa7bf4ee8316d462a08c6ac5052f888fef5b494b"
|
||||
dependencies = [
|
||||
"windows-sys",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "anstyle-wincon"
|
||||
version = "3.0.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f0699d10d2f4d628a98ee7b57b289abbc98ff3bad977cb3152709d4bf2330628"
|
||||
dependencies = [
|
||||
"anstyle",
|
||||
"windows-sys",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "assert-json-diff"
|
||||
version = "2.0.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "47e4f2b81832e72834d7518d8487a0396a28cc408186a2e8854c0f98011faf12"
|
||||
dependencies = [
|
||||
"serde",
|
||||
"serde_json",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "byteorder"
|
||||
version = "1.4.2"
|
||||
version = "1.5.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ae44d1a3d5a19df61dd0c8beb138458ac2a53a7ac09eba97d55592540004306b"
|
||||
checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b"
|
||||
|
||||
[[package]]
|
||||
name = "clap"
|
||||
version = "2.33.3"
|
||||
version = "4.4.8"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "37e58ac78573c40708d45522f0d80fa2f01cc4f9b4e2bf749807255454312002"
|
||||
checksum = "2275f18819641850fa26c89acc84d465c1bf91ce57bc2748b28c420473352f64"
|
||||
dependencies = [
|
||||
"ansi_term",
|
||||
"atty",
|
||||
"bitflags",
|
||||
"strsim",
|
||||
"textwrap",
|
||||
"unicode-width",
|
||||
"vec_map",
|
||||
"clap_builder",
|
||||
"clap_derive",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "hermit-abi"
|
||||
version = "0.1.19"
|
||||
name = "clap_builder"
|
||||
version = "4.4.8"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "62b467343b94ba476dcb2500d242dadbb39557df889310ac77c5d99100aaac33"
|
||||
checksum = "07cdf1b148b25c1e1f7a42225e30a0d99a615cd4637eae7365548dd4529b95bc"
|
||||
dependencies = [
|
||||
"libc",
|
||||
"anstream",
|
||||
"anstyle",
|
||||
"clap_lex",
|
||||
"strsim",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "clap_derive"
|
||||
version = "4.4.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "cf9804afaaf59a91e75b022a30fb7229a7901f60c755489cc61c9b423b836442"
|
||||
dependencies = [
|
||||
"heck",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "clap_lex"
|
||||
version = "0.6.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "702fc72eb24e5a1e48ce58027a675bc24edd52096d5397d4aea7c6dd9eca0bd1"
|
||||
|
||||
[[package]]
|
||||
name = "colorchoice"
|
||||
version = "1.0.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "acbf1af155f9b9ef647e42cdc158db4b64a1b61f743629225fde6f3e0be2a7c7"
|
||||
|
||||
[[package]]
|
||||
name = "heck"
|
||||
version = "0.4.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8"
|
||||
|
||||
[[package]]
|
||||
name = "itoa"
|
||||
version = "0.4.7"
|
||||
version = "1.0.9"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "dd25036021b0de88a0aff6b850051563c6516d0bf53f8638938edbb9de732736"
|
||||
checksum = "af150ab688ff2122fcef229be89cb50dd66af9e01a4ff320cc137eecc9bacc38"
|
||||
|
||||
[[package]]
|
||||
name = "libc"
|
||||
version = "0.2.101"
|
||||
name = "proc-macro2"
|
||||
version = "1.0.69"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3cb00336871be5ed2c8ed44b60ae9959dc5b9f08539422ed43f09e34ecaeba21"
|
||||
checksum = "134c189feb4956b20f6f547d2cf727d4c0fe06722b20a0eec87ed445a97f92da"
|
||||
dependencies = [
|
||||
"unicode-ident",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "quote"
|
||||
version = "1.0.33"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5267fca4496028628a95160fc423a33e8b2e6af8a5302579e322e4b520293cae"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ryu"
|
||||
version = "1.0.5"
|
||||
version = "1.0.15"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "71d301d4193d031abdd79ff7e3dd721168a9572ef3fe51a1517aba235bd8f86e"
|
||||
checksum = "1ad4cc8da4ef723ed60bced201181d83791ad433213d8c24efffda1eec85d741"
|
||||
|
||||
[[package]]
|
||||
name = "serde"
|
||||
version = "1.0.123"
|
||||
version = "1.0.192"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "92d5161132722baa40d802cc70b15262b98258453e85e5d1d365c757c73869ae"
|
||||
checksum = "bca2a08484b285dcb282d0f67b26cadc0df8b19f8c12502c13d966bf9482f001"
|
||||
dependencies = [
|
||||
"serde_derive",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "serde_derive"
|
||||
version = "1.0.192"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d6c7207fbec9faa48073f3e3074cbe553af6ea512d7c21ba46e434e70ea9fbc1"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "serde_json"
|
||||
version = "1.0.64"
|
||||
version = "1.0.108"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "799e97dc9fdae36a5c8b8f2cae9ce2ee9fdce2058c57a93e6099d919fd982f79"
|
||||
checksum = "3d1c7e3eac408d115102c4c24ad393e0821bb3a5df4d506a80f85f7a742a526b"
|
||||
dependencies = [
|
||||
"itoa",
|
||||
"ryu",
|
||||
|
|
@ -95,58 +181,106 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "strsim"
|
||||
version = "0.8.0"
|
||||
version = "0.10.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8ea5119cdb4c55b55d432abb513a0429384878c15dde60cc77b1c99de1a95a6a"
|
||||
checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623"
|
||||
|
||||
[[package]]
|
||||
name = "swaysome"
|
||||
version = "1.1.2"
|
||||
version = "2.3.2"
|
||||
dependencies = [
|
||||
"assert-json-diff",
|
||||
"byteorder",
|
||||
"clap",
|
||||
"serde",
|
||||
"serde_json",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "textwrap"
|
||||
version = "0.11.0"
|
||||
name = "syn"
|
||||
version = "2.0.39"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d326610f408c7a4eb6f51c37c330e496b08506c9457c9d34287ecc38809fb060"
|
||||
checksum = "23e78b90f2fcf45d3e842032ce32e3f2d1545ba6636271dcbf24fa306d87be7a"
|
||||
dependencies = [
|
||||
"unicode-width",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"unicode-ident",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "unicode-width"
|
||||
version = "0.1.8"
|
||||
name = "unicode-ident"
|
||||
version = "1.0.12"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9337591893a19b88d8d87f2cec1e73fad5cdfd10e5a6f349f498ad6ea2ffb1e3"
|
||||
checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b"
|
||||
|
||||
[[package]]
|
||||
name = "vec_map"
|
||||
version = "0.8.2"
|
||||
name = "utf8parse"
|
||||
version = "0.2.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f1bddf1187be692e79c5ffeab891132dfb0f236ed36a43c7ed39f1165ee20191"
|
||||
checksum = "711b9620af191e0cdc7468a8d14e709c3dcdb115b36f838e601583af800a370a"
|
||||
|
||||
[[package]]
|
||||
name = "winapi"
|
||||
version = "0.3.9"
|
||||
name = "windows-sys"
|
||||
version = "0.48.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419"
|
||||
checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9"
|
||||
dependencies = [
|
||||
"winapi-i686-pc-windows-gnu",
|
||||
"winapi-x86_64-pc-windows-gnu",
|
||||
"windows-targets",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "winapi-i686-pc-windows-gnu"
|
||||
version = "0.4.0"
|
||||
name = "windows-targets"
|
||||
version = "0.48.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6"
|
||||
checksum = "9a2fa6e2155d7247be68c096456083145c183cbbbc2764150dda45a87197940c"
|
||||
dependencies = [
|
||||
"windows_aarch64_gnullvm",
|
||||
"windows_aarch64_msvc",
|
||||
"windows_i686_gnu",
|
||||
"windows_i686_msvc",
|
||||
"windows_x86_64_gnu",
|
||||
"windows_x86_64_gnullvm",
|
||||
"windows_x86_64_msvc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "winapi-x86_64-pc-windows-gnu"
|
||||
version = "0.4.0"
|
||||
name = "windows_aarch64_gnullvm"
|
||||
version = "0.48.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"
|
||||
checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8"
|
||||
|
||||
[[package]]
|
||||
name = "windows_aarch64_msvc"
|
||||
version = "0.48.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc"
|
||||
|
||||
[[package]]
|
||||
name = "windows_i686_gnu"
|
||||
version = "0.48.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e"
|
||||
|
||||
[[package]]
|
||||
name = "windows_i686_msvc"
|
||||
version = "0.48.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406"
|
||||
|
||||
[[package]]
|
||||
name = "windows_x86_64_gnu"
|
||||
version = "0.48.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e"
|
||||
|
||||
[[package]]
|
||||
name = "windows_x86_64_gnullvm"
|
||||
version = "0.48.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc"
|
||||
|
||||
[[package]]
|
||||
name = "windows_x86_64_msvc"
|
||||
version = "0.48.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538"
|
||||
|
|
|
|||
18
Cargo.toml
18
Cargo.toml
|
|
@ -1,9 +1,9 @@
|
|||
[package]
|
||||
name = "swaysome"
|
||||
version = "1.1.2"
|
||||
authors = ["Skia <skia@hya.sk>"]
|
||||
edition = "2018"
|
||||
description = "swaysome provides a different way to manage your multiple outputs with the sway windows manager"
|
||||
version = "2.3.2"
|
||||
authors = ["Skia <skia@hya.sk>", "Nabos <nabos@glargh.fr>"]
|
||||
edition = "2021"
|
||||
description = "swaysome provides an awesome way to manage your multiple outputs with the sway windows manager"
|
||||
license = "MIT"
|
||||
repository = "https://gitlab.com/hyask/swaysome"
|
||||
homepage = "https://gitlab.com/hyask/swaysome"
|
||||
|
|
@ -11,6 +11,10 @@ homepage = "https://gitlab.com/hyask/swaysome"
|
|||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
[dependencies]
|
||||
byteorder = "1"
|
||||
serde_json = "1"
|
||||
clap = "2"
|
||||
byteorder = "1.5.0"
|
||||
clap = { version = "4.4.8", features = ["derive"] }
|
||||
serde = { version = "1.0.192", features = ["derive"] }
|
||||
serde_json = "1.0.108"
|
||||
|
||||
[dev-dependencies]
|
||||
assert-json-diff = "2.0.2"
|
||||
|
|
|
|||
14
HACKING.md
Normal file
14
HACKING.md
Normal file
|
|
@ -0,0 +1,14 @@
|
|||
# Hacking on swaysome
|
||||
|
||||
## Get the coverage of the test to improve them
|
||||
|
||||
You'll need more toolchain components:
|
||||
```
|
||||
rustup component add llvm-tools-preview
|
||||
```
|
||||
|
||||
Then run the tests with coverage profiling, and generate the HTML report:
|
||||
```
|
||||
CARGO_INCREMENTAL=0 RUSTFLAGS='-Cinstrument-coverage' LLVM_PROFILE_FILE='cargo-test-%p-%m.profraw' cargo test
|
||||
grcov . --binary-path ./target/debug/deps/ -s . -t html --branch --ignore-not-existing --ignore '../*' --ignore "/*" -o target/coverage
|
||||
```
|
||||
138
README.md
138
README.md
|
|
@ -1,14 +1,60 @@
|
|||
# Swaysome
|
||||
|
||||
This binary helps you configure sway to work a bit more like Awesome. This
|
||||
currently means workspaces that are name-spaced on a per-screen basis.
|
||||
An awesome way to manage your workspaces on [sway](https://swaywm.org/).
|
||||
|
||||
It may also work with i3, but this is untested.
|
||||
|
||||
*swaysome* is compatible with [sworkstyle](https://lib.rs/crates/sworkstyle).
|
||||
Join us on [#swaysome:matrix.hya.sk](https://matrix.to/#/%23swaysome:matrix.hya.sk)!
|
||||
|
||||
|
||||
## Usage
|
||||
## Description
|
||||
|
||||
This binary helps you configure sway to work a bit more like [AwesomeWM](https://awesomewm.org/). This means that
|
||||
**workspaces** are namespaced in what are called **workspace groups**, and **workspace groups** can be moved around the
|
||||
differents outputs easily.
|
||||
|
||||
For example, with workspace `11` on the first output, and workspace `21` on the second output, triggering the `swaysome focus 1`
|
||||
shortcut to get workspace `1` would lead you to workspace `11` if your focus is on the first output, and workspace `21`
|
||||
is the focus is on the second one.
|
||||
|
||||
By default, `swaysome init` will create a **workspace group** per active output, but you may create other groups while
|
||||
working, by either triggering `swaysome focus-group <new-number>` and opening a new window, or sending an existing
|
||||
window to it first with `swaysome move-to-group <new-number>`.
|
||||
|
||||
Here is a common use-case for this:
|
||||
|
||||
* `output-1`:
|
||||
* **workspace group** 1:
|
||||
* workspace `11`: chats
|
||||
* workspace `12`: emails
|
||||
* `output-2`:
|
||||
* **workspace group** 2:
|
||||
* workspace `21`: IDE for first project
|
||||
* workspace `22`: browser for first project
|
||||
* workspace `23`: terminals for first project
|
||||
* **workspace group** 3:
|
||||
* workspace `31`: IDE for second project
|
||||
* workspace `32`: browser for second project
|
||||
* workspace `33`: terminals for second project
|
||||
|
||||
That way, when `output-2` is focused on **workspace group** 2, be it workspace `21` or `22`, the quick `$mod+<number>`
|
||||
(bound to `swaysome focus <number>`) shortcut won't leave **workspace group** 2, allowing you to open multiple projects
|
||||
in parallel without the hassle of manually remembering how to namespace them.
|
||||
|
||||
In that situation, suppose you plug in a new output, `output-3`, you may then want to focus **workspace group 3**
|
||||
to send it to `output-3`: this is simply done by typing the shortcuts `$mod+Alt+3` (`swaysome focus-group 3`) then
|
||||
`$mod+Alt+o` (`swaysome workspace-group-next-output`).
|
||||
|
||||
|
||||
`swaysome` may also work with i3, but this is untested.
|
||||
|
||||
`swaysome` should be compatible with [sworkstyle](https://lib.rs/crates/sworkstyle). If this is broken, please report a bug.
|
||||
|
||||
|
||||
## Installation
|
||||
|
||||
|
||||
Arch Linux: Found on the AUR as [swaysome-git](https://aur.archlinux.org/packages/swaysome-git).
|
||||
|
||||
Void Linux: `xbps-install swaysome`
|
||||
|
||||
If you have Rust installed, then you can just `cargo install swaysome` and you're good to go.
|
||||
|
||||
|
|
@ -16,71 +62,26 @@ Otherwise, you may grab a [pre-built
|
|||
binary](https://gitlab.com/hyask/swaysome/-/jobs/artifacts/master/raw/target/release/swaysome?job=build:cargo) from the
|
||||
CI and put it in your `$PATH`.
|
||||
|
||||
**WARNING**: please double-check that your `swaysome` binary is in `sway`'s `$PATH`. Depending on your setup, the
|
||||
`$PATH` you have in your shell may not be the same as `sway`'s, and if `swaysome` can't be called by `sway`, the
|
||||
symptoms will only look like non-functional shortcuts.
|
||||
If you're in this situation, a quick workaround is to call `swaysome` with its full absolute path from `sway`'s config
|
||||
to check that everything works before fixing your `$PATH` issue.
|
||||
|
||||
Then create the file (and the directory if needed) "~/.config/sway/config.d/swaysome.conf" and paste this inside:
|
||||
```
|
||||
# Change focus between workspaces
|
||||
unbindsym $mod+1
|
||||
unbindsym $mod+2
|
||||
unbindsym $mod+3
|
||||
unbindsym $mod+4
|
||||
unbindsym $mod+5
|
||||
unbindsym $mod+6
|
||||
unbindsym $mod+7
|
||||
unbindsym $mod+8
|
||||
unbindsym $mod+9
|
||||
unbindsym $mod+0
|
||||
bindsym $mod+1 exec "swaysome focus 1"
|
||||
bindsym $mod+2 exec "swaysome focus 2"
|
||||
bindsym $mod+3 exec "swaysome focus 3"
|
||||
bindsym $mod+4 exec "swaysome focus 4"
|
||||
bindsym $mod+5 exec "swaysome focus 5"
|
||||
bindsym $mod+6 exec "swaysome focus 6"
|
||||
bindsym $mod+7 exec "swaysome focus 7"
|
||||
bindsym $mod+8 exec "swaysome focus 8"
|
||||
bindsym $mod+9 exec "swaysome focus 9"
|
||||
bindsym $mod+0 exec "swaysome focus 0"
|
||||
|
||||
# Move containers between workspaces
|
||||
unbindsym $mod+Shift+1
|
||||
unbindsym $mod+Shift+2
|
||||
unbindsym $mod+Shift+3
|
||||
unbindsym $mod+Shift+4
|
||||
unbindsym $mod+Shift+5
|
||||
unbindsym $mod+Shift+6
|
||||
unbindsym $mod+Shift+7
|
||||
unbindsym $mod+Shift+8
|
||||
unbindsym $mod+Shift+9
|
||||
unbindsym $mod+Shift+0
|
||||
bindsym $mod+Shift+1 exec "swaysome move 1"
|
||||
bindsym $mod+Shift+2 exec "swaysome move 2"
|
||||
bindsym $mod+Shift+3 exec "swaysome move 3"
|
||||
bindsym $mod+Shift+4 exec "swaysome move 4"
|
||||
bindsym $mod+Shift+5 exec "swaysome move 5"
|
||||
bindsym $mod+Shift+6 exec "swaysome move 6"
|
||||
bindsym $mod+Shift+7 exec "swaysome move 7"
|
||||
bindsym $mod+Shift+8 exec "swaysome move 8"
|
||||
bindsym $mod+Shift+9 exec "swaysome move 9"
|
||||
bindsym $mod+Shift+0 exec "swaysome move 0"
|
||||
## Usage
|
||||
|
||||
# Move focused container to next output
|
||||
bindsym $mod+o exec "swaysome next_output"
|
||||
Copy the `swaysome.conf` file in `~/.config/sway/config.d/swaysome.conf`.
|
||||
|
||||
# Move focused container to previous output
|
||||
bindsym $mod+Shift+o exec "swaysome prev_output"
|
||||
|
||||
# Init workspaces for every screen
|
||||
exec "swaysome init 1"
|
||||
```
|
||||
|
||||
Finally append your `sway` configuration with this:
|
||||
Then append your `sway` configuration with this:
|
||||
```
|
||||
include ~/.config/sway/config.d/*.conf
|
||||
```
|
||||
|
||||
You should end-up with workspaces from `1` to `0`, prefixed with a screen index,
|
||||
giving you workspace `01` on the first screen, and workspace `11` on the second
|
||||
one, both accessible with shortcut `$mod+1`.
|
||||
On next startup of `sway`, you should end-up with workspaces from `1` to `0`,
|
||||
prefixed with a screen index, giving you workspace `11` on the first screen, and
|
||||
workspace `21` on the second one, both accessible with shortcut `$mod+1` when
|
||||
focused on the right output.
|
||||
|
||||
The `init` command simply walks through every screen to initialize a prefixed
|
||||
workspace. It does it backwards so that you end-up focused on the first screen,
|
||||
|
|
@ -89,10 +90,9 @@ as usual.
|
|||
|
||||
## Exhaustive swaysome commands list
|
||||
|
||||
* `move [name]`: move the focused container to `[name]`
|
||||
* `next_output`: move the focused container to the next output
|
||||
* `prev_output`: move the focused container to the previous output
|
||||
* `focus [name]`: change focus to `[name]`
|
||||
* `focus_all_outputs [name]`: change all outputs focus to `[name]`
|
||||
* `init [name]`: cycle all outputs to create a default workspace with name `[name]`
|
||||
Just run `swaysome --help` for the most up to date list of command and documentation.
|
||||
|
||||
|
||||
## Breaking changes
|
||||
|
||||
* Starting with 2.0, `next_output` and `prev_output` have been changed to `next-output` and `prev-output`.
|
||||
|
|
|
|||
15
pkgbuild/.SRCINFO
Normal file
15
pkgbuild/.SRCINFO
Normal file
|
|
@ -0,0 +1,15 @@
|
|||
pkgbase = swaysome-git
|
||||
pkgdesc = Awesome WM like workspaces
|
||||
pkgver = 0.0.0
|
||||
pkgrel = 1
|
||||
url = https://gitlab.com/hyask/swaysome
|
||||
arch = x86_64
|
||||
license = MIT
|
||||
makedepends = git
|
||||
makedepends = rust
|
||||
provides = swaysome
|
||||
conflicts = swaysome
|
||||
source = swaysome::git+https://gitlab.com/hyask/swaysome
|
||||
md5sums = SKIP
|
||||
|
||||
pkgname = swaysome-git
|
||||
30
pkgbuild/PKGBUILD
Normal file
30
pkgbuild/PKGBUILD
Normal file
|
|
@ -0,0 +1,30 @@
|
|||
# Maintainer: XXX
|
||||
|
||||
pkgname=swaysome-git
|
||||
_pkgname=swaysome
|
||||
pkgver=0.0.0
|
||||
pkgrel=1
|
||||
pkgdesc='Awesome WM like workspaces'
|
||||
arch=('x86_64')
|
||||
url='https://gitlab.com/hyask/swaysome'
|
||||
license=('MIT')
|
||||
makedepends=('git' 'rust')
|
||||
provides=("$_pkgname")
|
||||
conflicts=("$_pkgname")
|
||||
source=("$_pkgname::git+$url")
|
||||
md5sums=('SKIP')
|
||||
|
||||
pkgver() {
|
||||
cd "$_pkgname"
|
||||
echo $(grep '^version =' Cargo.toml|head -n1|cut -d\" -f2).r$(git rev-list --count HEAD).g$(git rev-parse --short HEAD)
|
||||
}
|
||||
|
||||
build() {
|
||||
cd "$_pkgname"
|
||||
cargo build --release
|
||||
}
|
||||
|
||||
package() {
|
||||
cd "$_pkgname"
|
||||
install -Dm755 "target/release/$_pkgname" "$pkgdir/usr/bin/$_pkgname"
|
||||
}
|
||||
629
src/lib.rs
Normal file
629
src/lib.rs
Normal file
|
|
@ -0,0 +1,629 @@
|
|||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use std::cell::Cell;
|
||||
use std::env;
|
||||
use std::io::Cursor;
|
||||
use std::io::{Read, Write};
|
||||
use std::mem;
|
||||
use std::os::unix::net::UnixStream;
|
||||
use std::path::Path;
|
||||
|
||||
use byteorder::{NativeEndian, ReadBytesExt, WriteBytesExt};
|
||||
|
||||
// Maximum workspaces per group. This will determine the naming.
|
||||
// Examples:
|
||||
// 10 → 17, 27
|
||||
// 100 → 107, 207
|
||||
// 15 → 22, 37
|
||||
const MAX_GROUP_WS: usize = 10;
|
||||
|
||||
const RUN_COMMAND: u32 = 0;
|
||||
const GET_WORKSPACES: u32 = 1;
|
||||
// const SUBSCRIBE: u32 = 2;
|
||||
const GET_OUTPUTS: u32 = 3;
|
||||
const GET_TREE: u32 = 4;
|
||||
|
||||
pub struct SwaySome {
|
||||
socket: Cell<Option<UnixStream>>,
|
||||
outputs: Vec<Output>,
|
||||
// current_output: Output,
|
||||
workspaces: Vec<Workspace>,
|
||||
// current_workspace: Workspace,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug)]
|
||||
struct Output {
|
||||
name: String,
|
||||
#[serde(default)]
|
||||
focused: bool,
|
||||
#[serde(default)]
|
||||
active: bool,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug)]
|
||||
struct Workspace {
|
||||
num: usize,
|
||||
output: String,
|
||||
visible: bool,
|
||||
}
|
||||
|
||||
impl SwaySome {
|
||||
pub fn new() -> SwaySome {
|
||||
for socket_var in ["SWAYSOCK", "I3SOCK"] {
|
||||
let socket_path = match env::var(socket_var) {
|
||||
Ok(val) => val,
|
||||
Err(_e) => {
|
||||
eprintln!("{} not found in environment", socket_var);
|
||||
continue;
|
||||
}
|
||||
};
|
||||
|
||||
let socket = Path::new(&socket_path);
|
||||
match SwaySome::new_from_socket(socket) {
|
||||
Ok(swaysome) => return swaysome,
|
||||
Err(e) => eprintln!("Error with value found in ${}: {}", socket_var, e),
|
||||
}
|
||||
}
|
||||
panic!("couldn't find any i3/sway socket")
|
||||
}
|
||||
|
||||
pub fn new_from_socket(socket: &Path) -> Result<SwaySome, String> {
|
||||
let stream = match UnixStream::connect(&socket) {
|
||||
Err(_) => {
|
||||
return Err(format!(
|
||||
"counldn't connect to socket '{}'",
|
||||
socket.display()
|
||||
));
|
||||
}
|
||||
Ok(stream) => {
|
||||
eprintln!("successful connection to socket '{}'", socket.display());
|
||||
stream
|
||||
}
|
||||
};
|
||||
|
||||
let mut swaysome = SwaySome {
|
||||
socket: Cell::new(Some(stream)),
|
||||
outputs: vec![],
|
||||
workspaces: vec![],
|
||||
};
|
||||
swaysome.outputs = swaysome.get_outputs();
|
||||
swaysome.workspaces = swaysome.get_workspaces();
|
||||
Ok(swaysome)
|
||||
}
|
||||
|
||||
fn send_msg(&self, msg_type: u32, payload: &str) {
|
||||
let payload_length = payload.len() as u32;
|
||||
|
||||
let mut msg_prefix: [u8; 6 * mem::size_of::<u8>() + 2 * mem::size_of::<u32>()] =
|
||||
*b"i3-ipc00000000";
|
||||
|
||||
msg_prefix[6..]
|
||||
.as_mut()
|
||||
.write_u32::<NativeEndian>(payload_length)
|
||||
.expect("Unable to write");
|
||||
|
||||
msg_prefix[10..]
|
||||
.as_mut()
|
||||
.write_u32::<NativeEndian>(msg_type)
|
||||
.expect("Unable to write");
|
||||
|
||||
let mut msg: Vec<u8> = msg_prefix[..].to_vec();
|
||||
msg.extend(payload.as_bytes());
|
||||
|
||||
let mut socket = self
|
||||
.socket
|
||||
.take()
|
||||
.expect("Unexisting socket, there probably is a logic error");
|
||||
|
||||
if socket.write_all(&msg[..]).is_err() {
|
||||
panic!("couldn't send message");
|
||||
}
|
||||
self.socket.set(Some(socket));
|
||||
}
|
||||
|
||||
fn send_command(&self, command: &str) {
|
||||
eprint!("Sending command: '{}' - ", &command);
|
||||
self.send_msg(RUN_COMMAND, command);
|
||||
self.check_success();
|
||||
}
|
||||
|
||||
fn read_msg(&self) -> Result<String, &str> {
|
||||
let mut response_header: [u8; 14] = *b"uninitialized.";
|
||||
let mut socket = self
|
||||
.socket
|
||||
.take()
|
||||
.expect("Unexisting socket, there probably is a logic error");
|
||||
socket.read_exact(&mut response_header).unwrap();
|
||||
|
||||
if &response_header[0..6] == b"i3-ipc" {
|
||||
let mut v = Cursor::new(vec![
|
||||
response_header[6],
|
||||
response_header[7],
|
||||
response_header[8],
|
||||
response_header[9],
|
||||
]);
|
||||
let payload_length = v.read_u32::<NativeEndian>().unwrap();
|
||||
|
||||
let mut payload = vec![0; payload_length as usize];
|
||||
socket.read_exact(&mut payload[..]).unwrap();
|
||||
let payload_str = String::from_utf8(payload).unwrap();
|
||||
self.socket.set(Some(socket));
|
||||
Ok(payload_str)
|
||||
} else {
|
||||
eprint!("Not an i3-icp packet, emptying the buffer: ");
|
||||
let mut v = vec![];
|
||||
socket.read_to_end(&mut v).unwrap();
|
||||
eprintln!("{:?}", v);
|
||||
self.socket.set(Some(socket));
|
||||
Err("Unable to read i3-ipc packet")
|
||||
}
|
||||
}
|
||||
|
||||
fn check_success(&self) {
|
||||
match self.read_msg() {
|
||||
Ok(msg) => {
|
||||
let r: Vec<serde_json::Value> = serde_json::from_str(&msg).unwrap();
|
||||
match r[0]["success"] {
|
||||
serde_json::Value::Bool(true) => eprintln!("Command successful"),
|
||||
_ => panic!("Command failed: {:#?}", r),
|
||||
}
|
||||
}
|
||||
Err(_) => panic!("Unable to read response"),
|
||||
};
|
||||
}
|
||||
|
||||
/*
|
||||
* Only used in tests
|
||||
*/
|
||||
pub fn get_tree(&self) -> serde_json::Value {
|
||||
self.send_msg(GET_TREE, "");
|
||||
match self.read_msg() {
|
||||
Ok(msg) => serde_json::from_str(&msg).expect("Failed to parse JSON for get_tree"),
|
||||
Err(_) => panic!("Unable to get current workspace"),
|
||||
}
|
||||
}
|
||||
|
||||
fn get_outputs(&self) -> Vec<Output> {
|
||||
self.send_msg(GET_OUTPUTS, "");
|
||||
let o = match self.read_msg() {
|
||||
Ok(msg) => msg,
|
||||
Err(_) => panic!("Unable to get outputs"),
|
||||
};
|
||||
let mut outputs: Vec<Output> = serde_json::from_str::<Vec<Output>>(&o)
|
||||
.unwrap()
|
||||
.into_iter()
|
||||
.filter(|x| x.active)
|
||||
.collect();
|
||||
outputs.sort_by(|x, y| x.name.cmp(&y.name)); // sort_by_key doesn't work here (https://stackoverflow.com/a/47126516)
|
||||
outputs
|
||||
}
|
||||
|
||||
fn get_workspaces(&self) -> Vec<Workspace> {
|
||||
self.send_msg(GET_WORKSPACES, "");
|
||||
let ws = match self.read_msg() {
|
||||
Ok(msg) => msg,
|
||||
Err(_) => panic!("Unable to get current workspace"),
|
||||
};
|
||||
let mut workspaces: Vec<Workspace> = serde_json::from_str(&ws).unwrap();
|
||||
workspaces.sort_by_key(|x| x.num);
|
||||
workspaces
|
||||
}
|
||||
|
||||
fn get_current_output_index(&self) -> usize {
|
||||
// Do not use `self.outputs`, as the information here could be outdated, especially the `focused` attribute
|
||||
let outputs = self.get_outputs();
|
||||
match outputs.iter().position(|x| x.focused) {
|
||||
Some(i) => i,
|
||||
None => panic!("WTF! No focused output???"),
|
||||
}
|
||||
}
|
||||
|
||||
fn get_current_output_name(&self) -> String {
|
||||
// Do not use `self.outputs`, as the information here could be outdated, especially the `focused` attribute
|
||||
let outputs = self.get_outputs();
|
||||
let focused_output_index = match outputs.iter().find(|x| x.focused) {
|
||||
Some(i) => i.name.as_str(),
|
||||
None => panic!("WTF! No focused output???"),
|
||||
};
|
||||
|
||||
focused_output_index.to_string()
|
||||
}
|
||||
|
||||
fn get_current_workspace_index(&self) -> usize {
|
||||
// Do not use `self.outputs`, as the information here could be outdated, especially the `focused` attribute
|
||||
let outputs = self.get_outputs();
|
||||
// Do not use `self.workspaces`, as the information here could be outdated, especially the `visible` attribute
|
||||
self.get_workspaces()
|
||||
.iter()
|
||||
.find(|w| w.visible && outputs.iter().find(|o| o.name == w.output).unwrap().focused)
|
||||
.unwrap()
|
||||
.num
|
||||
}
|
||||
|
||||
pub fn move_container_to_workspace(&self, workspace_index: usize) {
|
||||
if workspace_index < MAX_GROUP_WS {
|
||||
self.move_container_to_workspace_relative(workspace_index);
|
||||
} else {
|
||||
self.move_container_to_workspace_absolute(workspace_index);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn move_container_to_workspace_group(&self, target_group: usize) {
|
||||
let current_workspace_index = self.get_current_workspace_index();
|
||||
let current_workspace_index_relative = (current_workspace_index % MAX_GROUP_WS) as usize;
|
||||
self.move_container_to_workspace_absolute(
|
||||
current_workspace_index_relative + target_group * MAX_GROUP_WS,
|
||||
);
|
||||
}
|
||||
|
||||
fn move_container_to_workspace_absolute(&self, workspace_index: usize) {
|
||||
let group_index = (workspace_index / MAX_GROUP_WS) as usize;
|
||||
let full_ws_name = format!(
|
||||
"{}",
|
||||
group_index * MAX_GROUP_WS + workspace_index % MAX_GROUP_WS
|
||||
);
|
||||
|
||||
// If the workspace already exists
|
||||
match self.workspaces.iter().find(|w| w.num == workspace_index) {
|
||||
Some(_) => {
|
||||
let mut focus_cmd: String = "move container to workspace number ".to_string();
|
||||
focus_cmd.push_str(&full_ws_name);
|
||||
self.send_command(&focus_cmd);
|
||||
}
|
||||
None => {
|
||||
let target_group = workspace_index / MAX_GROUP_WS;
|
||||
let target_screen_index = match self
|
||||
.workspaces
|
||||
.iter()
|
||||
.find(|w| w.num / MAX_GROUP_WS == target_group)
|
||||
{
|
||||
// If other workspaces on the same group exists
|
||||
Some(other_workspace) => Some(
|
||||
self.outputs
|
||||
.iter()
|
||||
.enumerate()
|
||||
.find(|i| i.1.name == other_workspace.output)
|
||||
.unwrap()
|
||||
.0,
|
||||
),
|
||||
None => {
|
||||
// Or if the targeted output is currently connected
|
||||
if group_index < self.outputs.len() {
|
||||
Some(group_index)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
match target_screen_index {
|
||||
Some(target_screen_index) => {
|
||||
let target_output = &self.outputs[target_screen_index];
|
||||
|
||||
let current_output_name = self.get_current_output_name();
|
||||
|
||||
if target_output.name == current_output_name {
|
||||
let mut focus_cmd: String = "move container to workspace ".to_string();
|
||||
focus_cmd.push_str(&full_ws_name);
|
||||
self.send_command(&focus_cmd);
|
||||
} else {
|
||||
// If we have to send it to another screen
|
||||
let mut focus_cmd: String = "focus output ".to_string();
|
||||
focus_cmd.push_str(&target_output.name);
|
||||
self.send_command(&focus_cmd);
|
||||
|
||||
let focused_workspace_index = self.get_current_workspace_index();
|
||||
|
||||
let mut focus_cmd: String = "workspace ".to_string();
|
||||
focus_cmd.push_str(&full_ws_name);
|
||||
self.send_command(&focus_cmd);
|
||||
|
||||
let mut focus_cmd: String = "focus output ".to_string();
|
||||
focus_cmd.push_str(¤t_output_name);
|
||||
self.send_command(&focus_cmd);
|
||||
|
||||
let mut focus_cmd: String = "move container to workspace ".to_string();
|
||||
focus_cmd.push_str(&full_ws_name);
|
||||
self.send_command(&focus_cmd);
|
||||
|
||||
let mut focus_cmd: String = "focus output ".to_string();
|
||||
focus_cmd.push_str(&target_output.name);
|
||||
self.send_command(&focus_cmd);
|
||||
|
||||
let mut focus_cmd: String = "workspace ".to_string();
|
||||
focus_cmd.push_str(&focused_workspace_index.to_string());
|
||||
self.send_command(&focus_cmd);
|
||||
|
||||
let mut focus_cmd: String = "focus output ".to_string();
|
||||
focus_cmd.push_str(¤t_output_name);
|
||||
self.send_command(&focus_cmd);
|
||||
}
|
||||
}
|
||||
None => {
|
||||
// Else, we send the container on the current output
|
||||
let mut focus_cmd: String = "move container to workspace ".to_string();
|
||||
focus_cmd.push_str(&full_ws_name);
|
||||
self.send_command(&focus_cmd);
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn move_container_to_workspace_relative(&self, workspace_index: usize) {
|
||||
let current_workspace_index: usize = self.get_current_workspace_index();
|
||||
let focused_output_index = current_workspace_index / MAX_GROUP_WS;
|
||||
|
||||
let mut cmd: String = "move container to workspace number ".to_string();
|
||||
let full_ws_name = format!("{}", focused_output_index * MAX_GROUP_WS + workspace_index);
|
||||
cmd.push_str(&full_ws_name);
|
||||
self.send_command(&cmd);
|
||||
}
|
||||
|
||||
pub fn focus_to_workspace(&self, workspace_index: usize) {
|
||||
if workspace_index < MAX_GROUP_WS {
|
||||
self.focus_to_workspace_relative(workspace_index);
|
||||
} else {
|
||||
self.focus_to_workspace_absolute(workspace_index);
|
||||
}
|
||||
}
|
||||
|
||||
fn focus_to_workspace_absolute(&self, workspace_index: usize) {
|
||||
let target_group = workspace_index / MAX_GROUP_WS;
|
||||
match self
|
||||
.workspaces
|
||||
.iter()
|
||||
.find(|w| w.num / MAX_GROUP_WS == target_group)
|
||||
{
|
||||
// If other workspaces on the same group exists
|
||||
Some(other_workspace) => {
|
||||
// find the corresponding output and focus it
|
||||
let target_output = self
|
||||
.outputs
|
||||
.iter()
|
||||
.enumerate()
|
||||
.find(|i| i.1.name == other_workspace.output)
|
||||
.unwrap()
|
||||
.1;
|
||||
let mut focus_cmd: String = "focus output ".to_string();
|
||||
focus_cmd.push_str(&target_output.name);
|
||||
self.send_command(&focus_cmd);
|
||||
}
|
||||
None => {}
|
||||
};
|
||||
|
||||
// Then we focus the workspace
|
||||
let mut focus_cmd: String = "workspace number ".to_string();
|
||||
focus_cmd.push_str(&workspace_index.to_string());
|
||||
self.send_command(&focus_cmd);
|
||||
}
|
||||
|
||||
fn focus_to_workspace_relative(&self, workspace_index: usize) {
|
||||
let current_workspace_index: usize = self.get_current_workspace_index();
|
||||
let focused_output_index = current_workspace_index / MAX_GROUP_WS;
|
||||
|
||||
let mut cmd: String = "workspace number ".to_string();
|
||||
let full_ws_name = format!("{}", focused_output_index * MAX_GROUP_WS + workspace_index);
|
||||
cmd.push_str(&full_ws_name);
|
||||
self.send_command(&cmd);
|
||||
}
|
||||
|
||||
pub fn focus_to_group(&self, group_index: usize) {
|
||||
let current_workspace_index: usize = self.get_current_workspace_index();
|
||||
let target_workspace_relative_index = current_workspace_index % MAX_GROUP_WS;
|
||||
|
||||
let target_workspace_index = group_index * MAX_GROUP_WS + target_workspace_relative_index;
|
||||
let full_ws_name = format!(
|
||||
"{}",
|
||||
group_index * MAX_GROUP_WS + target_workspace_relative_index
|
||||
);
|
||||
|
||||
// If the workspace already exists
|
||||
match self
|
||||
.workspaces
|
||||
.iter()
|
||||
.find(|w| w.num == target_workspace_index)
|
||||
{
|
||||
Some(_) => {
|
||||
let mut focus_cmd: String = "workspace number ".to_string();
|
||||
focus_cmd.push_str(&full_ws_name);
|
||||
self.send_command(&focus_cmd);
|
||||
}
|
||||
None => {
|
||||
let target_screen_index = match self
|
||||
.workspaces
|
||||
.iter()
|
||||
.find(|w| w.num / MAX_GROUP_WS == group_index)
|
||||
{
|
||||
// If other workspaces on the same group exists
|
||||
Some(other_workspace) => Some(
|
||||
self.outputs
|
||||
.iter()
|
||||
.enumerate()
|
||||
.find(|i| i.1.name == other_workspace.output)
|
||||
.unwrap()
|
||||
.0
|
||||
+ 1,
|
||||
),
|
||||
None => {
|
||||
// Or if the targeted output is currently connected
|
||||
if group_index > 0 && group_index <= self.outputs.len() {
|
||||
Some(group_index)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
match target_screen_index {
|
||||
// If we have to send it to another screen
|
||||
Some(target_screen_index) => {
|
||||
let target_output = &self.outputs[target_screen_index - 1];
|
||||
|
||||
let mut focus_cmd: String = "focus output ".to_string();
|
||||
focus_cmd.push_str(&target_output.name);
|
||||
self.send_command(&focus_cmd);
|
||||
}
|
||||
None => {}
|
||||
};
|
||||
// Then we focus the workspace
|
||||
let mut focus_cmd: String = "workspace number ".to_string();
|
||||
focus_cmd.push_str(&target_workspace_index.to_string());
|
||||
self.send_command(&focus_cmd);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn focus_all_outputs_to_workspace(&self, workspace_index: usize) {
|
||||
let current_output = self.get_current_output_name();
|
||||
|
||||
// Iterate on all outputs to focus on the given workspace
|
||||
for output in self.outputs.iter() {
|
||||
let mut cmd: String = "focus output ".to_string();
|
||||
cmd.push_str(output.name.as_str());
|
||||
self.send_command(&cmd);
|
||||
|
||||
self.focus_to_workspace(workspace_index);
|
||||
}
|
||||
|
||||
// Get back to currently focused output
|
||||
let mut cmd: String = "focus output ".to_string();
|
||||
cmd.push_str(¤t_output);
|
||||
self.send_command(&cmd);
|
||||
}
|
||||
|
||||
pub fn move_container_to_next_output(&self) {
|
||||
self.move_container_to_next_or_prev_output(false);
|
||||
}
|
||||
|
||||
pub fn move_container_to_prev_output(&self) {
|
||||
self.move_container_to_next_or_prev_output(true);
|
||||
}
|
||||
|
||||
fn move_container_to_next_or_prev_output(&self, go_to_prev: bool) {
|
||||
let focused_output_index = self.get_current_output_index();
|
||||
|
||||
let target_output = if go_to_prev {
|
||||
&self.outputs[(focused_output_index + self.outputs.len() - 1) % self.outputs.len()]
|
||||
} else {
|
||||
&self.outputs[(focused_output_index + 1) % self.outputs.len()]
|
||||
};
|
||||
|
||||
let workspaces = self.get_workspaces();
|
||||
let target_workspace = workspaces
|
||||
.iter()
|
||||
.find(|x| x.output == target_output.name && x.visible)
|
||||
.unwrap();
|
||||
let group_index = (target_workspace.num / MAX_GROUP_WS) as usize;
|
||||
let full_ws_name = format!(
|
||||
"{}",
|
||||
group_index * MAX_GROUP_WS + target_workspace.num % MAX_GROUP_WS
|
||||
);
|
||||
|
||||
// Move container to target workspace
|
||||
let mut cmd: String = "move container to workspace number ".to_string();
|
||||
cmd.push_str(&full_ws_name);
|
||||
self.send_command(&cmd);
|
||||
|
||||
// Focus that workspace to follow the container
|
||||
let mut cmd: String = "workspace number ".to_string();
|
||||
cmd.push_str(&full_ws_name);
|
||||
self.send_command(&cmd);
|
||||
}
|
||||
|
||||
pub fn move_workspace_group_to_next_output(&self) {
|
||||
self.move_workspace_group_to_next_or_prev_output(false);
|
||||
}
|
||||
|
||||
pub fn move_workspace_group_to_prev_output(&self) {
|
||||
self.move_workspace_group_to_next_or_prev_output(true);
|
||||
}
|
||||
|
||||
fn move_workspace_group_to_next_or_prev_output(&self, go_to_prev: bool) {
|
||||
let focused_output_index = self.get_current_output_index();
|
||||
|
||||
let target_output = if go_to_prev {
|
||||
&self.outputs[(focused_output_index + self.outputs.len() - 1) % self.outputs.len()]
|
||||
} else {
|
||||
&self.outputs[(focused_output_index + 1) % self.outputs.len()]
|
||||
};
|
||||
let current_workspace = self.get_current_workspace_index();
|
||||
let current_group_index = (current_workspace / MAX_GROUP_WS) as usize;
|
||||
for workspace in self.get_workspaces() {
|
||||
let ws_index = workspace.num / MAX_GROUP_WS;
|
||||
if ws_index == current_group_index {
|
||||
let cmd: String = format!("workspace number {}", workspace.num);
|
||||
self.send_command(&cmd);
|
||||
let cmd: String = format!("move workspace to {}", target_output.name);
|
||||
self.send_command(&cmd);
|
||||
}
|
||||
}
|
||||
let cmd: String = format!("workspace number {}", current_workspace);
|
||||
self.send_command(&cmd);
|
||||
}
|
||||
|
||||
pub fn focus_to_next_group(&self) {
|
||||
self.focus_to_next_or_prev_group(false);
|
||||
}
|
||||
|
||||
pub fn focus_to_prev_group(&self) {
|
||||
self.focus_to_next_or_prev_group(true);
|
||||
}
|
||||
|
||||
fn focus_to_next_or_prev_group(&self, go_to_prev: bool) {
|
||||
let current_workspace_index: usize = self.get_current_workspace_index();
|
||||
let focused_group_index = current_workspace_index / MAX_GROUP_WS;
|
||||
|
||||
let highest_group = self.workspaces.last().unwrap().num / MAX_GROUP_WS;
|
||||
let target_group;
|
||||
if go_to_prev {
|
||||
if focused_group_index == 0 {
|
||||
target_group = highest_group;
|
||||
} else {
|
||||
target_group = focused_group_index - 1;
|
||||
}
|
||||
} else {
|
||||
if focused_group_index >= highest_group {
|
||||
target_group = 0;
|
||||
} else {
|
||||
target_group = focused_group_index + 1;
|
||||
}
|
||||
};
|
||||
self.focus_to_group(target_group);
|
||||
}
|
||||
|
||||
pub fn init_workspaces(&self, workspace_index: usize) {
|
||||
let cmd_prefix: String = "focus output ".to_string();
|
||||
for output in self.outputs.iter().rev() {
|
||||
let mut cmd = cmd_prefix.clone();
|
||||
cmd.push_str(output.name.as_str());
|
||||
self.send_command(&cmd);
|
||||
|
||||
let mut cmd: String = "workspace number ".to_string();
|
||||
let full_ws_name = format!(
|
||||
"{}",
|
||||
(self.get_current_output_index() + 1) * MAX_GROUP_WS + workspace_index
|
||||
);
|
||||
cmd.push_str(&full_ws_name);
|
||||
self.send_command(&cmd);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn rearrange_workspaces(&self) {
|
||||
let focus_cmd_prefix: String = "workspace number ".to_string();
|
||||
let move_cmd_prefix: String = "move workspace to ".to_string();
|
||||
for workspace in self.workspaces.iter() {
|
||||
let mut focus_cmd = focus_cmd_prefix.clone();
|
||||
focus_cmd.push_str(&workspace.num.to_string());
|
||||
self.send_command(&focus_cmd);
|
||||
|
||||
let group_index =
|
||||
(workspace.num / MAX_GROUP_WS + self.outputs.len() - 1) % self.outputs.len();
|
||||
// if group_index <= self.outputs.len() - 1 {
|
||||
let mut move_cmd = move_cmd_prefix.clone();
|
||||
move_cmd.push_str(&self.outputs[group_index].name);
|
||||
self.send_command(&move_cmd);
|
||||
// }
|
||||
}
|
||||
}
|
||||
}
|
||||
390
src/main.rs
390
src/main.rs
|
|
@ -1,316 +1,112 @@
|
|||
extern crate byteorder;
|
||||
extern crate clap;
|
||||
extern crate serde_json;
|
||||
|
||||
use clap::{App, Arg, SubCommand};
|
||||
use std::env;
|
||||
use std::io::Cursor;
|
||||
use std::io::{Read, Write};
|
||||
use std::mem;
|
||||
use std::os::unix::net::UnixStream;
|
||||
use std::path::Path;
|
||||
use clap::{Args, Parser, Subcommand};
|
||||
use swaysome::SwaySome;
|
||||
|
||||
use byteorder::{LittleEndian, ReadBytesExt, WriteBytesExt};
|
||||
|
||||
const RUN_COMMAND: u32 = 0;
|
||||
const GET_WORKSPACES: u32 = 1;
|
||||
// const SUBSCRIBE: u32 = 2;
|
||||
const GET_OUTPUTS: u32 = 3;
|
||||
|
||||
fn get_stream() -> UnixStream {
|
||||
let socket_path = match env::var("I3SOCK") {
|
||||
Ok(val) => val,
|
||||
Err(_e) => {
|
||||
panic!("couldn't find i3/sway socket");
|
||||
}
|
||||
};
|
||||
|
||||
let socket = Path::new(&socket_path);
|
||||
|
||||
match UnixStream::connect(&socket) {
|
||||
Err(_) => panic!("couldn't connect to i3/sway socket"),
|
||||
Ok(stream) => stream,
|
||||
}
|
||||
#[derive(Parser, Debug)]
|
||||
#[clap(author, version, about = "Better multimonitor handling for sway", long_about = None)]
|
||||
#[clap(propagate_version = true)]
|
||||
struct Cli {
|
||||
#[clap(subcommand)]
|
||||
command: Command,
|
||||
}
|
||||
|
||||
fn send_msg(mut stream: &UnixStream, msg_type: u32, payload: &str) {
|
||||
let payload_length = payload.len() as u32;
|
||||
#[derive(Subcommand, Debug)]
|
||||
enum Command {
|
||||
#[clap(about = "Initialize the workspace groups for all the outputs")]
|
||||
Init(Index),
|
||||
|
||||
let mut msg_prefix: [u8; 6 * mem::size_of::<u8>() + 2 * mem::size_of::<u32>()] =
|
||||
*b"i3-ipc00000000";
|
||||
#[clap(about = "Move the focused container to another workspace on the same workspace group")]
|
||||
Move(Index),
|
||||
|
||||
msg_prefix[6..]
|
||||
.as_mut()
|
||||
.write_u32::<LittleEndian>(payload_length)
|
||||
.expect("Unable to write");
|
||||
#[clap(
|
||||
about = "Move the focused container to the same workspace index on another workspace group"
|
||||
)]
|
||||
MoveToGroup(Index),
|
||||
|
||||
msg_prefix[10..]
|
||||
.as_mut()
|
||||
.write_u32::<LittleEndian>(msg_type)
|
||||
.expect("Unable to write");
|
||||
#[clap(about = "Focus to another workspace on the same workspace group")]
|
||||
Focus(Index),
|
||||
|
||||
let mut msg: Vec<u8> = msg_prefix[..].to_vec();
|
||||
msg.extend(payload.as_bytes());
|
||||
#[clap(about = "Focus to workspace group")]
|
||||
FocusGroup(Index),
|
||||
|
||||
match stream.write_all(&msg[..]) {
|
||||
Err(_) => panic!("couldn't send message"),
|
||||
Ok(_) => {}
|
||||
}
|
||||
#[clap(about = "Focus to another workspace on all the outputs")]
|
||||
FocusAllOutputs(Index),
|
||||
|
||||
#[clap(about = "Move the focused container to the next output")]
|
||||
NextOutput,
|
||||
|
||||
#[clap(about = "Move the focused container to the previous output")]
|
||||
PrevOutput,
|
||||
|
||||
#[clap(about = "Move the focused workspace group to the next output")]
|
||||
WorkspaceGroupNextOutput,
|
||||
|
||||
#[clap(about = "Move the focused workspace group to the previous output")]
|
||||
WorkspaceGroupPrevOutput,
|
||||
|
||||
#[clap(about = "Move the focused container to the next group")]
|
||||
NextGroup,
|
||||
|
||||
#[clap(about = "Move the focused container to the previous group")]
|
||||
PrevGroup,
|
||||
|
||||
#[clap(
|
||||
about = "Rearrange already opened workspaces to the correct outputs, useful when plugging new monitors"
|
||||
)]
|
||||
RearrangeWorkspaces,
|
||||
}
|
||||
|
||||
fn send_command(stream: &UnixStream, command: &str) {
|
||||
eprint!("Sending command: '{}' - ", &command);
|
||||
send_msg(&stream, RUN_COMMAND, &command);
|
||||
check_success(&stream);
|
||||
}
|
||||
|
||||
fn read_msg(mut stream: &UnixStream) -> Result<String, &str> {
|
||||
let mut response_header: [u8; 14] = *b"uninitialized.";
|
||||
stream.read_exact(&mut response_header).unwrap();
|
||||
|
||||
if &response_header[0..6] == b"i3-ipc" {
|
||||
let mut v = Cursor::new(vec![
|
||||
response_header[6],
|
||||
response_header[7],
|
||||
response_header[8],
|
||||
response_header[9],
|
||||
]);
|
||||
let payload_length = v.read_u32::<LittleEndian>().unwrap();
|
||||
|
||||
let mut payload = vec![0; payload_length as usize];
|
||||
stream.read_exact(&mut payload[..]).unwrap();
|
||||
let payload_str = String::from_utf8(payload).unwrap();
|
||||
Ok(payload_str)
|
||||
} else {
|
||||
eprint!("Not an i3-icp packet, emptying the buffer: ");
|
||||
let mut v = vec![];
|
||||
stream.read_to_end(&mut v).unwrap();
|
||||
eprintln!("{:?}", v);
|
||||
Err("Unable to read i3-ipc packet")
|
||||
}
|
||||
}
|
||||
|
||||
fn check_success(stream: &UnixStream) {
|
||||
match read_msg(&stream) {
|
||||
Ok(msg) => {
|
||||
let r: Vec<serde_json::Value> = serde_json::from_str(&msg).unwrap();
|
||||
match r[0]["success"] {
|
||||
serde_json::Value::Bool(true) => eprintln!("Command successful"),
|
||||
_ => panic!("Command failed: {:#?}", r),
|
||||
}
|
||||
}
|
||||
Err(_) => panic!("Unable to read response"),
|
||||
};
|
||||
}
|
||||
|
||||
fn get_outputs(stream: &UnixStream) -> Vec<serde_json::Value> {
|
||||
send_msg(&stream, GET_OUTPUTS, "");
|
||||
let o = match read_msg(&stream) {
|
||||
Ok(msg) => msg,
|
||||
Err(_) => panic!("Unable to get outputs"),
|
||||
};
|
||||
serde_json::from_str(&o).unwrap()
|
||||
}
|
||||
|
||||
fn get_workspaces(stream: &UnixStream) -> Vec<serde_json::Value> {
|
||||
send_msg(&stream, GET_WORKSPACES, "");
|
||||
let ws = match read_msg(&stream) {
|
||||
Ok(msg) => msg,
|
||||
Err(_) => panic!("Unable to get current workspace"),
|
||||
};
|
||||
serde_json::from_str(&ws).unwrap()
|
||||
}
|
||||
|
||||
fn get_current_output_index(stream: &UnixStream) -> String {
|
||||
let outputs = get_outputs(&stream);
|
||||
|
||||
let focused_output_index = match outputs
|
||||
.iter()
|
||||
.position(|x| x["focused"] == serde_json::Value::Bool(true))
|
||||
{
|
||||
Some(i) => i,
|
||||
None => panic!("WTF! No focused output???"),
|
||||
};
|
||||
|
||||
format!("{}", focused_output_index)
|
||||
}
|
||||
|
||||
fn get_current_output_name(stream: &UnixStream) -> String {
|
||||
let outputs = get_outputs(&stream);
|
||||
|
||||
let focused_output_index = match outputs
|
||||
.iter()
|
||||
.find(|x| x["focused"] == serde_json::Value::Bool(true))
|
||||
{
|
||||
Some(i) => i["name"].as_str().unwrap(),
|
||||
None => panic!("WTF! No focused output???"),
|
||||
};
|
||||
|
||||
format!("{}", focused_output_index)
|
||||
}
|
||||
|
||||
fn move_container_to_workspace(stream: &UnixStream, workspace_name: &String) {
|
||||
let mut cmd: String = "move container to workspace number ".to_string();
|
||||
let full_ws_name = format!("{}{}", get_current_output_index(stream), &workspace_name)
|
||||
.parse::<i32>()
|
||||
.unwrap();
|
||||
cmd.push_str(&full_ws_name.to_string());
|
||||
send_command(&stream, &cmd);
|
||||
}
|
||||
|
||||
fn focus_to_workspace(stream: &UnixStream, workspace_name: &String) {
|
||||
let mut cmd: String = "workspace number ".to_string();
|
||||
let full_ws_name = format!("{}{}", get_current_output_index(stream), &workspace_name)
|
||||
.parse::<i32>()
|
||||
.unwrap();
|
||||
cmd.push_str(&full_ws_name.to_string());
|
||||
send_command(&stream, &cmd);
|
||||
}
|
||||
|
||||
fn focus_all_outputs_to_workspace(stream: &UnixStream, workspace_name: &String) {
|
||||
let current_output = get_current_output_name(stream);
|
||||
|
||||
// Iterate on all outputs to focus on the given workspace
|
||||
let outputs = get_outputs(&stream);
|
||||
for output in outputs.iter() {
|
||||
let mut cmd: String = "focus output ".to_string();
|
||||
cmd.push_str(&output["name"].as_str().unwrap());
|
||||
send_command(&stream, &cmd);
|
||||
|
||||
focus_to_workspace(&stream, &workspace_name);
|
||||
}
|
||||
|
||||
// Get back to currently focused output
|
||||
let mut cmd: String = "focus output ".to_string();
|
||||
cmd.push_str(¤t_output);
|
||||
send_command(&stream, &cmd);
|
||||
}
|
||||
|
||||
fn move_container_to_next_output(stream: &UnixStream) {
|
||||
move_container_to_next_or_prev_output(&stream, false);
|
||||
}
|
||||
|
||||
fn move_container_to_prev_output(stream: &UnixStream) {
|
||||
move_container_to_next_or_prev_output(&stream, true);
|
||||
}
|
||||
|
||||
fn move_container_to_next_or_prev_output(stream: &UnixStream, go_to_prev: bool) {
|
||||
let outputs = get_outputs(&stream);
|
||||
let focused_output_index = match outputs
|
||||
.iter()
|
||||
.position(|x| x["focused"] == serde_json::Value::Bool(true))
|
||||
{
|
||||
Some(i) => i,
|
||||
None => panic!("WTF! No focused output???"),
|
||||
};
|
||||
|
||||
let target_output;
|
||||
if go_to_prev {
|
||||
target_output = &outputs[(focused_output_index - 1 + &outputs.len()) % &outputs.len()];
|
||||
} else {
|
||||
target_output = &outputs[(focused_output_index + 1) % &outputs.len()];
|
||||
}
|
||||
|
||||
let workspaces = get_workspaces(&stream);
|
||||
let target_workspace = workspaces
|
||||
.iter()
|
||||
.filter(|x| {
|
||||
x["output"] == target_output["name"] && x["visible"] == serde_json::Value::Bool(true)
|
||||
})
|
||||
.next()
|
||||
.unwrap();
|
||||
|
||||
// Move container to target workspace
|
||||
let mut cmd: String = "move container to workspace number ".to_string();
|
||||
cmd.push_str(&target_workspace["num"].to_string());
|
||||
send_command(&stream, &cmd);
|
||||
|
||||
// Focus that workspace to follow the container
|
||||
let mut cmd: String = "workspace number ".to_string();
|
||||
cmd.push_str(&target_workspace["num"].to_string());
|
||||
send_command(&stream, &cmd);
|
||||
}
|
||||
|
||||
fn init_workspaces(stream: &UnixStream, workspace_name: &String) {
|
||||
let outputs = get_outputs(&stream);
|
||||
|
||||
let cmd_prefix: String = "focus output ".to_string();
|
||||
for output in outputs.iter().filter(|x| x["active"] == true).rev() {
|
||||
let mut cmd = cmd_prefix.clone();
|
||||
cmd.push_str(&output["name"].as_str().unwrap());
|
||||
send_command(&stream, &cmd);
|
||||
focus_to_workspace(&stream, &workspace_name);
|
||||
}
|
||||
#[derive(Args, Debug)]
|
||||
struct Index {
|
||||
#[clap(value_name = "index", help = "The workspace index to work with")]
|
||||
index: usize,
|
||||
}
|
||||
|
||||
fn main() {
|
||||
let matches = App::new("swaysome")
|
||||
.version("1.1.2")
|
||||
.author("Skia <skia@hya.sk>")
|
||||
.about("Better multimonitor handling for sway")
|
||||
.subcommand(
|
||||
SubCommand::with_name("init")
|
||||
.about("Initialize the workspaces for all the outputs")
|
||||
.arg(
|
||||
Arg::with_name("index")
|
||||
.help("The index to initialize with")
|
||||
.required(true)
|
||||
.takes_value(true),
|
||||
),
|
||||
)
|
||||
.subcommand(
|
||||
SubCommand::with_name("focus")
|
||||
.about("Focus to another workspace on the same output")
|
||||
.arg(
|
||||
Arg::with_name("index")
|
||||
.help("The index to focus on")
|
||||
.required(true)
|
||||
.takes_value(true),
|
||||
),
|
||||
)
|
||||
.subcommand(
|
||||
SubCommand::with_name("focus_all_outputs")
|
||||
.about("Focus to another workspace on all the outputs")
|
||||
.arg(
|
||||
Arg::with_name("index")
|
||||
.help("The index to focus on")
|
||||
.required(true)
|
||||
.takes_value(true),
|
||||
),
|
||||
)
|
||||
.subcommand(
|
||||
SubCommand::with_name("move")
|
||||
.about("Move the focused container to another workspace on the same output")
|
||||
.arg(
|
||||
Arg::with_name("index")
|
||||
.help("The index to move the container to")
|
||||
.required(true)
|
||||
.takes_value(true),
|
||||
),
|
||||
)
|
||||
.subcommand(
|
||||
SubCommand::with_name("next_output")
|
||||
.about("Move the focused container to the next output"),
|
||||
)
|
||||
.subcommand(
|
||||
SubCommand::with_name("prev_output")
|
||||
.about("Move the focused container to the previous output"),
|
||||
)
|
||||
.get_matches();
|
||||
let cli = Cli::parse();
|
||||
|
||||
let stream = get_stream();
|
||||
let swaysome = SwaySome::new();
|
||||
|
||||
if let Some(matches) = matches.subcommand_matches("init") {
|
||||
init_workspaces(&stream, &matches.value_of("index").unwrap().to_string());
|
||||
} else if let Some(matches) = matches.subcommand_matches("move") {
|
||||
move_container_to_workspace(&stream, &matches.value_of("index").unwrap().to_string());
|
||||
} else if let Some(matches) = matches.subcommand_matches("focus") {
|
||||
focus_to_workspace(&stream, &matches.value_of("index").unwrap().to_string());
|
||||
} else if let Some(matches) = matches.subcommand_matches("focus_all_outputs") {
|
||||
focus_all_outputs_to_workspace(&stream, &matches.value_of("index").unwrap().to_string());
|
||||
} else if let Some(_) = matches.subcommand_matches("next_output") {
|
||||
move_container_to_next_output(&stream);
|
||||
} else if let Some(_) = matches.subcommand_matches("prev_output") {
|
||||
move_container_to_prev_output(&stream);
|
||||
match &cli.command {
|
||||
Command::Init(action) => {
|
||||
swaysome.init_workspaces(action.index);
|
||||
}
|
||||
Command::Move(action) => {
|
||||
swaysome.move_container_to_workspace(action.index);
|
||||
}
|
||||
Command::MoveToGroup(action) => {
|
||||
swaysome.move_container_to_workspace_group(action.index);
|
||||
}
|
||||
Command::Focus(action) => {
|
||||
swaysome.focus_to_workspace(action.index);
|
||||
}
|
||||
Command::FocusGroup(action) => {
|
||||
swaysome.focus_to_group(action.index);
|
||||
}
|
||||
Command::FocusAllOutputs(action) => {
|
||||
swaysome.focus_all_outputs_to_workspace(action.index);
|
||||
}
|
||||
Command::NextOutput => {
|
||||
swaysome.move_container_to_next_output();
|
||||
}
|
||||
Command::PrevOutput => {
|
||||
swaysome.move_container_to_prev_output();
|
||||
}
|
||||
Command::WorkspaceGroupNextOutput => {
|
||||
swaysome.move_workspace_group_to_next_output();
|
||||
}
|
||||
Command::WorkspaceGroupPrevOutput => {
|
||||
swaysome.move_workspace_group_to_prev_output();
|
||||
}
|
||||
Command::NextGroup => {
|
||||
swaysome.focus_to_next_group();
|
||||
}
|
||||
Command::PrevGroup => {
|
||||
swaysome.focus_to_prev_group();
|
||||
}
|
||||
Command::RearrangeWorkspaces => {
|
||||
swaysome.rearrange_workspaces();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
155
swaysome.1
Normal file
155
swaysome.1
Normal file
|
|
@ -0,0 +1,155 @@
|
|||
.TH SWAYSOME "1" "Sep 2025" "2.3.2" "User Commands"
|
||||
.
|
||||
.
|
||||
.SH NAME
|
||||
swaysome - improve multi-output handling on sway
|
||||
.
|
||||
.
|
||||
.SH SYNOPSIS
|
||||
.B swaysome
|
||||
\fI\,[OPTIONS]\/\fR
|
||||
\fI\,<COMMAND>\/\fR
|
||||
.
|
||||
.
|
||||
.SH DESCRIPTION
|
||||
\fBswaysome\fR helps you configure \fBsway\fR to work a bit more
|
||||
like \fBAwesomeWM\fR (\fIhttps://awesomewm\.org/\fR). This means that
|
||||
"\fBworkspaces\fR" are namespaced in what are called "\fBworkspace groups\fR",
|
||||
and "\fBworkspace groups\fR" can be moved around the differents outputs easily.
|
||||
.P
|
||||
For example, with workspace 11 on the first output, and workspace 21 on the
|
||||
second output, triggering the `swaysome focus 1` shortcut to get workspace
|
||||
1 would lead you to workspace 11 if your focus is on the first output, and
|
||||
workspace 21 is the focus is on the second one.
|
||||
.P
|
||||
By default, `swaysome init` will create a \fBworkspace group\fR per active
|
||||
output, but you may create other groups while working, by either triggering
|
||||
`swaysome focus-group <new-number>` and opening a new window, or sending an
|
||||
existing window to it first with `swaysome move-to-group <new-number>`.
|
||||
.P
|
||||
Here is a common use-case for this:
|
||||
|
||||
.EX
|
||||
\fBoutput-1\fR:
|
||||
\fBworkspace group 1\fR:
|
||||
workspace 11: chats
|
||||
workspace 12: emails
|
||||
\fBoutput-2\fR:
|
||||
\fBworkspace group 2\fR:
|
||||
workspace 21: IDE for first project
|
||||
workspace 22: browser for first project
|
||||
workspace 23: terminals for first project
|
||||
\fBworkspace group 3\fR:
|
||||
workspace 31: IDE for second project
|
||||
workspace 32: browser for second project
|
||||
workspace 33: terminals for second project
|
||||
.EE
|
||||
|
||||
.P
|
||||
That way, when \fBoutput-2\fR is focused on \fBworkspace group 2\fR, be
|
||||
it workspace 21 or 22, the quick `$mod+<number>` (bound to `swaysome focus
|
||||
<number>`) shortcut won't leave \fBworkspace group 2\fR, allowing you to open
|
||||
multiple projects in parallel without the hassle of manually remembering how to
|
||||
namespace them.
|
||||
.P
|
||||
In that situation, suppose you plug in a new output, `output-3`, you may then
|
||||
want to focus \fBworkspace group 3\fR to send it to `output-3`: this is simply
|
||||
done by typing the shortcuts `$mod+Alt+3` (`swaysome focus-group 3`) then
|
||||
`$mod+Alt+o` (`swaysome workspace-group-next-output`)\.
|
||||
.P
|
||||
\fBswaysome\fR may also work with \fBi3\fR, but this is untested. Patches welcome if needed.
|
||||
.P
|
||||
\fBswaysome\fR should be compatible with \fBsworkstyle\fR (\fIhttps://lib\.rs/crates/sworkstyle\fR).
|
||||
If this is broken, please report a bug.
|
||||
.
|
||||
.
|
||||
.SH USAGE
|
||||
.P
|
||||
If you installed \fBswaysome\fR from your distribution's package manager, it
|
||||
should have provided your system with the default configuration file. In that
|
||||
case, you can just include from your \fBsway\fR configuration by appending the
|
||||
following to it:
|
||||
|
||||
include /etc/sway/config.d/swaysome.conf
|
||||
|
||||
Otherwise, if installing by any other way, copy the \fBswaysome.conf\fR file
|
||||
in \fB~/.config/sway/config.d/swaysome.conf\fR, then append your \fBsway\fR
|
||||
configuration with this:
|
||||
|
||||
include ~/.config/sway/config.d/sway.conf
|
||||
|
||||
On next startup of `sway`, you should end-up with workspaces from `1` to `0`,
|
||||
prefixed with a screen index, giving you workspace `11` on the first screen, and
|
||||
workspace `21` on the second one, both accessible with shortcut `$mod+1` when
|
||||
focused on the right output.
|
||||
.
|
||||
.
|
||||
.SH COMMANDS
|
||||
.TP
|
||||
\fBinit\fR \fIINDEX\fR
|
||||
Initialize the workspace groups for all the outputs on workspace \fIINDEX\fR.
|
||||
This command simply walks through every screen to initialize a prefixed
|
||||
workspace. It does it backwards so that you end-up focused on the first screen,
|
||||
as usual.
|
||||
.TP
|
||||
\fBmove\fR \fIINDEX\fR
|
||||
Move the focused container to workspace \fIINDEX\fR (stay in the same workspace group)
|
||||
.TP
|
||||
\fBmove-to-group\fR \fIINDEX\fR
|
||||
Move the focused container to workspace group \fIINDEX\fR (keep the same workspace index)
|
||||
.TP
|
||||
\fBfocus\fR \fIINDEX\fR
|
||||
Focus to workspace \fIINDEX\fR (stay in the same workspace group)
|
||||
.TP
|
||||
\fBfocus-group\fR \fIINDEX\fR
|
||||
Focus to workspace group \fIINDEX\fR (keep the same workspace index)
|
||||
.TP
|
||||
\fBfocus-all-outputs\fR \fIINDEX\fR
|
||||
Focus to workspace \fIINDEX\fR on all the outputs at once (no keyboard shortcut bound by default for this function)
|
||||
.TP
|
||||
\fBnext-output\fR
|
||||
Move the focused container to the next output
|
||||
.TP
|
||||
\fBprev-output\fR
|
||||
Move the focused container to the previous output
|
||||
.TP
|
||||
\fBworkspace-group-next-output\fR
|
||||
Move the focused workspace group to the next output
|
||||
.TP
|
||||
\fBworkspace-group-prev-output\fR
|
||||
Move the focused workspace group to the previous output
|
||||
.TP
|
||||
\fBnext-group\fR
|
||||
Move the focused container to the next group
|
||||
.TP
|
||||
\fBprev-group\fR
|
||||
Move the focused container to the previous group
|
||||
.TP
|
||||
\fBrearrange-workspaces\fR
|
||||
Rearrange already opened workspaces to the correct outputs, useful when plugging new monitors
|
||||
.TP
|
||||
\fBhelp\fR
|
||||
Print this message or the help of the given subcommand(s)
|
||||
.
|
||||
.
|
||||
.SH OPTIONS
|
||||
.TP
|
||||
\fB-h\fR, \fB--help\fR
|
||||
Print help
|
||||
.TP
|
||||
\fB-V\fR, \fB--version\fR
|
||||
Print version
|
||||
.
|
||||
.
|
||||
.SH "VERSION"
|
||||
2.3.2
|
||||
.
|
||||
.
|
||||
.SH "HOMEPAGE"
|
||||
\fIhttps://gitlab.com/hyask/swaysome\fP
|
||||
.sp
|
||||
Please report any bug or feature requests on the bug tracker.
|
||||
.
|
||||
.
|
||||
.SH AUTHORS
|
||||
Florent 'Skia' Jacquet <\fIskia@hya.sk\fP>
|
||||
66
swaysome.conf
Normal file
66
swaysome.conf
Normal file
|
|
@ -0,0 +1,66 @@
|
|||
# Use (un)bindcode or (un)bindsym, depending on what you used in your main sway config file.
|
||||
# The `--no-warn` setting is only added to shortcuts that exist in the default config. You may want to add or remove
|
||||
# that flag on some bindings depending on your config.
|
||||
|
||||
|
||||
# Change focus between workspaces
|
||||
bindsym --no-warn $mod+1 exec "swaysome focus 1"
|
||||
bindsym --no-warn $mod+2 exec "swaysome focus 2"
|
||||
bindsym --no-warn $mod+3 exec "swaysome focus 3"
|
||||
bindsym --no-warn $mod+4 exec "swaysome focus 4"
|
||||
bindsym --no-warn $mod+5 exec "swaysome focus 5"
|
||||
bindsym --no-warn $mod+6 exec "swaysome focus 6"
|
||||
bindsym --no-warn $mod+7 exec "swaysome focus 7"
|
||||
bindsym --no-warn $mod+8 exec "swaysome focus 8"
|
||||
bindsym --no-warn $mod+9 exec "swaysome focus 9"
|
||||
bindsym --no-warn $mod+0 exec "swaysome focus 0"
|
||||
|
||||
# Move containers between workspaces
|
||||
bindsym --no-warn $mod+Shift+1 exec "swaysome move 1"
|
||||
bindsym --no-warn $mod+Shift+2 exec "swaysome move 2"
|
||||
bindsym --no-warn $mod+Shift+3 exec "swaysome move 3"
|
||||
bindsym --no-warn $mod+Shift+4 exec "swaysome move 4"
|
||||
bindsym --no-warn $mod+Shift+5 exec "swaysome move 5"
|
||||
bindsym --no-warn $mod+Shift+6 exec "swaysome move 6"
|
||||
bindsym --no-warn $mod+Shift+7 exec "swaysome move 7"
|
||||
bindsym --no-warn $mod+Shift+8 exec "swaysome move 8"
|
||||
bindsym --no-warn $mod+Shift+9 exec "swaysome move 9"
|
||||
bindsym --no-warn $mod+Shift+0 exec "swaysome move 0"
|
||||
|
||||
# Focus workspace groups
|
||||
bindsym $mod+Alt+1 exec "swaysome focus-group 1"
|
||||
bindsym $mod+Alt+2 exec "swaysome focus-group 2"
|
||||
bindsym $mod+Alt+3 exec "swaysome focus-group 3"
|
||||
bindsym $mod+Alt+4 exec "swaysome focus-group 4"
|
||||
bindsym $mod+Alt+5 exec "swaysome focus-group 5"
|
||||
bindsym $mod+Alt+6 exec "swaysome focus-group 6"
|
||||
bindsym $mod+Alt+7 exec "swaysome focus-group 7"
|
||||
bindsym $mod+Alt+8 exec "swaysome focus-group 8"
|
||||
bindsym $mod+Alt+9 exec "swaysome focus-group 9"
|
||||
bindsym $mod+Alt+0 exec "swaysome focus-group 0"
|
||||
|
||||
# Move containers to other workspace groups
|
||||
bindsym $mod+Alt+Shift+1 exec "swaysome move-to-group 1"
|
||||
bindsym $mod+Alt+Shift+2 exec "swaysome move-to-group 2"
|
||||
bindsym $mod+Alt+Shift+3 exec "swaysome move-to-group 3"
|
||||
bindsym $mod+Alt+Shift+4 exec "swaysome move-to-group 4"
|
||||
bindsym $mod+Alt+Shift+5 exec "swaysome move-to-group 5"
|
||||
bindsym $mod+Alt+Shift+6 exec "swaysome move-to-group 6"
|
||||
bindsym $mod+Alt+Shift+7 exec "swaysome move-to-group 7"
|
||||
bindsym $mod+Alt+Shift+8 exec "swaysome move-to-group 8"
|
||||
bindsym $mod+Alt+Shift+9 exec "swaysome move-to-group 9"
|
||||
bindsym $mod+Alt+Shift+0 exec "swaysome move-to-group 0"
|
||||
|
||||
# Move focused container to next output
|
||||
bindsym $mod+o exec "swaysome next-output"
|
||||
# Move focused container to previous output
|
||||
bindsym $mod+Shift+o exec "swaysome prev-output"
|
||||
|
||||
# Move focused workspace group to next output
|
||||
bindsym $mod+Alt+o exec "swaysome workspace-group-next-output"
|
||||
# Move focused workspace group to previous output
|
||||
bindsym $mod+Alt+Shift+o exec "swaysome workspace-group-prev-output"
|
||||
|
||||
# Init workspaces for every screen
|
||||
exec "swaysome init 1"
|
||||
|
||||
988
tests/integration.rs
Normal file
988
tests/integration.rs
Normal file
|
|
@ -0,0 +1,988 @@
|
|||
// extern crate assert_json_diff;
|
||||
|
||||
use assert_json_diff::{assert_json_eq, assert_json_include};
|
||||
use serde_json::json;
|
||||
use swaysome::SwaySome;
|
||||
|
||||
mod utils;
|
||||
use utils::*;
|
||||
|
||||
#[test]
|
||||
fn test_init_three_outputs() {
|
||||
let sway = Sway::start();
|
||||
let swaysome = SwaySome::new_from_socket(&sway.sock).expect("SwaySome couldn't initialize");
|
||||
assert_json_include!(actual: swaysome.get_tree(), expected: json!({
|
||||
"nodes": [
|
||||
{},
|
||||
{"type": "output", "name": "HEADLESS-1", "current_mode": {"height": 270, "width": 480}, "nodes": [
|
||||
{"type": "workspace", "name": "1", "num": 1, "focused": true, "nodes": []},
|
||||
]},
|
||||
{"type": "output", "name": "HEADLESS-2", "current_mode": {"height": 1080, "width": 1920}, "nodes": [
|
||||
{"type": "workspace", "name": "2", "num": 2, "focused": false, "nodes": []},
|
||||
]},
|
||||
{"type": "output", "name": "HEADLESS-3", "current_mode": {"height": 1440, "width": 2560}, "nodes": [
|
||||
{"type": "workspace", "name": "3", "num": 3, "focused": false, "nodes": []},
|
||||
]},
|
||||
]}));
|
||||
|
||||
eprintln!("Init 1");
|
||||
swaysome.init_workspaces(1);
|
||||
assert_json_include!(actual: swaysome.get_tree(), expected: json!({
|
||||
"nodes": [
|
||||
{},
|
||||
{"type": "output", "name": "HEADLESS-1", "current_mode": {"height": 270, "width": 480}, "nodes": [
|
||||
{"type": "workspace", "name": "11", "num": 11, "focused": true, "nodes": []},
|
||||
]},
|
||||
{"type": "output", "name": "HEADLESS-2", "current_mode": {"height": 1080, "width": 1920}, "nodes": [
|
||||
{"type": "workspace", "name": "21", "num": 21, "focused": false, "nodes": []},
|
||||
]},
|
||||
{"type": "output", "name": "HEADLESS-3", "current_mode": {"height": 1440, "width": 2560}, "nodes": [
|
||||
{"type": "workspace", "name": "31", "num": 31, "focused": false, "nodes": []},
|
||||
]},
|
||||
]}));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_three_outputs_moving_around_same_workspace_group() {
|
||||
let sway = Sway::start();
|
||||
let swaysome = SwaySome::new_from_socket(&sway.sock).expect("SwaySome couldn't initialize");
|
||||
|
||||
swaysome.init_workspaces(1);
|
||||
sway.spawn_some_apps();
|
||||
|
||||
assert_json_include!(actual: swaysome.get_tree(), expected: json!({
|
||||
"nodes": [
|
||||
{},
|
||||
{"type": "output", "name": "HEADLESS-1", "current_mode": {"height": 270, "width": 480}, "nodes": [
|
||||
{"type": "workspace", "name": "11", "num": 11, "focused": false, "nodes": [
|
||||
{"name": "TERM1", "focused": false},
|
||||
{"name": "TERM2", "focused": false},
|
||||
{"name": "TERM3", "focused": true},
|
||||
]},
|
||||
]},
|
||||
{"type": "output", "name": "HEADLESS-2", "current_mode": {"height": 1080, "width": 1920}, "nodes": [
|
||||
{"type": "workspace", "name": "21", "num": 21, "focused": false, "nodes": []},
|
||||
]},
|
||||
{"type": "output", "name": "HEADLESS-3", "current_mode": {"height": 1440, "width": 2560}, "nodes": [
|
||||
{"type": "workspace", "name": "31", "num": 31, "focused": false, "nodes": []},
|
||||
]},
|
||||
]}));
|
||||
|
||||
eprintln!("Move TERM3 to 2");
|
||||
swaysome.move_container_to_workspace(2);
|
||||
assert_json_include!(actual: swaysome.get_tree(), expected: json!({
|
||||
"nodes": [
|
||||
{},
|
||||
{"type": "output", "name": "HEADLESS-1", "current_mode": {"height": 270, "width": 480}, "nodes": [
|
||||
{"type": "workspace", "name": "11", "num": 11, "focused": false, "nodes": [
|
||||
{"name": "TERM1", "focused": false},
|
||||
{"name": "TERM2", "focused": true},
|
||||
]},
|
||||
{"type": "workspace", "name": "12", "num": 12, "focused": false, "nodes": [
|
||||
{"name": "TERM3", "focused": false},
|
||||
]},
|
||||
]},
|
||||
{"type": "output", "name": "HEADLESS-2", "current_mode": {"height": 1080, "width": 1920}, "nodes": [
|
||||
{"type": "workspace", "name": "21", "num": 21, "focused": false, "nodes": []},
|
||||
]},
|
||||
{"type": "output", "name": "HEADLESS-3", "current_mode": {"height": 1440, "width": 2560}, "nodes": [
|
||||
{"type": "workspace", "name": "31", "num": 31, "focused": false, "nodes": []},
|
||||
]},
|
||||
]}));
|
||||
|
||||
eprintln!("Focus to 2");
|
||||
swaysome.focus_to_workspace(2);
|
||||
assert_json_include!(actual: swaysome.get_tree(), expected: json!({
|
||||
"nodes": [
|
||||
{},
|
||||
{"type": "output", "name": "HEADLESS-1", "current_mode": {"height": 270, "width": 480}, "nodes": [
|
||||
{"type": "workspace", "name": "11", "num": 11, "focused": false, "nodes": [
|
||||
{"name": "TERM1", "focused": false},
|
||||
{"name": "TERM2", "focused": false},
|
||||
]},
|
||||
{"type": "workspace", "name": "12", "num": 12, "focused": false, "nodes": [
|
||||
{"name": "TERM3", "focused": true},
|
||||
]},
|
||||
]},
|
||||
{"type": "output", "name": "HEADLESS-2", "current_mode": {"height": 1080, "width": 1920}, "nodes": [
|
||||
{"type": "workspace", "name": "21", "num": 21, "focused": false, "nodes": []},
|
||||
]},
|
||||
{"type": "output", "name": "HEADLESS-3", "current_mode": {"height": 1440, "width": 2560}, "nodes": [
|
||||
{"type": "workspace", "name": "31", "num": 31, "focused": false, "nodes": []},
|
||||
]},
|
||||
]}));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_three_outputs_moving_around_across_workspace_groups() {
|
||||
let sway = Sway::start();
|
||||
let swaysome = SwaySome::new_from_socket(&sway.sock).expect("SwaySome couldn't initialize");
|
||||
|
||||
swaysome.init_workspaces(1);
|
||||
sway.spawn_some_apps();
|
||||
|
||||
assert_json_include!(actual: swaysome.get_tree(), expected: json!({
|
||||
"nodes": [
|
||||
{},
|
||||
{"type": "output", "name": "HEADLESS-1", "current_mode": {"height": 270, "width": 480}, "nodes": [
|
||||
{"type": "workspace", "name": "11", "num": 11, "focused": false, "nodes": [
|
||||
{"name": "TERM1", "focused": false},
|
||||
{"name": "TERM2", "focused": false},
|
||||
{"name": "TERM3", "focused": true},
|
||||
]},
|
||||
]},
|
||||
{"type": "output", "name": "HEADLESS-2", "current_mode": {"height": 1080, "width": 1920}, "nodes": [
|
||||
{"type": "workspace", "name": "21", "num": 21, "focused": false, "nodes": []},
|
||||
]},
|
||||
{"type": "output", "name": "HEADLESS-3", "current_mode": {"height": 1440, "width": 2560}, "nodes": [
|
||||
{"type": "workspace", "name": "31", "num": 31, "focused": false, "nodes": []},
|
||||
]},
|
||||
]}));
|
||||
|
||||
eprintln!("Move TERM3 to group 2");
|
||||
swaysome.move_container_to_workspace_group(2);
|
||||
assert_json_include!(actual: swaysome.get_tree(), expected: json!({
|
||||
"nodes": [
|
||||
{},
|
||||
{"type": "output", "name": "HEADLESS-1", "current_mode": {"height": 270, "width": 480}, "nodes": [
|
||||
{"type": "workspace", "name": "11", "num": 11, "focused": false, "nodes": [
|
||||
{"name": "TERM1", "focused": false},
|
||||
{"name": "TERM2", "focused": true},
|
||||
]},
|
||||
]},
|
||||
{"type": "output", "name": "HEADLESS-2", "current_mode": {"height": 1080, "width": 1920}, "nodes": [
|
||||
{"type": "workspace", "name": "21", "num": 21, "focused": false, "nodes": [
|
||||
{"name": "TERM3", "focused": false},
|
||||
]},
|
||||
]},
|
||||
{"type": "output", "name": "HEADLESS-3", "current_mode": {"height": 1440, "width": 2560}, "nodes": [
|
||||
{"type": "workspace", "name": "31", "num": 31, "focused": false, "nodes": []},
|
||||
]},
|
||||
]}));
|
||||
|
||||
eprintln!("Focus to group 2");
|
||||
swaysome.focus_to_group(2);
|
||||
assert_json_include!(actual: swaysome.get_tree(), expected: json!({
|
||||
"nodes": [
|
||||
{},
|
||||
{"type": "output", "name": "HEADLESS-1", "current_mode": {"height": 270, "width": 480}, "nodes": [
|
||||
{"type": "workspace", "name": "11", "num": 11, "focused": false, "nodes": [
|
||||
{"name": "TERM1", "focused": false},
|
||||
{"name": "TERM2", "focused": false},
|
||||
]},
|
||||
]},
|
||||
{"type": "output", "name": "HEADLESS-2", "current_mode": {"height": 1080, "width": 1920}, "nodes": [
|
||||
{"type": "workspace", "name": "21", "num": 21, "focused": false, "nodes": [
|
||||
{"name": "TERM3", "focused": true},
|
||||
]},
|
||||
]},
|
||||
{"type": "output", "name": "HEADLESS-3", "current_mode": {"height": 1440, "width": 2560}, "nodes": [
|
||||
{"type": "workspace", "name": "31", "num": 31, "focused": false, "nodes": []},
|
||||
]},
|
||||
]}));
|
||||
// HEADLESS-3 is still empty
|
||||
assert_json_eq!(
|
||||
swaysome.get_tree()["nodes"][3]["nodes"][0]["nodes"],
|
||||
json!([])
|
||||
);
|
||||
|
||||
eprintln!("Focus to group 1");
|
||||
swaysome.focus_to_group(1);
|
||||
assert_json_include!(actual: swaysome.get_tree(), expected: json!({
|
||||
"nodes": [
|
||||
{},
|
||||
{"type": "output", "name": "HEADLESS-1", "current_mode": {"height": 270, "width": 480}, "nodes": [
|
||||
{"type": "workspace", "name": "11", "num": 11, "focused": false, "nodes": [
|
||||
{"name": "TERM1", "focused": false},
|
||||
{"name": "TERM2", "focused": true},
|
||||
]},
|
||||
]},
|
||||
{"type": "output", "name": "HEADLESS-2", "current_mode": {"height": 1080, "width": 1920}, "nodes": [
|
||||
{"type": "workspace", "name": "21", "num": 21, "focused": false, "nodes": [
|
||||
{"name": "TERM3", "focused": false},
|
||||
]},
|
||||
]},
|
||||
{"type": "output", "name": "HEADLESS-3", "current_mode": {"height": 1440, "width": 2560}, "nodes": [
|
||||
{"type": "workspace", "name": "31", "num": 31, "focused": false, "nodes": []},
|
||||
]},
|
||||
]}));
|
||||
// HEADLESS-3 is still empty
|
||||
assert_json_eq!(
|
||||
swaysome.get_tree()["nodes"][3]["nodes"][0]["nodes"],
|
||||
json!([])
|
||||
);
|
||||
|
||||
eprintln!("Move TERM2 to group 2");
|
||||
swaysome.move_container_to_workspace_group(3);
|
||||
assert_json_include!(actual: swaysome.get_tree(), expected: json!({
|
||||
"nodes": [
|
||||
{},
|
||||
{"type": "output", "name": "HEADLESS-1", "current_mode": {"height": 270, "width": 480}, "nodes": [
|
||||
{"type": "workspace", "name": "11", "num": 11, "focused": false, "nodes": [
|
||||
{"name": "TERM1", "focused": true},
|
||||
]},
|
||||
]},
|
||||
{"type": "output", "name": "HEADLESS-2", "current_mode": {"height": 1080, "width": 1920}, "nodes": [
|
||||
{"type": "workspace", "name": "21", "num": 21, "focused": false, "nodes": [
|
||||
{"name": "TERM3", "focused": false},
|
||||
]},
|
||||
]},
|
||||
{"type": "output", "name": "HEADLESS-3", "current_mode": {"height": 1440, "width": 2560}, "nodes": [
|
||||
{"type": "workspace", "name": "31", "num": 31, "focused": false, "nodes": [
|
||||
{"name": "TERM2", "focused": false},
|
||||
]},
|
||||
]},
|
||||
]}));
|
||||
|
||||
eprintln!("Focus all to 4");
|
||||
swaysome.focus_all_outputs_to_workspace(4);
|
||||
assert_json_include!(actual: swaysome.get_tree(), expected: json!({
|
||||
"nodes": [
|
||||
{},
|
||||
{"type": "output", "name": "HEADLESS-1", "current_mode": {"height": 270, "width": 480}, "nodes": [
|
||||
{"type": "workspace", "name": "11", "num": 11, "focused": false, "nodes": [
|
||||
{"name": "TERM1", "focused": false},
|
||||
]},
|
||||
{"type": "workspace", "name": "14", "num": 14, "focused": true, "nodes": []},
|
||||
]},
|
||||
{"type": "output", "name": "HEADLESS-2", "current_mode": {"height": 1080, "width": 1920}, "nodes": [
|
||||
{"type": "workspace", "name": "21", "num": 21, "focused": false, "nodes": [
|
||||
{"name": "TERM3", "focused": false},
|
||||
]},
|
||||
{"type": "workspace", "name": "24", "num": 24, "focused": false, "nodes": []},
|
||||
]},
|
||||
{"type": "output", "name": "HEADLESS-3", "current_mode": {"height": 1440, "width": 2560}, "nodes": [
|
||||
{"type": "workspace", "name": "31", "num": 31, "focused": false, "nodes": [
|
||||
{"name": "TERM2", "focused": false},
|
||||
]},
|
||||
{"type": "workspace", "name": "34", "num": 34, "focused": false, "nodes": []},
|
||||
]},
|
||||
]}));
|
||||
|
||||
eprintln!("Focus all back to 1");
|
||||
swaysome.focus_all_outputs_to_workspace(1);
|
||||
assert_json_include!(actual: swaysome.get_tree(), expected: json!({
|
||||
"nodes": [
|
||||
{},
|
||||
{"type": "output", "name": "HEADLESS-1", "current_mode": {"height": 270, "width": 480}, "nodes": [
|
||||
{"type": "workspace", "name": "11", "num": 11, "focused": false, "nodes": [
|
||||
{"name": "TERM1", "focused": true},
|
||||
]},
|
||||
]},
|
||||
{"type": "output", "name": "HEADLESS-2", "current_mode": {"height": 1080, "width": 1920}, "nodes": [
|
||||
{"type": "workspace", "name": "21", "num": 21, "focused": false, "nodes": [
|
||||
{"name": "TERM3", "focused": false},
|
||||
]},
|
||||
]},
|
||||
{"type": "output", "name": "HEADLESS-3", "current_mode": {"height": 1440, "width": 2560}, "nodes": [
|
||||
{"type": "workspace", "name": "31", "num": 31, "focused": false, "nodes": [
|
||||
{"name": "TERM2", "focused": false},
|
||||
]},
|
||||
]},
|
||||
]}));
|
||||
|
||||
// shadow and re-init swaysome to get up-to-date internals (outputs, workspaces)
|
||||
// XXX this is more of a hack than anything else. Ideally, swaysome would never have out-of-date internals
|
||||
let swaysome = SwaySome::new_from_socket(&sway.sock).expect("SwaySome couldn't initialize");
|
||||
|
||||
eprintln!("Focus to prev group");
|
||||
swaysome.focus_to_prev_group();
|
||||
assert_json_include!(actual: swaysome.get_tree(), expected: json!({
|
||||
"nodes": [
|
||||
{},
|
||||
{"type": "output", "name": "HEADLESS-1", "current_mode": {"height": 270, "width": 480}, "nodes": [
|
||||
{"type": "workspace", "name": "1", "num": 1, "focused": true, "nodes": []},
|
||||
{"type": "workspace", "name": "11", "num": 11, "focused": false, "nodes": [
|
||||
{"name": "TERM1", "focused": false},
|
||||
]},
|
||||
]},
|
||||
{"type": "output", "name": "HEADLESS-2", "current_mode": {"height": 1080, "width": 1920}, "nodes": [
|
||||
{"type": "workspace", "name": "21", "num": 21, "focused": false, "nodes": [
|
||||
{"name": "TERM3", "focused": false},
|
||||
]},
|
||||
]},
|
||||
{"type": "output", "name": "HEADLESS-3", "current_mode": {"height": 1440, "width": 2560}, "nodes": [
|
||||
{"type": "workspace", "name": "31", "num": 31, "focused": false, "nodes": [
|
||||
{"name": "TERM2", "focused": false},
|
||||
]},
|
||||
]},
|
||||
]}));
|
||||
|
||||
eprintln!("Focus to prev group again");
|
||||
swaysome.focus_to_prev_group();
|
||||
assert_json_include!(actual: swaysome.get_tree(), expected: json!({
|
||||
"nodes": [
|
||||
{},
|
||||
{"type": "output", "name": "HEADLESS-1", "current_mode": {"height": 270, "width": 480}, "nodes": [
|
||||
{"type": "workspace", "name": "1", "num": 1, "focused": false, "nodes": []},
|
||||
{"type": "workspace", "name": "11", "num": 11, "focused": false, "nodes": [
|
||||
{"name": "TERM1", "focused": false},
|
||||
]},
|
||||
]},
|
||||
{"type": "output", "name": "HEADLESS-2", "current_mode": {"height": 1080, "width": 1920}, "nodes": [
|
||||
{"type": "workspace", "name": "21", "num": 21, "focused": false, "nodes": [
|
||||
{"name": "TERM3", "focused": false},
|
||||
]},
|
||||
]},
|
||||
{"type": "output", "name": "HEADLESS-3", "current_mode": {"height": 1440, "width": 2560}, "nodes": [
|
||||
{"type": "workspace", "name": "31", "num": 31, "focused": false, "nodes": [
|
||||
{"name": "TERM2", "focused": true},
|
||||
]},
|
||||
]},
|
||||
]}));
|
||||
|
||||
eprintln!("Focus to next group");
|
||||
swaysome.focus_to_next_group();
|
||||
assert_json_include!(actual: swaysome.get_tree(), expected: json!({
|
||||
"nodes": [
|
||||
{},
|
||||
{"type": "output", "name": "HEADLESS-1", "current_mode": {"height": 270, "width": 480}, "nodes": [
|
||||
{"type": "workspace", "name": "1", "num": 1, "focused": true, "nodes": []},
|
||||
{"type": "workspace", "name": "11", "num": 11, "focused": false, "nodes": [
|
||||
{"name": "TERM1", "focused": false},
|
||||
]},
|
||||
]},
|
||||
{"type": "output", "name": "HEADLESS-2", "current_mode": {"height": 1080, "width": 1920}, "nodes": [
|
||||
{"type": "workspace", "name": "21", "num": 21, "focused": false, "nodes": [
|
||||
{"name": "TERM3", "focused": false},
|
||||
]},
|
||||
]},
|
||||
{"type": "output", "name": "HEADLESS-3", "current_mode": {"height": 1440, "width": 2560}, "nodes": [
|
||||
{"type": "workspace", "name": "31", "num": 31, "focused": false, "nodes": [
|
||||
{"name": "TERM2", "focused": false},
|
||||
]},
|
||||
]},
|
||||
]}));
|
||||
|
||||
eprintln!("Focus to next group again");
|
||||
swaysome.focus_to_next_group();
|
||||
assert_json_include!(actual: swaysome.get_tree(), expected: json!({
|
||||
"nodes": [
|
||||
{},
|
||||
{"type": "output", "name": "HEADLESS-1", "current_mode": {"height": 270, "width": 480}, "nodes": [
|
||||
{"type": "workspace", "name": "11", "num": 11, "focused": false, "nodes": [
|
||||
{"name": "TERM1", "focused": true},
|
||||
]},
|
||||
]},
|
||||
{"type": "output", "name": "HEADLESS-2", "current_mode": {"height": 1080, "width": 1920}, "nodes": [
|
||||
{"type": "workspace", "name": "21", "num": 21, "focused": false, "nodes": [
|
||||
{"name": "TERM3", "focused": false},
|
||||
]},
|
||||
]},
|
||||
{"type": "output", "name": "HEADLESS-3", "current_mode": {"height": 1440, "width": 2560}, "nodes": [
|
||||
{"type": "workspace", "name": "31", "num": 31, "focused": false, "nodes": [
|
||||
{"name": "TERM2", "focused": false},
|
||||
]},
|
||||
]},
|
||||
]}));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_three_outputs_moving_around_across_outputs() {
|
||||
let sway = Sway::start();
|
||||
let swaysome = SwaySome::new_from_socket(&sway.sock).expect("SwaySome couldn't initialize");
|
||||
|
||||
swaysome.init_workspaces(1);
|
||||
sway.spawn_some_apps();
|
||||
|
||||
eprintln!("Move TERM3 to group 3");
|
||||
swaysome.move_container_to_workspace_group(3);
|
||||
assert_json_include!(actual: swaysome.get_tree(), expected: json!({
|
||||
"nodes": [
|
||||
{},
|
||||
{"type": "output", "name": "HEADLESS-1", "current_mode": {"height": 270, "width": 480}, "nodes": [
|
||||
{"type": "workspace", "name": "11", "num": 11, "focused": false, "nodes": [
|
||||
{"name": "TERM1", "focused": false},
|
||||
{"name": "TERM2", "focused": true},
|
||||
]},
|
||||
]},
|
||||
{"type": "output", "name": "HEADLESS-2", "current_mode": {"height": 1080, "width": 1920}, "nodes": [
|
||||
{"type": "workspace", "name": "21", "num": 21, "focused": false, "nodes": []},
|
||||
]},
|
||||
{"type": "output", "name": "HEADLESS-3", "current_mode": {"height": 1440, "width": 2560}, "nodes": [
|
||||
{"type": "workspace", "name": "31", "num": 31, "focused": false, "nodes": [
|
||||
{"name": "TERM3", "focused": false},
|
||||
]},
|
||||
]},
|
||||
]}));
|
||||
|
||||
eprintln!("Move TERM2 to next output");
|
||||
swaysome.move_container_to_next_output();
|
||||
assert_json_include!(actual: swaysome.get_tree(), expected: json!({
|
||||
"nodes": [
|
||||
{},
|
||||
{"type": "output", "name": "HEADLESS-1", "current_mode": {"height": 270, "width": 480}, "nodes": [
|
||||
{"type": "workspace", "name": "11", "num": 11, "focused": false, "nodes": [
|
||||
{"name": "TERM1", "focused": false},
|
||||
]},
|
||||
]},
|
||||
{"type": "output", "name": "HEADLESS-2", "current_mode": {"height": 1080, "width": 1920}, "nodes": [
|
||||
{"type": "workspace", "name": "21", "num": 21, "focused": false, "nodes": [
|
||||
{"name": "TERM2", "focused": true},
|
||||
]},
|
||||
]},
|
||||
{"type": "output", "name": "HEADLESS-3", "current_mode": {"height": 1440, "width": 2560}, "nodes": [
|
||||
{"type": "workspace", "name": "31", "num": 31, "focused": false, "nodes": [
|
||||
{"name": "TERM3", "focused": false},
|
||||
]},
|
||||
]},
|
||||
]}));
|
||||
|
||||
eprintln!("Move TERM2 to prev output");
|
||||
swaysome.move_container_to_prev_output();
|
||||
assert_json_include!(actual: swaysome.get_tree(), expected: json!({
|
||||
"nodes": [
|
||||
{},
|
||||
{"type": "output", "name": "HEADLESS-1", "current_mode": {"height": 270, "width": 480}, "nodes": [
|
||||
{"type": "workspace", "name": "11", "num": 11, "focused": false, "nodes": [
|
||||
{"name": "TERM1", "focused": false},
|
||||
{"name": "TERM2", "focused": true},
|
||||
]},
|
||||
]},
|
||||
{"type": "output", "name": "HEADLESS-2", "current_mode": {"height": 1080, "width": 1920}, "nodes": [
|
||||
{"type": "workspace", "name": "21", "num": 21, "focused": false, "nodes": [
|
||||
]},
|
||||
]},
|
||||
{"type": "output", "name": "HEADLESS-3", "current_mode": {"height": 1440, "width": 2560}, "nodes": [
|
||||
{"type": "workspace", "name": "31", "num": 31, "focused": false, "nodes": [
|
||||
{"name": "TERM3", "focused": false},
|
||||
]},
|
||||
]},
|
||||
]}));
|
||||
|
||||
eprintln!("Move TERM2 to prev output again");
|
||||
swaysome.move_container_to_prev_output();
|
||||
assert_json_include!(actual: swaysome.get_tree(), expected: json!({
|
||||
"nodes": [
|
||||
{},
|
||||
{"type": "output", "name": "HEADLESS-1", "current_mode": {"height": 270, "width": 480}, "nodes": [
|
||||
{"type": "workspace", "name": "11", "num": 11, "focused": false, "nodes": [
|
||||
{"name": "TERM1", "focused": false},
|
||||
]},
|
||||
]},
|
||||
{"type": "output", "name": "HEADLESS-2", "current_mode": {"height": 1080, "width": 1920}, "nodes": [
|
||||
{"type": "workspace", "name": "21", "num": 21, "focused": false, "nodes": [
|
||||
]},
|
||||
]},
|
||||
{"type": "output", "name": "HEADLESS-3", "current_mode": {"height": 1440, "width": 2560}, "nodes": [
|
||||
{"type": "workspace", "name": "31", "num": 31, "focused": false, "nodes": [
|
||||
{"name": "TERM3", "focused": false},
|
||||
{"name": "TERM2", "focused": true},
|
||||
]},
|
||||
]},
|
||||
]}));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_three_outputs_moving_around_across_outputs_without_init() {
|
||||
let sway = Sway::start();
|
||||
let swaysome = SwaySome::new_from_socket(&sway.sock).expect("SwaySome couldn't initialize");
|
||||
|
||||
sway.spawn_some_apps();
|
||||
|
||||
eprintln!("Move TERM3 to group 3");
|
||||
swaysome.move_container_to_workspace_group(3);
|
||||
assert_json_include!(actual: swaysome.get_tree(), expected: json!({
|
||||
"nodes": [
|
||||
{},
|
||||
{"type": "output", "name": "HEADLESS-1", "current_mode": {"height": 270, "width": 480}, "nodes": [
|
||||
{"type": "workspace", "name": "1", "num": 1, "focused": false, "nodes": [
|
||||
{"name": "TERM1", "focused": false},
|
||||
{"name": "TERM2", "focused": true},
|
||||
]},
|
||||
{"type": "workspace", "name": "31", "num": 31, "focused": false, "nodes": [
|
||||
{"name": "TERM3", "focused": false},
|
||||
]},
|
||||
]},
|
||||
{"type": "output", "name": "HEADLESS-2", "current_mode": {"height": 1080, "width": 1920}, "nodes": [
|
||||
{"type": "workspace", "name": "2", "num": 2, "focused": false, "nodes": []},
|
||||
]},
|
||||
{"type": "output", "name": "HEADLESS-3", "current_mode": {"height": 1440, "width": 2560}, "nodes": [
|
||||
{"type": "workspace", "name": "3", "num": 3, "focused": false, "nodes": []},
|
||||
]},
|
||||
]}));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_three_outputs_moving_around_absolute() {
|
||||
let sway = Sway::start();
|
||||
let swaysome = SwaySome::new_from_socket(&sway.sock).expect("SwaySome couldn't initialize");
|
||||
|
||||
swaysome.init_workspaces(1);
|
||||
sway.spawn_some_apps();
|
||||
|
||||
// shadow and re-init swaysome to get up-to-date internals (outputs, workspaces)
|
||||
// XXX this is more of a hack than anything else. Ideally, swaysome would never have out-of-date internals
|
||||
let swaysome = SwaySome::new_from_socket(&sway.sock).expect("SwaySome couldn't initialize");
|
||||
|
||||
eprintln!("Moving TERM3 to 12");
|
||||
swaysome.move_container_to_workspace(12);
|
||||
assert_json_include!(actual: swaysome.get_tree(), expected: json!({
|
||||
"nodes": [
|
||||
{},
|
||||
{"type": "output", "name": "HEADLESS-1", "current_mode": {"height": 270, "width": 480}, "nodes": [
|
||||
{"type": "workspace", "name": "11", "num": 11, "focused": false, "nodes": [
|
||||
{"name": "TERM1", "focused": false},
|
||||
{"name": "TERM2", "focused": true},
|
||||
]},
|
||||
{"type": "workspace", "name": "12", "num": 12, "focused": false, "nodes": [
|
||||
{"name": "TERM3", "focused": false},
|
||||
|
||||
]},
|
||||
]},
|
||||
{"type": "output", "name": "HEADLESS-2", "current_mode": {"height": 1080, "width": 1920}, "nodes": [
|
||||
{"type": "workspace", "name": "21", "num": 21, "focused": false, "nodes": []},
|
||||
]},
|
||||
{"type": "output", "name": "HEADLESS-3", "current_mode": {"height": 1440, "width": 2560}, "nodes": [
|
||||
{"type": "workspace", "name": "31", "num": 31, "focused": false, "nodes": []},
|
||||
]},
|
||||
]}));
|
||||
|
||||
eprintln!("Moving TERM2 to 42");
|
||||
swaysome.move_container_to_workspace(42);
|
||||
assert_json_include!(actual: swaysome.get_tree(), expected: json!({
|
||||
"nodes": [
|
||||
{},
|
||||
{"type": "output", "name": "HEADLESS-1", "current_mode": {"height": 270, "width": 480}, "nodes": [
|
||||
{"type": "workspace", "name": "11", "num": 11, "focused": false, "nodes": [
|
||||
{"name": "TERM1", "focused": true},
|
||||
]},
|
||||
{"type": "workspace", "name": "12", "num": 12, "focused": false, "nodes": [
|
||||
{"name": "TERM3", "focused": false},
|
||||
|
||||
]},
|
||||
{"type": "workspace", "name": "42", "num": 42, "focused": false, "nodes": [
|
||||
{"name": "TERM2", "focused": false},
|
||||
]},
|
||||
]},
|
||||
{"type": "output", "name": "HEADLESS-2", "current_mode": {"height": 1080, "width": 1920}, "nodes": [
|
||||
{"type": "workspace", "name": "21", "num": 21, "focused": false, "nodes": []},
|
||||
]},
|
||||
{"type": "output", "name": "HEADLESS-3", "current_mode": {"height": 1440, "width": 2560}, "nodes": [
|
||||
{"type": "workspace", "name": "31", "num": 31, "focused": false, "nodes": []},
|
||||
]},
|
||||
]}));
|
||||
|
||||
// shadow and re-init swaysome to get up-to-date internals (outputs, workspaces)
|
||||
// XXX this is more of a hack than anything else. Ideally, swaysome would never have out-of-date internals
|
||||
let swaysome = SwaySome::new_from_socket(&sway.sock).expect("SwaySome couldn't initialize");
|
||||
|
||||
eprintln!("Moving TERM1 to 42");
|
||||
swaysome.move_container_to_workspace(42);
|
||||
assert_json_include!(actual: swaysome.get_tree(), expected: json!({
|
||||
"nodes": [
|
||||
{},
|
||||
{"type": "output", "name": "HEADLESS-1", "current_mode": {"height": 270, "width": 480}, "nodes": [
|
||||
{"type": "workspace", "name": "11", "num": 11, "focused": true, "nodes": []},
|
||||
{"type": "workspace", "name": "12", "num": 12, "focused": false, "nodes": [
|
||||
{"name": "TERM3", "focused": false},
|
||||
|
||||
]},
|
||||
{"type": "workspace", "name": "42", "num": 42, "focused": false, "nodes": [
|
||||
{"name": "TERM2", "focused": false},
|
||||
{"name": "TERM1", "focused": false},
|
||||
]},
|
||||
]},
|
||||
{"type": "output", "name": "HEADLESS-2", "current_mode": {"height": 1080, "width": 1920}, "nodes": [
|
||||
{"type": "workspace", "name": "21", "num": 21, "focused": false, "nodes": []},
|
||||
]},
|
||||
{"type": "output", "name": "HEADLESS-3", "current_mode": {"height": 1440, "width": 2560}, "nodes": [
|
||||
{"type": "workspace", "name": "31", "num": 31, "focused": false, "nodes": []},
|
||||
]},
|
||||
]}));
|
||||
|
||||
eprintln!("Spawn TERM4");
|
||||
sway.send_command(["exec", "foot -T TERM4"].as_slice());
|
||||
std::thread::sleep(std::time::Duration::from_millis(200));
|
||||
|
||||
eprintln!("Moving TERM4 to 22");
|
||||
swaysome.move_container_to_workspace(22);
|
||||
assert_json_include!(actual: swaysome.get_tree(), expected: json!({
|
||||
"nodes": [
|
||||
{},
|
||||
{"type": "output", "name": "HEADLESS-1", "current_mode": {"height": 270, "width": 480}, "nodes": [
|
||||
{"type": "workspace", "name": "11", "num": 11, "focused": true, "nodes": []},
|
||||
{"type": "workspace", "name": "12", "num": 12, "focused": false, "nodes": [
|
||||
{"name": "TERM3", "focused": false},
|
||||
|
||||
]},
|
||||
{"type": "workspace", "name": "42", "num": 42, "focused": false, "nodes": [
|
||||
{"name": "TERM2", "focused": false},
|
||||
{"name": "TERM1", "focused": false},
|
||||
]},
|
||||
]},
|
||||
{"type": "output", "name": "HEADLESS-2", "current_mode": {"height": 1080, "width": 1920}, "nodes": [
|
||||
{"type": "workspace", "name": "21", "num": 21, "focused": false, "nodes": []},
|
||||
{"type": "workspace", "name": "22", "num": 22, "focused": false, "nodes": [
|
||||
{"name": "TERM4", "focused": false},
|
||||
]},
|
||||
]},
|
||||
{"type": "output", "name": "HEADLESS-3", "current_mode": {"height": 1440, "width": 2560}, "nodes": [
|
||||
{"type": "workspace", "name": "31", "num": 31, "focused": false, "nodes": []},
|
||||
]},
|
||||
]}));
|
||||
|
||||
eprintln!("Focus on 42");
|
||||
swaysome.focus_to_workspace(42);
|
||||
assert_json_include!(actual: swaysome.get_tree(), expected: json!({
|
||||
"nodes": [
|
||||
{},
|
||||
{"type": "output", "name": "HEADLESS-1", "current_mode": {"height": 270, "width": 480}, "nodes": [
|
||||
{"type": "workspace", "name": "12", "num": 12, "focused": false, "nodes": [
|
||||
{"name": "TERM3", "focused": false},
|
||||
|
||||
]},
|
||||
{"type": "workspace", "name": "42", "num": 42, "focused": false, "nodes": [
|
||||
{"name": "TERM2", "focused": false},
|
||||
{"name": "TERM1", "focused": true},
|
||||
]},
|
||||
]},
|
||||
{"type": "output", "name": "HEADLESS-2", "current_mode": {"height": 1080, "width": 1920}, "nodes": [
|
||||
{"type": "workspace", "name": "21", "num": 21, "focused": false, "nodes": []},
|
||||
{"type": "workspace", "name": "22", "num": 22, "focused": false, "nodes": [
|
||||
{"name": "TERM4", "focused": false},
|
||||
]},
|
||||
]},
|
||||
{"type": "output", "name": "HEADLESS-3", "current_mode": {"height": 1440, "width": 2560}, "nodes": [
|
||||
{"type": "workspace", "name": "31", "num": 31, "focused": false, "nodes": []},
|
||||
]},
|
||||
]}));
|
||||
|
||||
eprintln!("Focus on 41");
|
||||
swaysome.focus_to_workspace(41);
|
||||
assert_json_include!(actual: swaysome.get_tree(), expected: json!({
|
||||
"nodes": [
|
||||
{},
|
||||
{"type": "output", "name": "HEADLESS-1", "current_mode": {"height": 270, "width": 480}, "nodes": [
|
||||
{"type": "workspace", "name": "12", "num": 12, "focused": false, "nodes": [
|
||||
{"name": "TERM3", "focused": false},
|
||||
|
||||
]},
|
||||
{"type": "workspace", "name": "41", "num": 41, "focused": true, "nodes": []},
|
||||
{"type": "workspace", "name": "42", "num": 42, "focused": false, "nodes": [
|
||||
{"name": "TERM2", "focused": false},
|
||||
{"name": "TERM1", "focused": false},
|
||||
]},
|
||||
]},
|
||||
{"type": "output", "name": "HEADLESS-2", "current_mode": {"height": 1080, "width": 1920}, "nodes": [
|
||||
{"type": "workspace", "name": "21", "num": 21, "focused": false, "nodes": []},
|
||||
{"type": "workspace", "name": "22", "num": 22, "focused": false, "nodes": [
|
||||
{"name": "TERM4", "focused": false},
|
||||
]},
|
||||
]},
|
||||
{"type": "output", "name": "HEADLESS-3", "current_mode": {"height": 1440, "width": 2560}, "nodes": [
|
||||
{"type": "workspace", "name": "31", "num": 31, "focused": false, "nodes": []},
|
||||
]},
|
||||
]}));
|
||||
|
||||
eprintln!("Focus on 51");
|
||||
swaysome.focus_to_workspace(51);
|
||||
assert_json_include!(actual: swaysome.get_tree(), expected: json!({
|
||||
"nodes": [
|
||||
{},
|
||||
{"type": "output", "name": "HEADLESS-1", "current_mode": {"height": 270, "width": 480}, "nodes": [
|
||||
{"type": "workspace", "name": "12", "num": 12, "focused": false, "nodes": [
|
||||
{"name": "TERM3", "focused": false},
|
||||
|
||||
]},
|
||||
{"type": "workspace", "name": "42", "num": 42, "focused": false, "nodes": [
|
||||
{"name": "TERM2", "focused": false},
|
||||
{"name": "TERM1", "focused": false},
|
||||
]},
|
||||
{"type": "workspace", "name": "51", "num": 51, "focused": true, "nodes": []},
|
||||
]},
|
||||
{"type": "output", "name": "HEADLESS-2", "current_mode": {"height": 1080, "width": 1920}, "nodes": [
|
||||
{"type": "workspace", "name": "21", "num": 21, "focused": false, "nodes": []},
|
||||
{"type": "workspace", "name": "22", "num": 22, "focused": false, "nodes": [
|
||||
{"name": "TERM4", "focused": false},
|
||||
]},
|
||||
]},
|
||||
{"type": "output", "name": "HEADLESS-3", "current_mode": {"height": 1440, "width": 2560}, "nodes": [
|
||||
{"type": "workspace", "name": "31", "num": 31, "focused": false, "nodes": []},
|
||||
]},
|
||||
]}));
|
||||
|
||||
eprintln!("Focus on 32");
|
||||
swaysome.focus_to_workspace(32);
|
||||
assert_json_include!(actual: swaysome.get_tree(), expected: json!({
|
||||
"nodes": [
|
||||
{},
|
||||
{"type": "output", "name": "HEADLESS-1", "current_mode": {"height": 270, "width": 480}, "nodes": [
|
||||
{"type": "workspace", "name": "12", "num": 12, "focused": false, "nodes": [
|
||||
{"name": "TERM3", "focused": false},
|
||||
|
||||
]},
|
||||
{"type": "workspace", "name": "42", "num": 42, "focused": false, "nodes": [
|
||||
{"name": "TERM2", "focused": false},
|
||||
{"name": "TERM1", "focused": false},
|
||||
]},
|
||||
]},
|
||||
{"type": "output", "name": "HEADLESS-2", "current_mode": {"height": 1080, "width": 1920}, "nodes": [
|
||||
{"type": "workspace", "name": "21", "num": 21, "focused": false, "nodes": []},
|
||||
{"type": "workspace", "name": "22", "num": 22, "focused": false, "nodes": [
|
||||
{"name": "TERM4", "focused": false},
|
||||
]},
|
||||
]},
|
||||
{"type": "output", "name": "HEADLESS-3", "current_mode": {"height": 1440, "width": 2560}, "nodes": [
|
||||
{"type": "workspace", "name": "32", "num": 32, "focused": true, "nodes": []},
|
||||
]},
|
||||
]}));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_three_outputs_moving_groups_across_outputs() {
|
||||
let sway = Sway::start();
|
||||
let swaysome = SwaySome::new_from_socket(&sway.sock).expect("SwaySome couldn't initialize");
|
||||
|
||||
swaysome.init_workspaces(1);
|
||||
sway.spawn_some_apps();
|
||||
|
||||
eprintln!("Move TERM3 to group 3");
|
||||
swaysome.move_container_to_workspace_group(3);
|
||||
assert_json_include!(actual: swaysome.get_tree(), expected: json!({
|
||||
"nodes": [
|
||||
{},
|
||||
{"type": "output", "name": "HEADLESS-1", "current_mode": {"height": 270, "width": 480}, "nodes": [
|
||||
{"type": "workspace", "name": "11", "num": 11, "focused": false, "nodes": [
|
||||
{"name": "TERM1", "focused": false},
|
||||
{"name": "TERM2", "focused": true},
|
||||
]},
|
||||
]},
|
||||
{"type": "output", "name": "HEADLESS-2", "current_mode": {"height": 1080, "width": 1920}, "nodes": [
|
||||
{"type": "workspace", "name": "21", "num": 21, "focused": false, "nodes": []},
|
||||
]},
|
||||
{"type": "output", "name": "HEADLESS-3", "current_mode": {"height": 1440, "width": 2560}, "nodes": [
|
||||
{"type": "workspace", "name": "31", "num": 31, "focused": false, "nodes": [
|
||||
{"name": "TERM3", "focused": false},
|
||||
]},
|
||||
]},
|
||||
]}));
|
||||
|
||||
eprintln!("Move group 1 to prev output");
|
||||
swaysome.move_workspace_group_to_prev_output();
|
||||
assert_json_include!(actual: swaysome.get_tree(), expected: json!({
|
||||
"nodes": [
|
||||
{},
|
||||
{"type": "output", "name": "HEADLESS-1", "current_mode": {"height": 270, "width": 480}, "nodes": [
|
||||
{"type": "workspace", "name": "1", "num": 1, "focused": false, "nodes": []},
|
||||
]},
|
||||
{"type": "output", "name": "HEADLESS-2", "current_mode": {"height": 1080, "width": 1920}, "nodes": [
|
||||
{"type": "workspace", "name": "21", "num": 21, "focused": false, "nodes": []},
|
||||
]},
|
||||
{"type": "output", "name": "HEADLESS-3", "current_mode": {"height": 1440, "width": 2560}, "nodes": [
|
||||
{"type": "workspace", "name": "11", "num": 11, "focused": false, "nodes": [
|
||||
{"name": "TERM1", "focused": false},
|
||||
{"name": "TERM2", "focused": true},
|
||||
]},
|
||||
{"type": "workspace", "name": "31", "num": 31, "focused": false, "nodes": [
|
||||
{"name": "TERM3", "focused": false},
|
||||
]},
|
||||
]},
|
||||
]}));
|
||||
|
||||
eprintln!("Move group 1 to next output");
|
||||
swaysome.move_workspace_group_to_next_output();
|
||||
assert_json_include!(actual: swaysome.get_tree(), expected: json!({
|
||||
"nodes": [
|
||||
{},
|
||||
{"type": "output", "name": "HEADLESS-1", "current_mode": {"height": 270, "width": 480}, "nodes": [
|
||||
{"type": "workspace", "name": "11", "num": 11, "focused": false, "nodes": [
|
||||
{"name": "TERM1", "focused": false},
|
||||
{"name": "TERM2", "focused": true},
|
||||
]},
|
||||
]},
|
||||
{"type": "output", "name": "HEADLESS-2", "current_mode": {"height": 1080, "width": 1920}, "nodes": [
|
||||
{"type": "workspace", "name": "21", "num": 21, "focused": false, "nodes": []},
|
||||
]},
|
||||
{"type": "output", "name": "HEADLESS-3", "current_mode": {"height": 1440, "width": 2560}, "nodes": [
|
||||
{"type": "workspace", "name": "31", "num": 31, "focused": false, "nodes": [
|
||||
{"name": "TERM3", "focused": false},
|
||||
]},
|
||||
]},
|
||||
]}));
|
||||
|
||||
eprintln!("Move group 1 to next output again");
|
||||
swaysome.move_workspace_group_to_next_output();
|
||||
assert_json_include!(actual: swaysome.get_tree(), expected: json!({
|
||||
"nodes": [
|
||||
{},
|
||||
{"type": "output", "name": "HEADLESS-1", "current_mode": {"height": 270, "width": 480}, "nodes": [
|
||||
{"type": "workspace", "name": "1", "num": 1, "focused": false, "nodes": []},
|
||||
]},
|
||||
{"type": "output", "name": "HEADLESS-2", "current_mode": {"height": 1080, "width": 1920}, "nodes": [
|
||||
{"type": "workspace", "name": "11", "num": 11, "focused": false, "nodes": [
|
||||
{"name": "TERM1", "focused": false},
|
||||
{"name": "TERM2", "focused": true},
|
||||
]},
|
||||
]},
|
||||
{"type": "output", "name": "HEADLESS-3", "current_mode": {"height": 1440, "width": 2560}, "nodes": [
|
||||
{"type": "workspace", "name": "31", "num": 31, "focused": false, "nodes": [
|
||||
{"name": "TERM3", "focused": false},
|
||||
]},
|
||||
]},
|
||||
]}));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_three_outputs_plugging_unplugging_outputs() {
|
||||
let sway = Sway::start();
|
||||
let swaysome = SwaySome::new_from_socket(&sway.sock).expect("SwaySome couldn't initialize");
|
||||
|
||||
swaysome.init_workspaces(1);
|
||||
sway.spawn_some_apps();
|
||||
|
||||
eprintln!("Move TERM3 to group 3");
|
||||
swaysome.move_container_to_workspace_group(3);
|
||||
assert_json_include!(actual: swaysome.get_tree(), expected: json!({
|
||||
"nodes": [
|
||||
{},
|
||||
{"type": "output", "name": "HEADLESS-1", "current_mode": {"height": 270, "width": 480}, "nodes": [
|
||||
{"type": "workspace", "name": "11", "num": 11, "focused": false, "nodes": [
|
||||
{"name": "TERM1", "focused": false},
|
||||
{"name": "TERM2", "focused": true},
|
||||
]},
|
||||
]},
|
||||
{"type": "output", "name": "HEADLESS-2", "current_mode": {"height": 1080, "width": 1920}, "nodes": [
|
||||
{"type": "workspace", "name": "21", "num": 21, "focused": false, "nodes": []},
|
||||
]},
|
||||
{"type": "output", "name": "HEADLESS-3", "current_mode": {"height": 1440, "width": 2560}, "nodes": [
|
||||
{"type": "workspace", "name": "31", "num": 31, "focused": false, "nodes": [
|
||||
{"name": "TERM3", "focused": false},
|
||||
]},
|
||||
]},
|
||||
]}));
|
||||
|
||||
eprintln!("Disabling output 3");
|
||||
sway.send_command(["output HEADLESS-3 disable"].as_slice());
|
||||
std::thread::sleep(std::time::Duration::from_millis(200));
|
||||
assert_json_include!(actual: swaysome.get_tree(), expected: json!({
|
||||
"nodes": [
|
||||
{},
|
||||
{"type": "output", "name": "HEADLESS-1", "current_mode": {"height": 270, "width": 480}, "nodes": [
|
||||
{"type": "workspace", "name": "11", "num": 11, "focused": false, "nodes": [
|
||||
{"name": "TERM1", "focused": false},
|
||||
{"name": "TERM2", "focused": true},
|
||||
]},
|
||||
{"type": "workspace", "name": "31", "num": 31, "focused": false, "nodes": [
|
||||
{"name": "TERM3", "focused": false},
|
||||
]},
|
||||
]},
|
||||
{"type": "output", "name": "HEADLESS-2", "current_mode": {"height": 1080, "width": 1920}, "nodes": [
|
||||
{"type": "workspace", "name": "21", "num": 21, "focused": false, "nodes": []},
|
||||
]},
|
||||
]}));
|
||||
assert_eq!(swaysome.get_tree()["nodes"].as_array().unwrap().len(), 3);
|
||||
|
||||
eprintln!("Enabling output 3");
|
||||
sway.send_command(["output HEADLESS-3 enable"].as_slice());
|
||||
std::thread::sleep(std::time::Duration::from_millis(200));
|
||||
assert_json_include!(actual: swaysome.get_tree(), expected: json!({
|
||||
"nodes": [
|
||||
{},
|
||||
{"type": "output", "name": "HEADLESS-1", "current_mode": {"height": 270, "width": 480}, "nodes": [
|
||||
{"type": "workspace", "name": "11", "num": 11, "focused": false, "nodes": [
|
||||
{"name": "TERM1", "focused": false},
|
||||
{"name": "TERM2", "focused": true},
|
||||
]},
|
||||
{"type": "workspace", "name": "31", "num": 31, "focused": false, "nodes": [
|
||||
{"name": "TERM3", "focused": false},
|
||||
]},
|
||||
]},
|
||||
{"type": "output", "name": "HEADLESS-2", "current_mode": {"height": 1080, "width": 1920}, "nodes": [
|
||||
{"type": "workspace", "name": "21", "num": 21, "focused": false, "nodes": []},
|
||||
]},
|
||||
]}));
|
||||
assert_eq!(swaysome.get_tree()["nodes"].as_array().unwrap().len(), 4);
|
||||
|
||||
// shadow and re-init swaysome to get up-to-date internals (outputs, workspaces)
|
||||
// XXX this is more of a hack than anything else. Ideally, swaysome would never have out-of-date internals
|
||||
let swaysome = SwaySome::new_from_socket(&sway.sock).expect("SwaySome couldn't initialize");
|
||||
swaysome.rearrange_workspaces();
|
||||
|
||||
assert_json_include!(actual: swaysome.get_tree(), expected: json!({
|
||||
"nodes": [
|
||||
{},
|
||||
{"type": "output", "name": "HEADLESS-1", "current_mode": {"height": 270, "width": 480}, "nodes": [
|
||||
{"type": "workspace", "name": "11", "num": 11, "focused": false, "nodes": [
|
||||
{"name": "TERM1", "focused": false},
|
||||
{"name": "TERM2", "focused": false},
|
||||
]},
|
||||
]},
|
||||
{"type": "output", "name": "HEADLESS-2", "current_mode": {"height": 1080, "width": 1920}, "nodes": [
|
||||
{"type": "workspace", "name": "21", "num": 21, "focused": false, "nodes": []},
|
||||
]},
|
||||
{"type": "output", "name": "HEADLESS-3", "current_mode": {"height": 1440, "width": 2560}, "nodes": [
|
||||
{"type": "workspace", "name": "31", "num": 31, "focused": false, "nodes": [
|
||||
{"name": "TERM3", "focused": true},
|
||||
]},
|
||||
]},
|
||||
]}));
|
||||
|
||||
swaysome.focus_to_workspace(11);
|
||||
swaysome.move_container_to_workspace(21);
|
||||
|
||||
eprintln!("Disabling output 2");
|
||||
sway.send_command(["output HEADLESS-2 disable"].as_slice());
|
||||
assert_json_include!(actual: swaysome.get_tree(), expected: json!({
|
||||
"nodes": [
|
||||
{},
|
||||
{"type": "output", "name": "HEADLESS-1", "current_mode": {"height": 270, "width": 480}, "nodes": [
|
||||
{"type": "workspace", "name": "11", "num": 11, "focused": false, "nodes": [
|
||||
{"name": "TERM1", "focused": true},
|
||||
]},
|
||||
{"type": "workspace", "name": "21", "num": 21, "focused": false, "nodes": [
|
||||
{"name": "TERM2", "focused": false},
|
||||
]},
|
||||
]},
|
||||
{"type": "output", "name": "HEADLESS-3", "current_mode": {"height": 1440, "width": 2560}, "nodes": [
|
||||
{"type": "workspace", "name": "31", "num": 31, "focused": false, "nodes": [
|
||||
{"name": "TERM3", "focused": false},
|
||||
]},
|
||||
]},
|
||||
]}));
|
||||
|
||||
eprintln!("Enabling output 2");
|
||||
sway.send_command(["output HEADLESS-2 enable"].as_slice());
|
||||
assert_json_include!(actual: swaysome.get_tree(), expected: json!({
|
||||
"nodes": [
|
||||
{},
|
||||
{"type": "output", "name": "HEADLESS-1", "current_mode": {"height": 270, "width": 480}, "nodes": [
|
||||
{"type": "workspace", "name": "11", "num": 11, "focused": false, "nodes": [
|
||||
{"name": "TERM1", "focused": true},
|
||||
]},
|
||||
{"type": "workspace", "name": "21", "num": 21, "focused": false, "nodes": [
|
||||
{"name": "TERM2", "focused": false},
|
||||
]},
|
||||
]},
|
||||
{"type": "output", "name": "HEADLESS-3", "current_mode": {"height": 1440, "width": 2560}, "nodes": [
|
||||
{"type": "workspace", "name": "31", "num": 31, "focused": false, "nodes": [
|
||||
{"name": "TERM3", "focused": false},
|
||||
]},
|
||||
]},
|
||||
]}));
|
||||
|
||||
// shadow and re-init swaysome to get up-to-date internals (outputs, workspaces)
|
||||
// XXX this is more of a hack than anything else. Ideally, swaysome would never have out-of-date internals
|
||||
let swaysome = SwaySome::new_from_socket(&sway.sock).expect("SwaySome couldn't initialize");
|
||||
swaysome.rearrange_workspaces();
|
||||
|
||||
assert_json_include!(actual: swaysome.get_tree(), expected: json!({
|
||||
"nodes": [
|
||||
{},
|
||||
{"type": "output", "name": "HEADLESS-1", "current_mode": {"height": 270, "width": 480}, "nodes": [
|
||||
{"type": "workspace", "name": "11", "num": 11, "focused": false, "nodes": [
|
||||
{"name": "TERM1", "focused": false},
|
||||
]},
|
||||
]},
|
||||
{"type": "output", "name": "HEADLESS-3", "current_mode": {"height": 1440, "width": 2560}, "nodes": [
|
||||
{"type": "workspace", "name": "31", "num": 31, "focused": false, "nodes": [
|
||||
{"name": "TERM3", "focused": true},
|
||||
]},
|
||||
]},
|
||||
{"type": "output", "name": "HEADLESS-2", "current_mode": {"height": 1080, "width": 1920}, "nodes": [
|
||||
{"type": "workspace", "name": "21", "num": 21, "focused": false, "nodes": [
|
||||
{"name": "TERM2", "focused": false},
|
||||
]},
|
||||
]},
|
||||
]}));
|
||||
}
|
||||
50
tests/integration_bin.rs
Normal file
50
tests/integration_bin.rs
Normal file
|
|
@ -0,0 +1,50 @@
|
|||
use std::process::Command;
|
||||
|
||||
mod utils;
|
||||
use utils::Sway;
|
||||
|
||||
static SWAYSOME_BIN: &str = env!("CARGO_BIN_EXE_swaysome");
|
||||
|
||||
// Poor safeguard that the tests are actually testing the right version, in
|
||||
// case there is some confusion with a swaysome installed from the system. That
|
||||
// obviously does not always worked, but has saved me a couple of times already 🙃
|
||||
#[test]
|
||||
fn test_binary_version() {
|
||||
let output = Command::new(SWAYSOME_BIN)
|
||||
.args(["--version"])
|
||||
.env_clear()
|
||||
.env("SWAYSOCK", "/dev/null")
|
||||
.output()
|
||||
.expect("Couldn't run swaysome");
|
||||
assert_eq!(
|
||||
String::from_utf8(output.stdout).unwrap(),
|
||||
format!("swaysome {}\n", env!("CARGO_PKG_VERSION"))
|
||||
);
|
||||
}
|
||||
|
||||
// This is useful when working on swapping argument parsing libraries.
|
||||
#[test]
|
||||
fn test_binary_help() {
|
||||
let output = Command::new(SWAYSOME_BIN)
|
||||
.args(["-h"])
|
||||
.env_clear()
|
||||
.env("SWAYSOCK", "/dev/null")
|
||||
.output()
|
||||
.expect("Couldn't run swaysome");
|
||||
assert_eq!(String::from_utf8(output.stdout).unwrap(), "Better multimonitor handling for sway\n\nUsage: swaysome <COMMAND>\n\nCommands:\n init Initialize the workspace groups for all the outputs\n move Move the focused container to another workspace on the same workspace group\n move-to-group Move the focused container to the same workspace index on another workspace group\n focus Focus to another workspace on the same workspace group\n focus-group Focus to workspace group\n focus-all-outputs Focus to another workspace on all the outputs\n next-output Move the focused container to the next output\n prev-output Move the focused container to the previous output\n workspace-group-next-output Move the focused workspace group to the next output\n workspace-group-prev-output Move the focused workspace group to the previous output\n next-group Move the focused container to the next group\n prev-group Move the focused container to the previous group\n rearrange-workspaces Rearrange already opened workspaces to the correct outputs, useful when plugging new monitors\n help Print this message or the help of the given subcommand(s)\n\nOptions:\n -h, --help Print help\n -V, --version Print version\n");
|
||||
}
|
||||
|
||||
// We only test the 'init' command, given that the exhaustive command testing
|
||||
// is done in the library integration tests. Here, we only verify that the
|
||||
// interaction with `sway` works seamslessly.
|
||||
#[test]
|
||||
fn test_binary_interaction_with_sway() {
|
||||
let sway = Sway::start();
|
||||
let output = Command::new(SWAYSOME_BIN)
|
||||
.args(["init", "1"])
|
||||
.env_clear()
|
||||
.env("SWAYSOCK", sway.sock.clone())
|
||||
.output()
|
||||
.expect("Couldn't run swaysome");
|
||||
assert_eq!(String::from_utf8(output.stderr).unwrap(), "successful connection to socket '/tmp/swaysome_tests/test_binary_interaction_with_sway/swaysock'\nSending command: 'focus output HEADLESS-3' - Command successful\nSending command: 'workspace number 31' - Command successful\nSending command: 'focus output HEADLESS-2' - Command successful\nSending command: 'workspace number 21' - Command successful\nSending command: 'focus output HEADLESS-1' - Command successful\nSending command: 'workspace number 11' - Command successful\n");
|
||||
}
|
||||
11
tests/sway.conf
Normal file
11
tests/sway.conf
Normal file
|
|
@ -0,0 +1,11 @@
|
|||
output HEADLESS-1 {
|
||||
resolution 480x270
|
||||
}
|
||||
|
||||
output HEADLESS-2 {
|
||||
resolution 1920x1080
|
||||
}
|
||||
|
||||
output HEADLESS-3 {
|
||||
resolution 2560x1440
|
||||
}
|
||||
132
tests/utils/mod.rs
Normal file
132
tests/utils/mod.rs
Normal file
|
|
@ -0,0 +1,132 @@
|
|||
use std::{
|
||||
fs::File,
|
||||
io::Read,
|
||||
path::PathBuf,
|
||||
process::{Child, Command, Output},
|
||||
};
|
||||
|
||||
fn signal(pid: u32, sig: &str) {
|
||||
Command::new("kill")
|
||||
.arg("-s")
|
||||
.arg(sig)
|
||||
.arg(format!("{}", pid))
|
||||
.status()
|
||||
.expect("failed to execute 'kill'");
|
||||
}
|
||||
|
||||
pub struct Sway {
|
||||
pub sock: PathBuf,
|
||||
process: Child,
|
||||
}
|
||||
|
||||
impl Sway {
|
||||
pub fn start() -> Sway {
|
||||
let tmp = std::env::temp_dir()
|
||||
.join("swaysome_tests")
|
||||
.join(std::thread::current().name().unwrap());
|
||||
std::fs::create_dir_all(&tmp).expect("Unable to create temporary working directory");
|
||||
|
||||
let pwd = std::env::current_dir().expect("Unable to get current dir");
|
||||
let conf_path = pwd.join("tests/sway.conf");
|
||||
let swaysock_path = tmp.join("swaysock");
|
||||
let sway_log =
|
||||
File::create(tmp.join("sway.log")).expect("Unable to create sway's log file");
|
||||
|
||||
let sway = Command::new("sway")
|
||||
.arg("-c")
|
||||
.arg(conf_path.clone())
|
||||
.env_clear()
|
||||
.env("WLR_BACKENDS", "headless")
|
||||
.env("WLR_LIBINPUT_NO_DEVICES", "1")
|
||||
.env("XDG_RUNTIME_DIR", &tmp)
|
||||
.env("SWAYSOCK", &swaysock_path)
|
||||
.stderr(sway_log)
|
||||
.spawn()
|
||||
.expect("failed to execute sway");
|
||||
|
||||
// check that sway works correctly without using swaysome
|
||||
let sway = Sway {
|
||||
sock: swaysock_path,
|
||||
process: sway,
|
||||
};
|
||||
match sway.check_connection("loaded_config_file_name") {
|
||||
Ok(()) => {
|
||||
// Let's do some common initialization of the desktop
|
||||
sway.send_command(["create_output"].as_slice());
|
||||
sway.send_command(["create_output"].as_slice());
|
||||
return sway;
|
||||
}
|
||||
Err(()) => {
|
||||
eprintln!("Failed to start 'sway', aborting the tests");
|
||||
eprintln!("---- sway stderr ----");
|
||||
let mut buffer = String::new();
|
||||
let mut sway_stderr =
|
||||
File::open(tmp.join("sway.log")).expect("Unable to open sway's log file");
|
||||
sway_stderr.read_to_string(&mut buffer).unwrap();
|
||||
eprintln!("{}", buffer);
|
||||
eprintln!("---------------------");
|
||||
panic!();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn send_command(&self, commands: &[&str]) -> Output {
|
||||
Command::new("swaymsg")
|
||||
.args(commands)
|
||||
.env_clear()
|
||||
.env("SWAYSOCK", self.sock.clone())
|
||||
.output()
|
||||
.expect("Couldn't run swaymsg")
|
||||
}
|
||||
|
||||
// work around https://github.com/rust-lang/rust/issues/46379
|
||||
// TODO: maybe implement that: https://momori.dev/posts/organize-rust-integration-tests-without-dead-code-warning/
|
||||
#[allow(dead_code)]
|
||||
pub fn spawn_some_apps(&self) {
|
||||
self.send_command(["exec", "foot -T TERM1"].as_slice());
|
||||
// Make sure the app are created in the right order.
|
||||
// 200ms would still sometimes be racy on my Ryzen 5 PRO 4650U, so let's
|
||||
// take a safe bet and give plenty of time for shared CI runners.
|
||||
std::thread::sleep(std::time::Duration::from_millis(500));
|
||||
self.send_command(["exec", "foot -T TERM2"].as_slice());
|
||||
std::thread::sleep(std::time::Duration::from_millis(500));
|
||||
self.send_command(["exec", "foot -T TERM3"].as_slice());
|
||||
std::thread::sleep(std::time::Duration::from_millis(500));
|
||||
}
|
||||
|
||||
fn check_connection(&self, flag: &str) -> Result<(), ()> {
|
||||
let mut retries = 100; // wait for max 10s
|
||||
while retries > 0 {
|
||||
let version = self.send_command(["-t", "get_version"].as_slice());
|
||||
if String::from_utf8(version.stdout).unwrap().contains(flag)
|
||||
|| String::from_utf8(version.stderr).unwrap().contains(flag)
|
||||
{
|
||||
return Ok(());
|
||||
}
|
||||
std::thread::sleep(std::time::Duration::from_millis(100));
|
||||
retries -= 1;
|
||||
}
|
||||
return Err(());
|
||||
}
|
||||
|
||||
fn stop(&mut self) {
|
||||
// in case some apps were spawned, kill them, and give them some time to be killed
|
||||
self.send_command(["[all] kill"].as_slice());
|
||||
std::thread::sleep(std::time::Duration::from_millis(500));
|
||||
// now terminate sway
|
||||
signal(self.process.id(), "TERM");
|
||||
match self.check_connection("Unable to connect to") {
|
||||
Ok(()) => eprintln!("Sway terminated correctly on its own"),
|
||||
Err(_) => {
|
||||
self.process.kill().expect("Failed to kill sway");
|
||||
eprintln!("Sway had to be killed");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Drop for Sway {
|
||||
fn drop(&mut self) {
|
||||
self.stop();
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Reference in a new issue