Compare commits
10 commits
d3d4530cfd
...
690777aa16
Author | SHA1 | Date | |
---|---|---|---|
690777aa16 | |||
114e7892d7 | |||
1cb9672b48 | |||
d0ad88af6a | |||
5189ba323b | |||
1b6c9e46be | |||
b76d325d60 | |||
23dc47a008 | |||
0f24273c93 | |||
81790d4308 |
11 changed files with 1668 additions and 832 deletions
1
.gitignore
vendored
Normal file
1
.gitignore
vendored
Normal file
|
@ -0,0 +1 @@
|
||||||
|
/target
|
851
Cargo.lock
generated
Normal file
851
Cargo.lock
generated
Normal file
|
@ -0,0 +1,851 @@
|
||||||
|
# This file is automatically @generated by Cargo.
|
||||||
|
# It is not intended for manual editing.
|
||||||
|
version = 3
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "addr2line"
|
||||||
|
version = "0.21.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "8a30b2e23b9e17a9f90641c7ab1549cd9b44f296d3ccbf309d2863cfe398a0cb"
|
||||||
|
dependencies = [
|
||||||
|
"gimli",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "adler"
|
||||||
|
version = "1.0.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "aho-corasick"
|
||||||
|
version = "1.1.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "b2969dcb958b36655471fc61f7e416fa76033bdd4bfed0678d8fee1e2d07a1f0"
|
||||||
|
dependencies = [
|
||||||
|
"memchr",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "anstream"
|
||||||
|
version = "0.6.4"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "2ab91ebe16eb252986481c5b62f6098f3b698a45e34b5b98200cf20dd2484a44"
|
||||||
|
dependencies = [
|
||||||
|
"anstyle",
|
||||||
|
"anstyle-parse",
|
||||||
|
"anstyle-query",
|
||||||
|
"anstyle-wincon",
|
||||||
|
"colorchoice",
|
||||||
|
"utf8parse",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "anstyle"
|
||||||
|
version = "1.0.4"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "7079075b41f533b8c61d2a4d073c4676e1f8b249ff94a393b0595db304e0dd87"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "anstyle-parse"
|
||||||
|
version = "0.2.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "317b9a89c1868f5ea6ff1d9539a69f45dffc21ce321ac1fd1160dfa48c8e2140"
|
||||||
|
dependencies = [
|
||||||
|
"utf8parse",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "anstyle-query"
|
||||||
|
version = "1.0.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
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 = "anyhow"
|
||||||
|
version = "1.0.75"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "a4668cab20f66d8d020e1fbc0ebe47217433c1b6c8f2040faf858554e394ace6"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "autocfg"
|
||||||
|
version = "1.1.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "backtrace"
|
||||||
|
version = "0.3.69"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "2089b7e3f35b9dd2d0ed921ead4f6d318c27680d4a5bd167b3ee120edb105837"
|
||||||
|
dependencies = [
|
||||||
|
"addr2line",
|
||||||
|
"cc",
|
||||||
|
"cfg-if",
|
||||||
|
"libc",
|
||||||
|
"miniz_oxide",
|
||||||
|
"object",
|
||||||
|
"rustc-demangle",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "bitflags"
|
||||||
|
version = "1.3.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "bitflags"
|
||||||
|
version = "2.4.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "b4682ae6287fcf752ecaabbfcc7b6f9b72aa33933dc23a554d853aea8eea8635"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "byteorder"
|
||||||
|
version = "1.5.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "bytes"
|
||||||
|
version = "1.5.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "a2bd12c1caf447e69cd4528f47f94d203fd2582878ecb9e9465484c4148a8223"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "cc"
|
||||||
|
version = "1.0.83"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "f1174fb0b6ec23863f8b971027804a42614e347eafb0a95bf0b12cdae21fc4d0"
|
||||||
|
dependencies = [
|
||||||
|
"libc",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "cfg-if"
|
||||||
|
version = "1.0.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "clap"
|
||||||
|
version = "4.4.6"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "d04704f56c2cde07f43e8e2c154b43f216dc5c92fc98ada720177362f953b956"
|
||||||
|
dependencies = [
|
||||||
|
"clap_builder",
|
||||||
|
"clap_derive",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "clap_builder"
|
||||||
|
version = "4.4.6"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "0e231faeaca65ebd1ea3c737966bf858971cd38c3849107aa3ea7de90a804e45"
|
||||||
|
dependencies = [
|
||||||
|
"anstream",
|
||||||
|
"anstyle",
|
||||||
|
"clap_lex",
|
||||||
|
"strsim",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "clap_derive"
|
||||||
|
version = "4.4.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "0862016ff20d69b84ef8247369fabf5c008a7417002411897d40ee1f4532b873"
|
||||||
|
dependencies = [
|
||||||
|
"heck",
|
||||||
|
"proc-macro2",
|
||||||
|
"quote",
|
||||||
|
"syn",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "clap_lex"
|
||||||
|
version = "0.5.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "cd7cc57abe963c6d3b9d8be5b06ba7c8957a930305ca90304f24ef040aa6f961"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "colorchoice"
|
||||||
|
version = "1.0.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "acbf1af155f9b9ef647e42cdc158db4b64a1b61f743629225fde6f3e0be2a7c7"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "env_logger"
|
||||||
|
version = "0.10.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "85cdab6a89accf66733ad5a1693a4dcced6aeff64602b634530dd73c1f3ee9f0"
|
||||||
|
dependencies = [
|
||||||
|
"humantime",
|
||||||
|
"is-terminal",
|
||||||
|
"log",
|
||||||
|
"regex",
|
||||||
|
"termcolor",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "equivalent"
|
||||||
|
version = "1.0.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "errno"
|
||||||
|
version = "0.3.5"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "ac3e13f66a2f95e32a39eaa81f6b95d42878ca0e1db0c7543723dfe12557e860"
|
||||||
|
dependencies = [
|
||||||
|
"libc",
|
||||||
|
"windows-sys",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "futures"
|
||||||
|
version = "0.3.28"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "23342abe12aba583913b2e62f22225ff9c950774065e4bfb61a19cd9770fec40"
|
||||||
|
dependencies = [
|
||||||
|
"futures-channel",
|
||||||
|
"futures-core",
|
||||||
|
"futures-executor",
|
||||||
|
"futures-io",
|
||||||
|
"futures-sink",
|
||||||
|
"futures-task",
|
||||||
|
"futures-util",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "futures-channel"
|
||||||
|
version = "0.3.28"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "955518d47e09b25bbebc7a18df10b81f0c766eaf4c4f1cccef2fca5f2a4fb5f2"
|
||||||
|
dependencies = [
|
||||||
|
"futures-core",
|
||||||
|
"futures-sink",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "futures-core"
|
||||||
|
version = "0.3.28"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "4bca583b7e26f571124fe5b7561d49cb2868d79116cfa0eefce955557c6fee8c"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "futures-executor"
|
||||||
|
version = "0.3.28"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "ccecee823288125bd88b4d7f565c9e58e41858e47ab72e8ea2d64e93624386e0"
|
||||||
|
dependencies = [
|
||||||
|
"futures-core",
|
||||||
|
"futures-task",
|
||||||
|
"futures-util",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "futures-io"
|
||||||
|
version = "0.3.28"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "4fff74096e71ed47f8e023204cfd0aa1289cd54ae5430a9523be060cdb849964"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "futures-macro"
|
||||||
|
version = "0.3.28"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "89ca545a94061b6365f2c7355b4b32bd20df3ff95f02da9329b34ccc3bd6ee72"
|
||||||
|
dependencies = [
|
||||||
|
"proc-macro2",
|
||||||
|
"quote",
|
||||||
|
"syn",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "futures-sink"
|
||||||
|
version = "0.3.28"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "f43be4fe21a13b9781a69afa4985b0f6ee0e1afab2c6f454a8cf30e2b2237b6e"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "futures-task"
|
||||||
|
version = "0.3.28"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "76d3d132be6c0e6aa1534069c705a74a5997a356c0dc2f86a47765e5617c5b65"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "futures-util"
|
||||||
|
version = "0.3.28"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "26b01e40b772d54cf6c6d721c1d1abd0647a0106a12ecaa1c186273392a69533"
|
||||||
|
dependencies = [
|
||||||
|
"futures-channel",
|
||||||
|
"futures-core",
|
||||||
|
"futures-io",
|
||||||
|
"futures-macro",
|
||||||
|
"futures-sink",
|
||||||
|
"futures-task",
|
||||||
|
"memchr",
|
||||||
|
"pin-project-lite",
|
||||||
|
"pin-utils",
|
||||||
|
"slab",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "gimli"
|
||||||
|
version = "0.28.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "6fb8d784f27acf97159b40fc4db5ecd8aa23b9ad5ef69cdd136d3bc80665f0c0"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "hashbrown"
|
||||||
|
version = "0.14.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "7dfda62a12f55daeae5015f81b0baea145391cb4520f86c248fc615d72640d12"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "heck"
|
||||||
|
version = "0.4.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "hermit-abi"
|
||||||
|
version = "0.3.3"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "d77f7ec81a6d05a3abb01ab6eb7590f6083d08449fe5a1c8b1e620283546ccb7"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "humantime"
|
||||||
|
version = "2.1.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "9a3a5bfb195931eeb336b2a7b4d761daec841b97f947d34394601737a7bba5e4"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "i3toolwait"
|
||||||
|
version = "0.1.0"
|
||||||
|
dependencies = [
|
||||||
|
"anyhow",
|
||||||
|
"byteorder",
|
||||||
|
"clap",
|
||||||
|
"env_logger",
|
||||||
|
"futures",
|
||||||
|
"log",
|
||||||
|
"rust_lisp",
|
||||||
|
"serde",
|
||||||
|
"serde_json",
|
||||||
|
"serde_yaml",
|
||||||
|
"strfmt",
|
||||||
|
"tokio",
|
||||||
|
"xdg",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "indexmap"
|
||||||
|
version = "2.0.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "8adf3ddd720272c6ea8bf59463c04e0f93d0bbf7c5439b691bca2987e0270897"
|
||||||
|
dependencies = [
|
||||||
|
"equivalent",
|
||||||
|
"hashbrown",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "is-terminal"
|
||||||
|
version = "0.4.9"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "cb0889898416213fab133e1d33a0e5858a48177452750691bde3666d0fdbaf8b"
|
||||||
|
dependencies = [
|
||||||
|
"hermit-abi",
|
||||||
|
"rustix",
|
||||||
|
"windows-sys",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "itoa"
|
||||||
|
version = "1.0.9"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "af150ab688ff2122fcef229be89cb50dd66af9e01a4ff320cc137eecc9bacc38"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "libc"
|
||||||
|
version = "0.2.149"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "a08173bc88b7955d1b3145aa561539096c421ac8debde8cbc3612ec635fee29b"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "linux-raw-sys"
|
||||||
|
version = "0.4.10"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "da2479e8c062e40bf0066ffa0bc823de0a9368974af99c9f6df941d2c231e03f"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "lock_api"
|
||||||
|
version = "0.4.10"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "c1cc9717a20b1bb222f333e6a92fd32f7d8a18ddc5a3191a11af45dcbf4dcd16"
|
||||||
|
dependencies = [
|
||||||
|
"autocfg",
|
||||||
|
"scopeguard",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "log"
|
||||||
|
version = "0.4.20"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "b5e6163cb8c49088c2c36f57875e58ccd8c87c7427f7fbd50ea6710b2f3f2e8f"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "memchr"
|
||||||
|
version = "2.6.4"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "f665ee40bc4a3c5590afb1e9677db74a508659dfd71e126420da8274909a0167"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "miniz_oxide"
|
||||||
|
version = "0.7.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "e7810e0be55b428ada41041c41f32c9f1a42817901b4ccf45fa3d4b6561e74c7"
|
||||||
|
dependencies = [
|
||||||
|
"adler",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "mio"
|
||||||
|
version = "0.8.8"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "927a765cd3fc26206e66b296465fa9d3e5ab003e651c1b3c060e7956d96b19d2"
|
||||||
|
dependencies = [
|
||||||
|
"libc",
|
||||||
|
"wasi",
|
||||||
|
"windows-sys",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "num_cpus"
|
||||||
|
version = "1.16.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "4161fcb6d602d4d2081af7c3a45852d875a03dd337a6bfdd6e06407b61342a43"
|
||||||
|
dependencies = [
|
||||||
|
"hermit-abi",
|
||||||
|
"libc",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "object"
|
||||||
|
version = "0.32.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "9cf5f9dd3933bd50a9e1f149ec995f39ae2c496d31fd772c1fd45ebc27e902b0"
|
||||||
|
dependencies = [
|
||||||
|
"memchr",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "parking_lot"
|
||||||
|
version = "0.12.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "3742b2c103b9f06bc9fff0a37ff4912935851bee6d36f3c02bcc755bcfec228f"
|
||||||
|
dependencies = [
|
||||||
|
"lock_api",
|
||||||
|
"parking_lot_core",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "parking_lot_core"
|
||||||
|
version = "0.9.8"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "93f00c865fe7cabf650081affecd3871070f26767e7b2070a3ffae14c654b447"
|
||||||
|
dependencies = [
|
||||||
|
"cfg-if",
|
||||||
|
"libc",
|
||||||
|
"redox_syscall",
|
||||||
|
"smallvec",
|
||||||
|
"windows-targets",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "pin-project-lite"
|
||||||
|
version = "0.2.13"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "8afb450f006bf6385ca15ef45d71d2288452bc3683ce2e2cacc0d18e4be60b58"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "pin-utils"
|
||||||
|
version = "0.1.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "proc-macro2"
|
||||||
|
version = "1.0.69"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
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 = "redox_syscall"
|
||||||
|
version = "0.3.5"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "567664f262709473930a4bf9e51bf2ebf3348f2e748ccc50dea20646858f8f29"
|
||||||
|
dependencies = [
|
||||||
|
"bitflags 1.3.2",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "regex"
|
||||||
|
version = "1.10.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "aaac441002f822bc9705a681810a4dd2963094b9ca0ddc41cb963a4c189189ea"
|
||||||
|
dependencies = [
|
||||||
|
"aho-corasick",
|
||||||
|
"memchr",
|
||||||
|
"regex-automata",
|
||||||
|
"regex-syntax",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "regex-automata"
|
||||||
|
version = "0.4.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "5011c7e263a695dc8ca064cddb722af1be54e517a280b12a5356f98366899e5d"
|
||||||
|
dependencies = [
|
||||||
|
"aho-corasick",
|
||||||
|
"memchr",
|
||||||
|
"regex-syntax",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "regex-syntax"
|
||||||
|
version = "0.8.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "c08c74e62047bb2de4ff487b251e4a92e24f48745648451635cec7d591162d9f"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "rust_lisp"
|
||||||
|
version = "0.18.0"
|
||||||
|
source = "git+https://github.com/brundonsmith/rust_lisp.git?branch=arc-feature-addition#6c4445965c027bd4d3cf1f3154e9145bd45e8ba6"
|
||||||
|
dependencies = [
|
||||||
|
"cfg-if",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "rustc-demangle"
|
||||||
|
version = "0.1.23"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "d626bb9dae77e28219937af045c257c28bfd3f69333c512553507f5f9798cb76"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "rustix"
|
||||||
|
version = "0.38.19"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "745ecfa778e66b2b63c88a61cb36e0eea109e803b0b86bf9879fbc77c70e86ed"
|
||||||
|
dependencies = [
|
||||||
|
"bitflags 2.4.0",
|
||||||
|
"errno",
|
||||||
|
"libc",
|
||||||
|
"linux-raw-sys",
|
||||||
|
"windows-sys",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "ryu"
|
||||||
|
version = "1.0.15"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "1ad4cc8da4ef723ed60bced201181d83791ad433213d8c24efffda1eec85d741"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "scopeguard"
|
||||||
|
version = "1.2.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "serde"
|
||||||
|
version = "1.0.188"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "cf9e0fcba69a370eed61bcf2b728575f726b50b55cba78064753d708ddc7549e"
|
||||||
|
dependencies = [
|
||||||
|
"serde_derive",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "serde_derive"
|
||||||
|
version = "1.0.188"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "4eca7ac642d82aa35b60049a6eccb4be6be75e599bd2e9adb5f875a737654af2"
|
||||||
|
dependencies = [
|
||||||
|
"proc-macro2",
|
||||||
|
"quote",
|
||||||
|
"syn",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "serde_json"
|
||||||
|
version = "1.0.107"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "6b420ce6e3d8bd882e9b243c6eed35dbc9a6110c9769e74b584e0d68d1f20c65"
|
||||||
|
dependencies = [
|
||||||
|
"itoa",
|
||||||
|
"ryu",
|
||||||
|
"serde",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "serde_yaml"
|
||||||
|
version = "0.9.25"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "1a49e178e4452f45cb61d0cd8cebc1b0fafd3e41929e996cef79aa3aca91f574"
|
||||||
|
dependencies = [
|
||||||
|
"indexmap",
|
||||||
|
"itoa",
|
||||||
|
"ryu",
|
||||||
|
"serde",
|
||||||
|
"unsafe-libyaml",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "signal-hook-registry"
|
||||||
|
version = "1.4.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "d8229b473baa5980ac72ef434c4415e70c4b5e71b423043adb4ba059f89c99a1"
|
||||||
|
dependencies = [
|
||||||
|
"libc",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "slab"
|
||||||
|
version = "0.4.9"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "8f92a496fb766b417c996b9c5e57daf2f7ad3b0bebe1ccfca4856390e3d3bb67"
|
||||||
|
dependencies = [
|
||||||
|
"autocfg",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "smallvec"
|
||||||
|
version = "1.11.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "942b4a808e05215192e39f4ab80813e599068285906cc91aa64f923db842bd5a"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "socket2"
|
||||||
|
version = "0.5.4"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "4031e820eb552adee9295814c0ced9e5cf38ddf1e8b7d566d6de8e2538ea989e"
|
||||||
|
dependencies = [
|
||||||
|
"libc",
|
||||||
|
"windows-sys",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "strfmt"
|
||||||
|
version = "0.2.4"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "7a8348af2d9fc3258c8733b8d9d8db2e56f54b2363a4b5b81585c7875ed65e65"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "strsim"
|
||||||
|
version = "0.10.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "syn"
|
||||||
|
version = "2.0.38"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "e96b79aaa137db8f61e26363a0c9b47d8b4ec75da28b7d1d614c2303e232408b"
|
||||||
|
dependencies = [
|
||||||
|
"proc-macro2",
|
||||||
|
"quote",
|
||||||
|
"unicode-ident",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "termcolor"
|
||||||
|
version = "1.3.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "6093bad37da69aab9d123a8091e4be0aa4a03e4d601ec641c327398315f62b64"
|
||||||
|
dependencies = [
|
||||||
|
"winapi-util",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "tokio"
|
||||||
|
version = "1.33.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "4f38200e3ef7995e5ef13baec2f432a6da0aa9ac495b2c0e8f3b7eec2c92d653"
|
||||||
|
dependencies = [
|
||||||
|
"backtrace",
|
||||||
|
"bytes",
|
||||||
|
"libc",
|
||||||
|
"mio",
|
||||||
|
"num_cpus",
|
||||||
|
"parking_lot",
|
||||||
|
"pin-project-lite",
|
||||||
|
"signal-hook-registry",
|
||||||
|
"socket2",
|
||||||
|
"tokio-macros",
|
||||||
|
"windows-sys",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "tokio-macros"
|
||||||
|
version = "2.1.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "630bdcf245f78637c13ec01ffae6187cca34625e8c63150d424b59e55af2675e"
|
||||||
|
dependencies = [
|
||||||
|
"proc-macro2",
|
||||||
|
"quote",
|
||||||
|
"syn",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "unicode-ident"
|
||||||
|
version = "1.0.12"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "unsafe-libyaml"
|
||||||
|
version = "0.2.9"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "f28467d3e1d3c6586d8f25fa243f544f5800fec42d97032474e17222c2b75cfa"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "utf8parse"
|
||||||
|
version = "0.2.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "711b9620af191e0cdc7468a8d14e709c3dcdb115b36f838e601583af800a370a"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "wasi"
|
||||||
|
version = "0.11.0+wasi-snapshot-preview1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "winapi"
|
||||||
|
version = "0.3.9"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419"
|
||||||
|
dependencies = [
|
||||||
|
"winapi-i686-pc-windows-gnu",
|
||||||
|
"winapi-x86_64-pc-windows-gnu",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "winapi-i686-pc-windows-gnu"
|
||||||
|
version = "0.4.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "winapi-util"
|
||||||
|
version = "0.1.6"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "f29e6f9198ba0d26b4c9f07dbe6f9ed633e1f3d5b8b414090084349e46a52596"
|
||||||
|
dependencies = [
|
||||||
|
"winapi",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "winapi-x86_64-pc-windows-gnu"
|
||||||
|
version = "0.4.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "windows-sys"
|
||||||
|
version = "0.48.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9"
|
||||||
|
dependencies = [
|
||||||
|
"windows-targets",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "windows-targets"
|
||||||
|
version = "0.48.5"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
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 = "windows_aarch64_gnullvm"
|
||||||
|
version = "0.48.5"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
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"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "xdg"
|
||||||
|
version = "2.5.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "213b7324336b53d2414b2db8537e56544d981803139155afa84f76eeebb7a546"
|
21
Cargo.toml
Normal file
21
Cargo.toml
Normal file
|
@ -0,0 +1,21 @@
|
||||||
|
[package]
|
||||||
|
name = "i3toolwait"
|
||||||
|
version = "0.1.0"
|
||||||
|
edition = "2021"
|
||||||
|
|
||||||
|
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
anyhow = "1.0.75"
|
||||||
|
byteorder = "1.5.0"
|
||||||
|
clap = { version = "4.4.6", features = ["derive"] }
|
||||||
|
env_logger = "0.10.0"
|
||||||
|
futures = "0.3.28"
|
||||||
|
log = "0.4.20"
|
||||||
|
rust_lisp = { git = "https://github.com/brundonsmith/rust_lisp.git", branch = "arc-feature-addition", features = ["arc"] }
|
||||||
|
serde = { version = "1.0.188", features = ["std", "derive", "serde_derive"] }
|
||||||
|
serde_json = "1.0.107"
|
||||||
|
serde_yaml = "0.9.25"
|
||||||
|
strfmt = "0.2.4"
|
||||||
|
tokio = { version = "1.33.0", features = ["full"] }
|
||||||
|
xdg = "2.5.2"
|
18
Makefile
18
Makefile
|
@ -1,9 +1,17 @@
|
||||||
|
EXEC := i3toolwait
|
||||||
INSTALL_BASE ?= /usr/local
|
INSTALL_BASE ?= /usr/local
|
||||||
|
|
||||||
install: i3toolwait install-modules
|
default: target/debug/${EXEC}
|
||||||
install -Dm0755 -oroot -groot $< ${INSTALL_BASE}/bin/$<
|
release: target/release/${EXEC}
|
||||||
|
default: target/debug/${EXEC}
|
||||||
|
|
||||||
install-modules: requirements.txt
|
install: target/release/${EXEC}
|
||||||
python3 -mpip install --upgrade --requirement $<
|
install -Dm0755 -oroot -groot $< ${INSTALL_BASE}/bin/${EXEC}
|
||||||
|
|
||||||
.PHONY: install install-modules
|
target/release/${EXEC}:
|
||||||
|
@cargo build --release
|
||||||
|
|
||||||
|
target/debug/${EXEC}:
|
||||||
|
@cargo build
|
||||||
|
|
||||||
|
.PHONY: install
|
||||||
|
|
207
README.md
207
README.md
|
@ -4,114 +4,125 @@ Launch a program and move it to the correct workspace.
|
||||||
|
|
||||||
## Usage
|
## Usage
|
||||||
|
|
||||||
- **simple:** `i3toolwait simple ...`
|
`i3toolwait -c FILE`
|
||||||
- **config:** `i3toolwait config ...`
|
|
||||||
|
|
||||||
### Simple
|
Optionally start multiple programs and wait for their windows to appear.
|
||||||
|
Once these windows appeared a custom i3 command can be specified.
|
||||||
|
|
||||||
Run only one program.
|
## Example
|
||||||
|
|
||||||
### Config
|
|
||||||
|
|
||||||
Run multiple programs by specifying a yaml configuration file:
|
|
||||||
|
|
||||||
```yaml
|
```yaml
|
||||||
---
|
---
|
||||||
signal: signal number or name, optional. Should program entries which have signal: true wait for this signal before continuing to the next one.
|
timeout: 10000
|
||||||
timeout: timeout in milliseconds
|
|
||||||
init: a lisp program, optional. Used to initialize the environment, useful to define custom functions which should be available everywhere.
|
|
||||||
programs:
|
|
||||||
- match: a filter with which to match the window
|
|
||||||
workspace: string or null, the workspace to move windows to
|
|
||||||
cmd: string or list, the command to execute
|
|
||||||
signal: boolean, should we wait before continuing with the next entry
|
|
||||||
timeout: timeout in milliseconds, used only if signal: true - how long to wait for the signal
|
|
||||||
```
|
|
||||||
|
|
||||||
The programs will be started asynchronously, except when `signal = true` which means that, before continuing
|
|
||||||
to the next program we wait for a signal. I would start all programs, which do not wait for a signal first
|
|
||||||
and then only the ones depending on the signal to reduce the startup delay.
|
|
||||||
|
|
||||||
## Installing
|
|
||||||
|
|
||||||
Use the makefile: `INSTALL_BASE=/usr/local/ make install` or install all dependencies
|
|
||||||
`python3 -mpip install --upgrade -r requirements.txt` and copy the script to your
|
|
||||||
path: `cp i3toolwait /usr/local/bin/i3toolwait`.
|
|
||||||
|
|
||||||
## Filtering
|
|
||||||
|
|
||||||
The program allows to match the window or container based on the returned IPC data.
|
|
||||||
Some programs might open multiple windows (looking at you, Discord).
|
|
||||||
|
|
||||||
In order to move the correct window to the desired workspace a filter can be defined.
|
|
||||||
|
|
||||||
The syntax for the filter is lisp-like. To view all spawned containers run the program
|
|
||||||
with `--debug --filter=False` which will not match any windows and print their properties.
|
|
||||||
|
|
||||||
It is then possible to construct a filter for any program.
|
|
||||||
|
|
||||||
Available Operators:
|
|
||||||
|
|
||||||
- and: `&`: logical and, ungreedy
|
|
||||||
- or: `|`: logical or, ungreedy
|
|
||||||
- if: `?`: branch, if the first argument evaluates to `True` return the second, otherwise the third
|
|
||||||
- eq: `=`: equality
|
|
||||||
- neq: `!=`: inequality
|
|
||||||
- gt: `>`: greater than
|
|
||||||
- lt: `<`: less than
|
|
||||||
- load: `load`: load a key from the provided input `(load ".container.app_id")`
|
|
||||||
- has-key: `has-key`: check if a key is in the input: `(has-key ".container.app_id")`
|
|
||||||
- let: `let`: assign a local variable: `(let x 10)`
|
|
||||||
- setq: `setq`: assign a global variable: `(setq x 11)`
|
|
||||||
- defun: `defun`: user-defined functions: `((defun greet (a) (write (+ "Hello " a "!"))) (greet "Alice"))`
|
|
||||||
|
|
||||||
For example: `(> (load ".container.geometry.width") 300)` would match the first window where the width is greater than 300.
|
|
||||||
|
|
||||||
Multiple filters are combined via nesting: `(& (> (load ".container.geometry.width") 300) (= (load ".container.window_properties.class") "discord"))`.
|
|
||||||
|
|
||||||
## Starting tray programs in a specific order
|
|
||||||
|
|
||||||
To start tray programs in a specific order it is possible to specify the `signal` parameter.
|
|
||||||
Starting of programs will be halted until the program has received the corresponding signal.
|
|
||||||
|
|
||||||
This could be combined with waybar to enforce an ordering of tray applications:
|
|
||||||
|
|
||||||
`~/.config/waybar/config`
|
|
||||||
```json
|
|
||||||
"tray": {
|
|
||||||
"on-update": "pkill --full --signal SIGUSR1 i3toolwait",
|
|
||||||
"reverse-direction": true,
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
`config-file`
|
|
||||||
```yaml
|
|
||||||
signal: SIGUSR1
|
|
||||||
timeout: 2000
|
|
||||||
init: |
|
init: |
|
||||||
(
|
(begin
|
||||||
(setq i3_path ".container.window_properties.class")
|
(define i3_path ".container.window_properties.class")
|
||||||
(setq sway_path ".container.app_id")
|
(define sway_path ".container.app_id")
|
||||||
(defun "idmatch" (name) (= (? (has-key sway_path) (load sway_path) (load i3_path)) name))
|
(defun idmatch (name) (== (if (has-key sway_path) (load sway_path) (load i3_path)) name))
|
||||||
|
(defun match (name) (and (== (load ".change") "new") (idmatch name)))
|
||||||
|
(defun match-load (name) (if (match name) (load ".container.id") F))
|
||||||
)
|
)
|
||||||
|
cmd: 'workspace 1'
|
||||||
programs:
|
programs:
|
||||||
- cmd: 'nm-applet --indicator'
|
- run: 'exec gtk-launch librewolf'
|
||||||
match: '(False)'
|
cmd: 'for_window [con_id="{result}"] focus; move container to workspace 1'
|
||||||
timeout: 1000
|
match: '(match-load "LibreWolf")'
|
||||||
signal: true
|
- run: 'exec gtk-launch nheko || gtk-launch io.element.Element'
|
||||||
- cmd: 'blueman-applet'
|
cmd: 'for_window [con_id="{result}"] focus; move container to workspace 2'
|
||||||
match: '(False)'
|
match: '(if (or (match "Electron") (match "nheko")) (load ".container.id") F)'
|
||||||
timeout: 1000
|
- run: 'exec gtk-launch thunderbird'
|
||||||
signal: true
|
cmd: 'for_window [con_id="{result}"] focus; move container to workspace 3'
|
||||||
- ...
|
match: '(match-load "thunderbird")'
|
||||||
|
- run: 'exec nm-applet --indicator'
|
||||||
|
- run: 'exec blueman-applet'
|
||||||
|
- run: 'exec gtk-launch org.kde.kdeconnect.nonplasma'
|
||||||
|
- run: 'exec gtk-launch syncthing-gtk'
|
||||||
```
|
```
|
||||||
|
|
||||||
This setup would order the icons in waybar from left-to-right like in the config file.
|
## Configuration
|
||||||
|
|
||||||
## Troubleshooting
|
The configuration file is in YAML format.
|
||||||
|
|
||||||
### My windows do not get rearranged
|
|
||||||
|
|
||||||
It is very likely that the timeout is too short and the program exits before the window spawns.
|
### Configuration
|
||||||
Alternatively your filter might just be wrong. To debug execute the script with the `--debug`
|
|
||||||
flag to see if the window is recognized.
|
#### timeout: int
|
||||||
|
|
||||||
|
_Optional_ _Default_ `3000`
|
||||||
|
|
||||||
|
Total program timeout in ms.
|
||||||
|
|
||||||
|
#### init: String
|
||||||
|
|
||||||
|
_Optional_ _Default_ `""`
|
||||||
|
|
||||||
|
Initialization program; Used to initialize the environment, useful
|
||||||
|
to define custom functions which should be available everywhere.
|
||||||
|
|
||||||
|
#### cmd: String
|
||||||
|
|
||||||
|
_Optional_ _Default_ `""`
|
||||||
|
|
||||||
|
A final i3 command to be executed before exiting.
|
||||||
|
|
||||||
|
#### programs: List[Union[[Program](#program), [Signal](#signal)]]
|
||||||
|
|
||||||
|
_Optional_ _Default_ `[]`
|
||||||
|
|
||||||
|
A list of programs to execute.
|
||||||
|
|
||||||
|
### Program
|
||||||
|
|
||||||
|
Launch all programs using [`run`](#run-string) and execute
|
||||||
|
[`cmd`](#cmd-string-1) once [`match`](#match-string) matches
|
||||||
|
a window.
|
||||||
|
|
||||||
|
#### match: String
|
||||||
|
|
||||||
|
_Required_
|
||||||
|
|
||||||
|
A lisp program which analyzes the i3 window event and returns a value.
|
||||||
|
If the return value is `false` the window does not match and no
|
||||||
|
further processing occurs. Otherwise the i3 command
|
||||||
|
[`cmd`](#cmd-string-1).
|
||||||
|
will be executed.
|
||||||
|
|
||||||
|
#### cmd: String
|
||||||
|
|
||||||
|
_Required_
|
||||||
|
|
||||||
|
A i3 command. Can contain a format `{result}` which gets replaced
|
||||||
|
by the output of the match command.
|
||||||
|
|
||||||
|
**Example:**
|
||||||
|
|
||||||
|
`for_window [con_id="{result}"] focus; move container to window 1`
|
||||||
|
|
||||||
|
#### run: String
|
||||||
|
|
||||||
|
_Optional_ _Default_ `null`
|
||||||
|
|
||||||
|
A i3 command which is run at program startup, can be used to launch
|
||||||
|
programs.
|
||||||
|
|
||||||
|
**Example:**
|
||||||
|
|
||||||
|
`exec gtk-launch firefox`
|
||||||
|
|
||||||
|
### Signal
|
||||||
|
|
||||||
|
Programs are launched in order and only advance after
|
||||||
|
[`timeout`](#timeout-int-1) or after receiving signal
|
||||||
|
`SIGUSR1`.
|
||||||
|
|
||||||
|
#### run: String
|
||||||
|
|
||||||
|
_Optional_ _Default_ `null`
|
||||||
|
|
||||||
|
A i3 command.
|
||||||
|
|
||||||
|
#### timeout: int
|
||||||
|
|
||||||
|
_Optional_ _Default_ `500`
|
||||||
|
|
||||||
|
How long to wait for the signal in ms.
|
||||||
|
|
724
i3toolwait
724
i3toolwait
|
@ -1,724 +0,0 @@
|
||||||
#!/usr/bin/env python3
|
|
||||||
# -*- coding: utf-8 -*-
|
|
||||||
|
|
||||||
|
|
||||||
import string
|
|
||||||
import typing
|
|
||||||
import asyncio
|
|
||||||
import signal
|
|
||||||
import os
|
|
||||||
import time
|
|
||||||
import functools
|
|
||||||
import json
|
|
||||||
import logging
|
|
||||||
|
|
||||||
import yaml
|
|
||||||
import click
|
|
||||||
import pydantic
|
|
||||||
import i3ipc
|
|
||||||
import i3ipc.aio
|
|
||||||
|
|
||||||
try:
|
|
||||||
from yaml import CSafeLoader as SafeLoader
|
|
||||||
except ImportError:
|
|
||||||
from yaml import SafeLoader
|
|
||||||
|
|
||||||
LOGGER = logging.getLogger('i3toolwait' if __name__ == '__main__' else __name__)
|
|
||||||
|
|
||||||
def lazy_fc_if(env, local, a, b, c):
|
|
||||||
a.reduce(env, local)
|
|
||||||
if a.reduced:
|
|
||||||
b.reduce(env, local)
|
|
||||||
return b.reduced
|
|
||||||
c.reduce(env, local)
|
|
||||||
return c.reduced
|
|
||||||
|
|
||||||
def lazy_fc_nif(env, local, a, b, c):
|
|
||||||
a.reduce(env, local)
|
|
||||||
if not a.reduced:
|
|
||||||
b.reduce(env, local)
|
|
||||||
return b.reduced
|
|
||||||
c.reduce(env, local)
|
|
||||||
c.reduced
|
|
||||||
|
|
||||||
def lazy_fc_defun(env, local, name, variables, func):
|
|
||||||
_ = local
|
|
||||||
# need ugly hack, because variables are actually a function with n-1 args
|
|
||||||
varnames = [variables._fc] + [v._value for v in variables._args]
|
|
||||||
env.set_lisp_function(name._value, varnames, func)
|
|
||||||
|
|
||||||
def fc_load(env, local, path):
|
|
||||||
_ = local
|
|
||||||
ipc_value = env.input
|
|
||||||
for k in path.strip('.').split('.'):
|
|
||||||
ipc_value = ipc_value[k]
|
|
||||||
return ipc_value
|
|
||||||
|
|
||||||
def fc_has_key(env, local, path):
|
|
||||||
_ = local
|
|
||||||
ipc_value = env.input
|
|
||||||
for k in path.strip('.').split('.'):
|
|
||||||
try:
|
|
||||||
ipc_value = ipc_value[k]
|
|
||||||
except KeyError:
|
|
||||||
return False
|
|
||||||
return True
|
|
||||||
|
|
||||||
class Environment:
|
|
||||||
|
|
||||||
def __init__(self, input):
|
|
||||||
self._input = input
|
|
||||||
self._variables = {}
|
|
||||||
self._functions = {
|
|
||||||
'__last__': lambda _env, _local, *a: a[-1], # special function, if multiple expressions, execute all and return result of last one
|
|
||||||
'setq': lambda env, _, n, v: env.set_variable(n, v),
|
|
||||||
'let': lambda _, local, n, v: local.set_variable(n, v),
|
|
||||||
'write': lambda _env, _local, a: print(a),
|
|
||||||
'load': fc_load,
|
|
||||||
'has-key': fc_has_key,
|
|
||||||
'=': lambda _, _l, a, b: a == b,
|
|
||||||
'!=': lambda _, _l, a, b: a != b,
|
|
||||||
'>': lambda _, _l, a, b: a > b,
|
|
||||||
'<': lambda _, _l, a, b: a < b,
|
|
||||||
'>=': lambda _, _l, a, b: a >= b,
|
|
||||||
'<=': lambda _, _l, a, b: a <= b,
|
|
||||||
'+': lambda _, _l, *a: functools.reduce(lambda a, b: a + b, a),
|
|
||||||
'-': lambda _, _l, a, b: a - b,
|
|
||||||
'*': lambda _, _l, *a: functools.reduce(lambda a, b: a * b, a),
|
|
||||||
'/': lambda _, _l, a, b: a // b,
|
|
||||||
'|': lambda _, _l, *a: functools.reduce(lambda a, b: a or b, a),
|
|
||||||
'&': lambda _, _l, *a: functools.reduce(lambda a, b: a and b, a),
|
|
||||||
}
|
|
||||||
self._lazy_functions = {
|
|
||||||
'?': lazy_fc_if,
|
|
||||||
'!?': lazy_fc_nif,
|
|
||||||
'defun': lazy_fc_defun,
|
|
||||||
}
|
|
||||||
self._lisp_functions = {}
|
|
||||||
|
|
||||||
@property
|
|
||||||
def input(self):
|
|
||||||
return self._input
|
|
||||||
|
|
||||||
def set_variable(self, name: str, value: object):
|
|
||||||
self._variables[name] = value
|
|
||||||
|
|
||||||
def get_variable(self, name: str):
|
|
||||||
return self._variables[name]
|
|
||||||
|
|
||||||
def get_function(self, name: str):
|
|
||||||
return self._functions[name]
|
|
||||||
|
|
||||||
def get_lazy_function(self, name: str):
|
|
||||||
return self._lazy_functions[name]
|
|
||||||
|
|
||||||
def set_lisp_function(self, name: str, vars: list[object], e: object):
|
|
||||||
self._lisp_functions[name] = vars, e
|
|
||||||
|
|
||||||
def get_lisp_function(self, name: str) -> tuple[list[str], object]:
|
|
||||||
return self._lisp_functions[name]
|
|
||||||
|
|
||||||
class LocalEnvironment:
|
|
||||||
|
|
||||||
def __init__(self):
|
|
||||||
self._variables = {}
|
|
||||||
|
|
||||||
def copy(self) -> 'LocalEnvironment':
|
|
||||||
n = LocalEnvironment()
|
|
||||||
n._variables = self._variables.copy()
|
|
||||||
return n
|
|
||||||
|
|
||||||
def set_variable(self, name: str, value: object):
|
|
||||||
self._variables[name] = value
|
|
||||||
|
|
||||||
def get_variable(self, name: str):
|
|
||||||
return self._variables[name]
|
|
||||||
|
|
||||||
class Expression:
|
|
||||||
|
|
||||||
STATE_CONSTRUCTED = 0
|
|
||||||
STATE_REDUCED = 1
|
|
||||||
|
|
||||||
def __init__(self):
|
|
||||||
self._state = Expression.STATE_CONSTRUCTED
|
|
||||||
self._reduced = None
|
|
||||||
|
|
||||||
def _reduce(self, env: Environment, local: LocalEnvironment, args: list[object]):
|
|
||||||
_ = env, local, args
|
|
||||||
raise NotImplementedError('Implement in subclass')
|
|
||||||
|
|
||||||
def reduce(self, env: Environment, local: LocalEnvironment):
|
|
||||||
self._reduced = self._reduce(env, local, [])
|
|
||||||
self._state = Expression.STATE_REDUCED
|
|
||||||
|
|
||||||
@property
|
|
||||||
def reduced(self) -> object:
|
|
||||||
if self._state != Expression.STATE_REDUCED:
|
|
||||||
raise RuntimeError('Tried to get the reduced value before reducing')
|
|
||||||
return self._reduced
|
|
||||||
|
|
||||||
class Constant(Expression):
|
|
||||||
|
|
||||||
def __init__(self, value):
|
|
||||||
super().__init__()
|
|
||||||
self._value = value
|
|
||||||
|
|
||||||
def __repr__(self):
|
|
||||||
if isinstance(self._value, str):
|
|
||||||
return f'"{self._value}"'
|
|
||||||
return repr(self._value)
|
|
||||||
|
|
||||||
def _reduce(self, env: Environment, local: LocalEnvironment, args: list[Expression]):
|
|
||||||
_ = env, local, args
|
|
||||||
return self._value
|
|
||||||
|
|
||||||
class VariableSet(Constant):
|
|
||||||
|
|
||||||
def __repr__(self):
|
|
||||||
return self._value
|
|
||||||
|
|
||||||
class VariableGet(Constant):
|
|
||||||
|
|
||||||
def __repr__(self):
|
|
||||||
return self._value
|
|
||||||
|
|
||||||
def _reduce(self, env: Environment, local: LocalEnvironment, args: list[Expression]):
|
|
||||||
_ = args
|
|
||||||
try:
|
|
||||||
return local.get_variable(self._value)
|
|
||||||
except KeyError:
|
|
||||||
return env.get_variable(self._value)
|
|
||||||
|
|
||||||
class Function(Expression):
|
|
||||||
|
|
||||||
def __init__(self, fc, args: list[Expression]):
|
|
||||||
super().__init__()
|
|
||||||
self._fc = fc
|
|
||||||
self._args = args
|
|
||||||
|
|
||||||
def __repr__(self):
|
|
||||||
a = ' '.join([repr(a) for a in self._args])
|
|
||||||
return f'({self._fc} {a})'
|
|
||||||
|
|
||||||
def _reduce(self, env: Environment, local: LocalEnvironment, args: list[Expression]):
|
|
||||||
try:
|
|
||||||
argnames, fc = env.get_lisp_function(self._fc)
|
|
||||||
assert isinstance(fc, Expression)
|
|
||||||
l = local.copy()
|
|
||||||
for an, av in zip(argnames, args):
|
|
||||||
av.reduce(env, l)
|
|
||||||
l.set_variable(an, av.reduced)
|
|
||||||
fc.reduce(env, l)
|
|
||||||
r = fc.reduced
|
|
||||||
except KeyError as e:
|
|
||||||
try:
|
|
||||||
fc = env.get_function(self._fc)
|
|
||||||
[a.reduce(env, local) for a in args]
|
|
||||||
r = fc(env, local, *[a.reduced for a in args])
|
|
||||||
except KeyError:
|
|
||||||
fc = env.get_lazy_function(self._fc)
|
|
||||||
r = fc(env, local, *args)
|
|
||||||
return r
|
|
||||||
|
|
||||||
def reduce(self, env: Environment, local: LocalEnvironment):
|
|
||||||
self._reduced = self._reduce(env, local, self._args)
|
|
||||||
self._state = Expression.STATE_REDUCED
|
|
||||||
|
|
||||||
class Token:
|
|
||||||
|
|
||||||
CONSTANT_STRING = 0
|
|
||||||
CONSTANT_INTEGER = 10
|
|
||||||
CONSTANT_BOOLEAN = 20
|
|
||||||
KEYWORD = 30
|
|
||||||
VARIABLE_SET = 40
|
|
||||||
VARIABLE_GET = 50
|
|
||||||
FUNCTION = 60
|
|
||||||
GROUPING_OPEN = 70
|
|
||||||
GROUPING_CLOSE = 80
|
|
||||||
WHITESPACE = 90
|
|
||||||
|
|
||||||
def __init__(self, t, v):
|
|
||||||
self.t = t
|
|
||||||
self.v = v
|
|
||||||
|
|
||||||
def __repr__(self):
|
|
||||||
return f'{self.v}::{self.t}'
|
|
||||||
|
|
||||||
def to_expression(self):
|
|
||||||
if self.t == Token.CONSTANT_STRING:
|
|
||||||
return Constant(self.v[1:-1]) # slice away the quotes
|
|
||||||
if self.t == Token.CONSTANT_INTEGER:
|
|
||||||
return Constant(int(self.v, base=0))
|
|
||||||
if self.t == Token.CONSTANT_BOOLEAN:
|
|
||||||
return Constant(self.v == 'True')
|
|
||||||
if self.t == Token.KEYWORD:
|
|
||||||
raise RuntimeError(f'This is a meta token type and should be swallowed by the sanitizer: {self}')
|
|
||||||
if self.t == Token.VARIABLE_GET:
|
|
||||||
return VariableGet(self.v)
|
|
||||||
if self.t == Token.VARIABLE_SET:
|
|
||||||
return VariableSet(self.v)
|
|
||||||
if self.t == Token.FUNCTION:
|
|
||||||
raise RuntimeError('Cant construct function just from its token')
|
|
||||||
if self.t == Token.GROUPING_OPEN or self.t == Token.GROUPING_CLOSE:
|
|
||||||
raise RuntimeError('Groupings should never be constructed, this is a bug')
|
|
||||||
if self.t == Token.WHITESPACE:
|
|
||||||
raise RuntimeError('Whitespaces should not be present in this stage of the build')
|
|
||||||
raise RuntimeError(f'The token type {self.t} is not implemented')
|
|
||||||
|
|
||||||
def token_extract_string(stream: str) -> tuple[Token, str]:
|
|
||||||
if stream[0] != '"':
|
|
||||||
raise ValueError('No such token in stream')
|
|
||||||
i = stream.find('"', 1)
|
|
||||||
return Token(Token.CONSTANT_STRING, stream[:i+1]), stream[i+1:]
|
|
||||||
|
|
||||||
def token_extract_integer(stream: str) -> tuple[Token, str]:
|
|
||||||
i = 0
|
|
||||||
base = None
|
|
||||||
if stream[i] in '+-':
|
|
||||||
i += 1
|
|
||||||
if stream[i] in '0123456789':
|
|
||||||
i += 1
|
|
||||||
else:
|
|
||||||
raise ValueError('Malformed integer')
|
|
||||||
|
|
||||||
if stream[i] in 'xbo':
|
|
||||||
base = stream[i]
|
|
||||||
i += 1
|
|
||||||
int_set = {None: '0123456789', 'x': '0123456789abcdefABCDEF', 'b': '01', 'o': '01234567'}[base]
|
|
||||||
while stream[i] in int_set:
|
|
||||||
i += 1
|
|
||||||
return Token(Token.CONSTANT_INTEGER, stream[:i]), stream[i:]
|
|
||||||
|
|
||||||
def token_extract_boolean(stream: str) -> tuple[Token, str]:
|
|
||||||
if stream.startswith('True'):
|
|
||||||
return Token(Token.CONSTANT_BOOLEAN, stream[:4]), stream[4:]
|
|
||||||
elif stream.startswith('False'):
|
|
||||||
return Token(Token.CONSTANT_BOOLEAN, stream[:5]), stream[5:]
|
|
||||||
raise ValueError('No such token in stream')
|
|
||||||
|
|
||||||
def token_extract_keyword(stream: str) -> tuple[Token, str]:
|
|
||||||
i = 0
|
|
||||||
if stream[i] in string.ascii_letters + '_-><=!+-*/?&|':
|
|
||||||
i += 1
|
|
||||||
else:
|
|
||||||
raise ValueError('No keyword in stream')
|
|
||||||
while stream[i] in string.ascii_letters + string.digits + '_-><=!+-*/?&|':
|
|
||||||
i += 1
|
|
||||||
return Token(Token.KEYWORD, stream[:i]), stream[i:]
|
|
||||||
|
|
||||||
def token_extract_grouping_open(stream: str) -> tuple[Token, str]:
|
|
||||||
if stream[0] == '(':
|
|
||||||
return Token(Token.GROUPING_OPEN, '('), stream[1:]
|
|
||||||
raise ValueError('No such token in stream')
|
|
||||||
|
|
||||||
def token_extract_grouping_close(stream: str) -> tuple[Token, str]:
|
|
||||||
if stream[0] == ')':
|
|
||||||
return Token(Token.GROUPING_CLOSE, ')'), stream[1:]
|
|
||||||
raise ValueError('No such token in stream')
|
|
||||||
|
|
||||||
def token_extract_space(stream: str) -> tuple[Token, str]:
|
|
||||||
i = 0
|
|
||||||
try:
|
|
||||||
while stream[i] in string.whitespace:
|
|
||||||
i += 1
|
|
||||||
except IndexError:
|
|
||||||
pass
|
|
||||||
return Token(Token.WHITESPACE, stream[:i]), stream[i:]
|
|
||||||
|
|
||||||
def tokenize(program: str) -> list[Token]:
|
|
||||||
extractors = [
|
|
||||||
token_extract_boolean,
|
|
||||||
token_extract_integer,
|
|
||||||
token_extract_string,
|
|
||||||
token_extract_keyword,
|
|
||||||
token_extract_grouping_open,
|
|
||||||
token_extract_grouping_close,
|
|
||||||
token_extract_space,
|
|
||||||
]
|
|
||||||
p = program
|
|
||||||
tokens = []
|
|
||||||
while p:
|
|
||||||
success = False
|
|
||||||
for e in extractors:
|
|
||||||
try:
|
|
||||||
t, p = e(p)
|
|
||||||
tokens += [t]
|
|
||||||
success = True
|
|
||||||
break
|
|
||||||
except ValueError:
|
|
||||||
pass
|
|
||||||
if not success:
|
|
||||||
raise ValueError('Program is invalid')
|
|
||||||
return [t for t in tokens if t.t != Token.WHITESPACE]
|
|
||||||
|
|
||||||
def tokenize_sanitize_function(token_before: Token | None, token: Token, token_after: Token | None) -> Token | None:
|
|
||||||
if token_before is None:
|
|
||||||
return
|
|
||||||
if token_before.t == Token.GROUPING_OPEN and token.t == Token.KEYWORD:
|
|
||||||
return Token(Token.FUNCTION, token.v)
|
|
||||||
|
|
||||||
def tokenize_sanitize_setvar(token_before: Token | None, token: Token, token_after: Token | None) -> Token | None:
|
|
||||||
if token_before is None:
|
|
||||||
return
|
|
||||||
if (token_before.t == Token.FUNCTION and token_before.v in ('setq', 'let')) and token.t == Token.KEYWORD:
|
|
||||||
return Token(Token.VARIABLE_SET, token.v)
|
|
||||||
|
|
||||||
def tokenize_sanitize_getvar(token_before: Token | None, token: Token, token_after: Token | None) -> Token | None:
|
|
||||||
if token_before is None:
|
|
||||||
if token.t == Token.KEYWORD:
|
|
||||||
return Token(Token.VARIABLE_GET, token.v)
|
|
||||||
return
|
|
||||||
if (token_before.t != Token.FUNCTION or token_before.v not in ('setq', 'let')) and token.t == Token.KEYWORD:
|
|
||||||
return Token(Token.VARIABLE_GET, token.v)
|
|
||||||
|
|
||||||
def _tokenize_sanitize(tokens: list[Token]) -> tuple[bool, list[Token]]:
|
|
||||||
sanitizers = [
|
|
||||||
tokenize_sanitize_function,
|
|
||||||
tokenize_sanitize_setvar,
|
|
||||||
tokenize_sanitize_getvar,
|
|
||||||
]
|
|
||||||
new_tokens = []
|
|
||||||
changed = False
|
|
||||||
for i in range(len(tokens)):
|
|
||||||
for s in sanitizers:
|
|
||||||
p_token = new_tokens[i-1] if i > 0 else None
|
|
||||||
n_token = tokens[i+1] if i < (len(tokens)-1) else None
|
|
||||||
new_token = s(p_token, tokens[i], n_token)
|
|
||||||
if new_token is not None:
|
|
||||||
changed = True
|
|
||||||
new_tokens += [new_token]
|
|
||||||
break
|
|
||||||
else:
|
|
||||||
new_tokens += [tokens[i]]
|
|
||||||
return changed, new_tokens
|
|
||||||
|
|
||||||
def tokenize_sanitize(tokens: list[Token]) -> list[Token]:
|
|
||||||
_, tokens = _tokenize_sanitize(tokens)
|
|
||||||
return tokens
|
|
||||||
|
|
||||||
def take_token_group(tokens: list[Token], n: int = 1) -> list[Token]:
|
|
||||||
i = 0
|
|
||||||
start = i
|
|
||||||
group_count = 0
|
|
||||||
consider_groups = False
|
|
||||||
while n:
|
|
||||||
if tokens[i].t == Token.GROUPING_OPEN:
|
|
||||||
consider_groups = True
|
|
||||||
if group_count == 0:
|
|
||||||
start = i
|
|
||||||
group_count += 1
|
|
||||||
elif tokens[i].t == Token.GROUPING_CLOSE:
|
|
||||||
group_count -= 1
|
|
||||||
if group_count == 0:
|
|
||||||
consider_groups = False
|
|
||||||
else:
|
|
||||||
if not consider_groups:
|
|
||||||
start = i
|
|
||||||
if group_count == 0:
|
|
||||||
n -= 1
|
|
||||||
if group_count < 0:
|
|
||||||
raise ValueError('reached past end')
|
|
||||||
i += 1
|
|
||||||
return tokens[start:i]
|
|
||||||
|
|
||||||
def unwrap_token_group(tokens: list[Token]) -> list[Token]:
|
|
||||||
if tokens[0].t != Token.GROUPING_OPEN:
|
|
||||||
return tokens
|
|
||||||
|
|
||||||
brace_count = 0
|
|
||||||
for i, t in enumerate(tokens):
|
|
||||||
brace_count += int(t.t == Token.GROUPING_OPEN)
|
|
||||||
brace_count -= int(t.t == Token.GROUPING_CLOSE)
|
|
||||||
if i == len(tokens) - 2:
|
|
||||||
if brace_count > 0:
|
|
||||||
tokens = tokens[1:-1]
|
|
||||||
break
|
|
||||||
return tokens
|
|
||||||
|
|
||||||
def build(tokens: list[Token]) -> Expression:
|
|
||||||
tokens = unwrap_token_group(tokens)
|
|
||||||
token_groups: list[list[Token]] = []
|
|
||||||
i = 1
|
|
||||||
while True:
|
|
||||||
try:
|
|
||||||
token_groups += [take_token_group(tokens, n=i)]
|
|
||||||
i += 1
|
|
||||||
except IndexError:
|
|
||||||
break
|
|
||||||
|
|
||||||
# special function case
|
|
||||||
if len(token_groups[0]) == 1 and token_groups[0][0].t == Token.FUNCTION:
|
|
||||||
token_0 = token_groups[0][0]
|
|
||||||
args = [build(tg) for tg in token_groups[1:]]
|
|
||||||
return Function(token_0.v, args)
|
|
||||||
|
|
||||||
# combine to multiple statements
|
|
||||||
if len(token_groups) > 1:
|
|
||||||
return Function('__last__', [build(tg) for tg in token_groups])
|
|
||||||
|
|
||||||
# create a basic expression
|
|
||||||
if len(token_groups) == 1 and len(token_groups[0]) == 1:
|
|
||||||
return token_groups[0][0].to_expression()
|
|
||||||
|
|
||||||
raise RuntimeError(f'Did not handle token case in build function, token_groups: {token_groups}')
|
|
||||||
|
|
||||||
def parse(program: str) -> Expression:
|
|
||||||
tokens = tokenize_sanitize(tokenize(program))
|
|
||||||
expression = build(tokens)
|
|
||||||
return expression
|
|
||||||
|
|
||||||
class Filter(Expression):
|
|
||||||
|
|
||||||
@classmethod
|
|
||||||
def __get_validators__(cls):
|
|
||||||
yield cls.validate
|
|
||||||
|
|
||||||
@classmethod
|
|
||||||
def __modify_schema__(cls, field_schema):
|
|
||||||
pass
|
|
||||||
|
|
||||||
@classmethod
|
|
||||||
def validate(cls, v):
|
|
||||||
if not isinstance(v, str):
|
|
||||||
raise TypeError('Must be string')
|
|
||||||
return parse(v)
|
|
||||||
|
|
||||||
class Command(str):
|
|
||||||
|
|
||||||
@classmethod
|
|
||||||
def __get_validators__(cls):
|
|
||||||
yield cls.validate
|
|
||||||
|
|
||||||
@classmethod
|
|
||||||
def __modify_schema__(cls, field_schema):
|
|
||||||
pass
|
|
||||||
|
|
||||||
@classmethod
|
|
||||||
def validate(cls, v):
|
|
||||||
if not isinstance(v, (str, list, tuple)):
|
|
||||||
raise TypeError('Must be string or list')
|
|
||||||
if isinstance(v, (list, tuple)):
|
|
||||||
v = ' '.join([f"'{x}'" for x in v])
|
|
||||||
return v
|
|
||||||
|
|
||||||
class Signal(int):
|
|
||||||
|
|
||||||
@classmethod
|
|
||||||
def __get_validators__(cls):
|
|
||||||
yield cls.validate
|
|
||||||
|
|
||||||
@classmethod
|
|
||||||
def __modify_schema__(cls, field_schema):
|
|
||||||
pass
|
|
||||||
|
|
||||||
@classmethod
|
|
||||||
def validate(cls, v):
|
|
||||||
if not isinstance(v, (str, int)):
|
|
||||||
raise TypeError('Must be string or int')
|
|
||||||
if isinstance(v, str) and v.isnumeric():
|
|
||||||
return signal.Signals(int(v))
|
|
||||||
elif isinstance(v, int):
|
|
||||||
return signal.Signals(v)
|
|
||||||
return getattr(signal.Signals, v)
|
|
||||||
|
|
||||||
class Lock(asyncio.Lock):
|
|
||||||
@classmethod
|
|
||||||
def __get_validators__(cls):
|
|
||||||
yield cls.validate
|
|
||||||
@classmethod
|
|
||||||
def __modify_schema__(cls, field_schema):
|
|
||||||
pass
|
|
||||||
@classmethod
|
|
||||||
def validate(cls, v):
|
|
||||||
if not isinstance(v, asyncio.Lock):
|
|
||||||
raise TypeError('Must be a asyncio.Lock')
|
|
||||||
return v
|
|
||||||
|
|
||||||
class Event(asyncio.Event):
|
|
||||||
@classmethod
|
|
||||||
def __get_validators__(cls):
|
|
||||||
yield cls.validate
|
|
||||||
@classmethod
|
|
||||||
def __modify_schema__(cls, field_schema):
|
|
||||||
pass
|
|
||||||
@classmethod
|
|
||||||
def validate(cls, v):
|
|
||||||
if not isinstance(v, asyncio.Event):
|
|
||||||
raise TypeError('Must be a asyncio.Event')
|
|
||||||
return v
|
|
||||||
|
|
||||||
class Connection(i3ipc.aio.Connection):
|
|
||||||
@classmethod
|
|
||||||
def __get_validators__(cls):
|
|
||||||
yield cls.validate
|
|
||||||
@classmethod
|
|
||||||
def __modify_schema__(cls, field_schema):
|
|
||||||
pass
|
|
||||||
@classmethod
|
|
||||||
def validate(cls, v):
|
|
||||||
if not isinstance(v, i3ipc.aio.Connection):
|
|
||||||
raise TypeError('Must be a i3ipc.aio.Connection')
|
|
||||||
return v
|
|
||||||
|
|
||||||
|
|
||||||
class ProgramConfig(pydantic.BaseModel):
|
|
||||||
cmd: Command
|
|
||||||
workspace: typing.Optional[str] = None
|
|
||||||
signal: bool = False
|
|
||||||
timeout: int = 1000
|
|
||||||
match: Filter
|
|
||||||
|
|
||||||
class Config(pydantic.BaseModel):
|
|
||||||
signal: typing.Optional[Signal] = None
|
|
||||||
timeout: int = 3000
|
|
||||||
init: typing.Optional[Filter] = None
|
|
||||||
programs: typing.List[ProgramConfig]
|
|
||||||
final_workspace: typing.Optional[str] = None
|
|
||||||
final_workspace_delay: int = 100
|
|
||||||
|
|
||||||
class RuntimeData(pydantic.BaseModel):
|
|
||||||
init: typing.Optional[str]
|
|
||||||
programs: typing.List[ProgramConfig] = []
|
|
||||||
lock: Lock
|
|
||||||
event: Event
|
|
||||||
ipc: Connection
|
|
||||||
|
|
||||||
def window_new(runtime_data: RuntimeData, *, debug):
|
|
||||||
async def callback(ipc: i3ipc.aio.Connection, e: i3ipc.WorkspaceEvent):
|
|
||||||
assert e.change == 'new'
|
|
||||||
LOGGER.debug('New window: %s', json.dumps(e.ipc_data))
|
|
||||||
async with runtime_data.lock:
|
|
||||||
env = Environment(e.ipc_data)
|
|
||||||
local = LocalEnvironment()
|
|
||||||
if runtime_data.init is not None:
|
|
||||||
parse(runtime_data.init).reduce(env, local)
|
|
||||||
for i, cfg in enumerate(runtime_data.programs):
|
|
||||||
cfg.match.reduce(env, local)
|
|
||||||
LOGGER.debug('Tried to match %s, result: %s', cfg.match, cfg.match.reduced)
|
|
||||||
if cfg.match.reduced:
|
|
||||||
container_id = e.ipc_data['container']['id']
|
|
||||||
await ipc.command(f'for_window [con_id="{container_id}"] focus')
|
|
||||||
await ipc.command(f'move container to workspace {cfg.workspace}')
|
|
||||||
runtime_data.programs.pop(i)
|
|
||||||
if not runtime_data.programs:
|
|
||||||
ipc.main_quit()
|
|
||||||
return callback
|
|
||||||
|
|
||||||
async def wait_signal(rt: RuntimeData):
|
|
||||||
await rt.event.wait()
|
|
||||||
rt.event.clear()
|
|
||||||
|
|
||||||
async def coro_wait_signal(coro, rt: RuntimeData):
|
|
||||||
await coro
|
|
||||||
await wait_signal(rt)
|
|
||||||
|
|
||||||
async def init(config: Config, *, debug: bool) -> RuntimeData:
|
|
||||||
rd = RuntimeData(
|
|
||||||
init=str(config.init),
|
|
||||||
programs=[p for p in config.programs if p.workspace is not None],
|
|
||||||
lock=Lock(),
|
|
||||||
event=Event(),
|
|
||||||
ipc=Connection(),
|
|
||||||
)
|
|
||||||
logging.basicConfig(level=logging.WARNING)
|
|
||||||
if debug:
|
|
||||||
LOGGER.setLevel(logging.DEBUG)
|
|
||||||
else:
|
|
||||||
LOGGER.setLevel(logging.INFO)
|
|
||||||
if config.signal is not None:
|
|
||||||
asyncio.get_running_loop().add_signal_handler(config.signal, lambda: rd.event.set())
|
|
||||||
return rd
|
|
||||||
|
|
||||||
async def run(config: Config, *, debug: bool):
|
|
||||||
runtime_data = await init(config, debug=debug)
|
|
||||||
await runtime_data.ipc.connect()
|
|
||||||
handler = window_new(runtime_data, debug=debug)
|
|
||||||
runtime_data.ipc.on('window::new', handler)
|
|
||||||
|
|
||||||
variables = {
|
|
||||||
'pid': os.getpid(),
|
|
||||||
}
|
|
||||||
coroutines = []
|
|
||||||
timeout = config.timeout
|
|
||||||
started_at = time.monotonic_ns()
|
|
||||||
for cfg in config.programs:
|
|
||||||
p = cfg.cmd.format(**variables)
|
|
||||||
coro = runtime_data.ipc.command(f'exec {p}')
|
|
||||||
if cfg.signal:
|
|
||||||
coro = coro_wait_signal(coro, runtime_data)
|
|
||||||
if cfg.timeout is not None:
|
|
||||||
timeout = max(timeout, cfg.timeout)
|
|
||||||
try:
|
|
||||||
await asyncio.wait_for(coro, timeout=cfg.timeout/1000 if cfg.timeout is not None else 0)
|
|
||||||
except asyncio.TimeoutError:
|
|
||||||
pass
|
|
||||||
else:
|
|
||||||
coroutines += [coro]
|
|
||||||
await asyncio.gather(*coroutines)
|
|
||||||
try:
|
|
||||||
if runtime_data.programs:
|
|
||||||
# run main loop only if we wait for something
|
|
||||||
diff = (time.monotonic_ns() - started_at) / (1000*1000)
|
|
||||||
new_timeout = max(timeout - diff, 0)
|
|
||||||
await asyncio.wait_for(runtime_data.ipc.main(), timeout=new_timeout/1000)
|
|
||||||
except asyncio.TimeoutError:
|
|
||||||
runtime_data.ipc.off(handler)
|
|
||||||
if runtime_data.programs:
|
|
||||||
LOGGER.debug('Not all programs consumed: %s', runtime_data.programs)
|
|
||||||
LOGGER.debug('Maybe the timeouts are too short?')
|
|
||||||
return 1
|
|
||||||
finally:
|
|
||||||
if config.final_workspace is not None:
|
|
||||||
await asyncio.sleep(config.final_workspace_delay/1000)
|
|
||||||
await runtime_data.ipc.command(f'workspace {config.final_workspace}')
|
|
||||||
return 0
|
|
||||||
|
|
||||||
@click.group()
|
|
||||||
@click.pass_context
|
|
||||||
@click.option('--debug', '-d', default=False, is_flag=True, help="Enable debug mode, will log ipc dictionary.")
|
|
||||||
def main(ctx, debug):
|
|
||||||
ctx.ensure_object(dict)
|
|
||||||
ctx.obj['DEBUG'] = debug
|
|
||||||
|
|
||||||
@main.command()
|
|
||||||
@click.pass_context
|
|
||||||
@click.option('--filter', '-f', default='True', help="A filter expression for the raw ipc dictionary.")
|
|
||||||
@click.option('--timeout', '-t', default=3000, help="Wait time for a window to appear (and match) in milliseconds.")
|
|
||||||
@click.option('--workspace', '-w', default=None, help="The workspace to move to.")
|
|
||||||
@click.argument('command', nargs=-1)
|
|
||||||
def simple(ctx, filter, timeout, workspace, command):
|
|
||||||
"""
|
|
||||||
Start a program and move it's created window to the desired i3 workspace.
|
|
||||||
|
|
||||||
\b
|
|
||||||
Exist status:
|
|
||||||
0 on success,
|
|
||||||
1 when no window has been found.
|
|
||||||
"""
|
|
||||||
debug = ctx.obj['DEBUG']
|
|
||||||
config = Config(programs=[ProgramConfig(
|
|
||||||
cmd=command,
|
|
||||||
workspace=workspace,
|
|
||||||
match=filter,
|
|
||||||
)], timeout=timeout)
|
|
||||||
ctx.exit(asyncio.run(run(config, debug=debug)))
|
|
||||||
|
|
||||||
@main.command()
|
|
||||||
@click.pass_context
|
|
||||||
@click.argument('config', type=click.File('r'), default='-')
|
|
||||||
def config(ctx, config):
|
|
||||||
"""
|
|
||||||
Start a program and move it's created window to the desired i3 workspace.
|
|
||||||
|
|
||||||
\b
|
|
||||||
Exist status:
|
|
||||||
0 on success,
|
|
||||||
1 when no window has been found.
|
|
||||||
"""
|
|
||||||
debug = ctx.obj['DEBUG']
|
|
||||||
config = Config(**yaml.load(config, Loader=SafeLoader))
|
|
||||||
ctx.exit(asyncio.run(run(config, debug=debug)))
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
|
||||||
main()
|
|
||||||
|
|
|
@ -1,5 +0,0 @@
|
||||||
click
|
|
||||||
pydantic
|
|
||||||
pyyaml
|
|
||||||
i3ipc
|
|
||||||
|
|
105
src/config.rs
Normal file
105
src/config.rs
Normal file
|
@ -0,0 +1,105 @@
|
||||||
|
use std::fmt::{Display, Formatter};
|
||||||
|
|
||||||
|
use rust_lisp::model::Value as RValue;
|
||||||
|
use serde::{Deserialize, Deserializer};
|
||||||
|
|
||||||
|
#[derive(Clone, Debug)]
|
||||||
|
pub struct Value(Vec<RValue>);
|
||||||
|
unsafe impl Send for Value {}
|
||||||
|
unsafe impl Sync for Value {}
|
||||||
|
|
||||||
|
impl Into<Value> for RValue {
|
||||||
|
fn into(self) -> Value {
|
||||||
|
Value(vec![self])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Into<Vec<RValue>> for Value {
|
||||||
|
fn into(self) -> Vec<RValue> {
|
||||||
|
self.0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Display for Value {
|
||||||
|
fn fmt(&self, f: &mut Formatter) -> std::fmt::Result {
|
||||||
|
let mut s = String::new();
|
||||||
|
s.push_str("(begin\n");
|
||||||
|
for i in &self.0 {
|
||||||
|
s.push_str(&format!("{}\n", i));
|
||||||
|
}
|
||||||
|
s.push_str(")");
|
||||||
|
write!(f, "{}", &s)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'de> Deserialize<'de> for Value {
|
||||||
|
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
|
||||||
|
where
|
||||||
|
D: Deserializer<'de>,
|
||||||
|
{
|
||||||
|
let s: String = Deserialize::deserialize(deserializer)?;
|
||||||
|
let r: Vec<RValue> = rust_lisp::parser::parse(&s)
|
||||||
|
.filter_map(|x| x.ok())
|
||||||
|
.collect();
|
||||||
|
Ok(Value(r))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Debug, Deserialize)]
|
||||||
|
pub struct Program {
|
||||||
|
#[serde(rename = "match")]
|
||||||
|
pub match_: Value,
|
||||||
|
pub cmd: String,
|
||||||
|
#[serde(default)]
|
||||||
|
pub run: Option<String>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Debug, Deserialize)]
|
||||||
|
pub struct Signal {
|
||||||
|
#[serde(default)]
|
||||||
|
pub run: Option<String>,
|
||||||
|
#[serde(default = "Signal::default_timeout")]
|
||||||
|
pub timeout: u64,
|
||||||
|
}
|
||||||
|
impl Signal {
|
||||||
|
fn default_timeout() -> u64 {
|
||||||
|
500
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Debug, Deserialize)]
|
||||||
|
#[serde(untagged)]
|
||||||
|
pub enum ProgramEntry {
|
||||||
|
Program(Program),
|
||||||
|
Signal(Signal),
|
||||||
|
}
|
||||||
|
|
||||||
|
// Program is only unsafe because Value has dyn Any in it (via Foreign).
|
||||||
|
// if we don't use !Send in Foreign everything is fine.
|
||||||
|
unsafe impl Send for Program {}
|
||||||
|
|
||||||
|
#[derive(Clone, Debug, Deserialize)]
|
||||||
|
pub struct Config {
|
||||||
|
#[serde(default = "Config::default_timeout")]
|
||||||
|
pub timeout: u64,
|
||||||
|
#[serde(default = "Config::default_init")]
|
||||||
|
pub init: Value,
|
||||||
|
#[serde(default)]
|
||||||
|
pub cmd: Option<String>,
|
||||||
|
#[serde(default = "Config::default_programs")]
|
||||||
|
pub programs: Vec<ProgramEntry>,
|
||||||
|
}
|
||||||
|
// Config is only unsafe because Value has dyn Any in it (via Foreign).
|
||||||
|
// if we don't use !Send in Foreign everything is fine.
|
||||||
|
unsafe impl Send for Config {}
|
||||||
|
impl Config {
|
||||||
|
fn default_timeout() -> u64 {
|
||||||
|
3000
|
||||||
|
}
|
||||||
|
fn default_init() -> Value {
|
||||||
|
Value(vec![])
|
||||||
|
}
|
||||||
|
fn default_programs() -> Vec<ProgramEntry> {
|
||||||
|
vec![]
|
||||||
|
}
|
||||||
|
}
|
236
src/i3ipc.rs
Normal file
236
src/i3ipc.rs
Normal file
|
@ -0,0 +1,236 @@
|
||||||
|
use std::collections::HashMap;
|
||||||
|
use std::pin::Pin;
|
||||||
|
use std::str::FromStr;
|
||||||
|
|
||||||
|
|
||||||
|
use anyhow::Result;
|
||||||
|
|
||||||
|
use serde::{Deserialize, Serialize};
|
||||||
|
use tokio::io::{AsyncReadExt, AsyncWriteExt, BufStream};
|
||||||
|
use tokio::net::UnixStream;
|
||||||
|
|
||||||
|
pub async fn get_socket_path() -> Result<std::path::PathBuf, anyhow::Error> {
|
||||||
|
if let Ok(p) = std::env::var("I3SOCK") {
|
||||||
|
return Ok(std::path::PathBuf::from_str(&p).unwrap());
|
||||||
|
}
|
||||||
|
if let Ok(p) = std::env::var("SWAYSOCK") {
|
||||||
|
return Ok(std::path::PathBuf::from_str(&p).unwrap());
|
||||||
|
}
|
||||||
|
|
||||||
|
for command_name in ["i3", "sway"] {
|
||||||
|
let output = tokio::process::Command::new(command_name)
|
||||||
|
.arg("--get-socketpath")
|
||||||
|
.output()
|
||||||
|
.await?;
|
||||||
|
if output.status.success() {
|
||||||
|
return Ok(std::path::PathBuf::from_str(
|
||||||
|
String::from_utf8_lossy(&output.stdout).trim_end_matches('\n'),
|
||||||
|
)
|
||||||
|
.unwrap());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Err(tokio::io::Error::new(tokio::io::ErrorKind::Other, ""))?
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq, Serialize, Deserialize)]
|
||||||
|
#[serde(rename_all = "snake_case")]
|
||||||
|
#[repr(u32)]
|
||||||
|
pub enum MessageType {
|
||||||
|
Command = 0,
|
||||||
|
Workspace = 1,
|
||||||
|
Subscribe = 2,
|
||||||
|
Outputs = 3,
|
||||||
|
Tree = 4,
|
||||||
|
Marks = 5,
|
||||||
|
BarConfig = 6,
|
||||||
|
Version = 7,
|
||||||
|
BindingModes = 8,
|
||||||
|
Config = 9,
|
||||||
|
Tick = 10,
|
||||||
|
Sync = 11,
|
||||||
|
BindingState = 12,
|
||||||
|
#[serde(rename = "workspace")]
|
||||||
|
SubWorkspace = 0 | 1 << 31,
|
||||||
|
#[serde(rename = "output")]
|
||||||
|
SubOutput = 1 | 1 << 31,
|
||||||
|
#[serde(rename = "mode")]
|
||||||
|
SubMode = 2 | 1 << 31,
|
||||||
|
#[serde(rename = "window")]
|
||||||
|
SubWindow = 3 | 1 << 31,
|
||||||
|
#[serde(rename = "barconfig_update")]
|
||||||
|
SubBarConfig = 4 | 1 << 31,
|
||||||
|
#[serde(rename = "binding")]
|
||||||
|
SubBinding = 5 | 1 << 31,
|
||||||
|
#[serde(rename = "shutdown")]
|
||||||
|
SubShutdown = 6 | 1 << 31,
|
||||||
|
#[serde(rename = "tick")]
|
||||||
|
SubTick = 7 | 1 << 31,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl MessageType {
|
||||||
|
pub fn is_subscription(&self) -> bool {
|
||||||
|
((*self as u32) & (1 << 31)) != 0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl TryFrom<u32> for MessageType {
|
||||||
|
type Error = &'static str;
|
||||||
|
fn try_from(value: u32) -> Result<Self, Self::Error> {
|
||||||
|
Ok(match value {
|
||||||
|
0x00000000 => Self::Command,
|
||||||
|
0x00000001 => Self::Workspace,
|
||||||
|
0x00000002 => Self::Subscribe,
|
||||||
|
0x00000003 => Self::Outputs,
|
||||||
|
0x00000004 => Self::Tree,
|
||||||
|
0x00000005 => Self::Marks,
|
||||||
|
0x00000006 => Self::BarConfig,
|
||||||
|
0x00000007 => Self::Version,
|
||||||
|
0x00000008 => Self::BindingModes,
|
||||||
|
0x00000009 => Self::Config,
|
||||||
|
0x0000000a => Self::Tick,
|
||||||
|
0x0000000b => Self::Sync,
|
||||||
|
0x0000000c => Self::BindingState,
|
||||||
|
0x80000000 => Self::SubWorkspace,
|
||||||
|
0x80000001 => Self::SubOutput,
|
||||||
|
0x80000002 => Self::SubMode,
|
||||||
|
0x80000003 => Self::SubWindow,
|
||||||
|
0x80000004 => Self::SubBarConfig,
|
||||||
|
0x80000005 => Self::SubBinding,
|
||||||
|
0x80000006 => Self::SubShutdown,
|
||||||
|
0x80000007 => Self::SubTick,
|
||||||
|
_ => return Err(""),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type SubscriptionCallback =
|
||||||
|
dyn Fn(
|
||||||
|
MessageType,
|
||||||
|
serde_json::Value,
|
||||||
|
) -> Pin<Box<dyn std::future::Future<Output = Vec<(MessageType, Vec<u8>)>> + Send>>;
|
||||||
|
|
||||||
|
pub struct Connection<'a> {
|
||||||
|
stream: BufStream<UnixStream>,
|
||||||
|
subscriptions: HashMap<MessageType, Box<&'a SubscriptionCallback>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a> Connection<'a> {
|
||||||
|
pub fn connect(path: &std::path::Path) -> Result<Self, anyhow::Error> {
|
||||||
|
let stream = std::os::unix::net::UnixStream::connect(path)?;
|
||||||
|
stream.set_nonblocking(true)?;
|
||||||
|
let stream = BufStream::new(UnixStream::from_std(stream)?);
|
||||||
|
let subscriptions = HashMap::new();
|
||||||
|
Ok(Self {
|
||||||
|
stream,
|
||||||
|
subscriptions,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn send_message(
|
||||||
|
&mut self,
|
||||||
|
message_type: &MessageType,
|
||||||
|
message: &[u8],
|
||||||
|
) -> Result<(), anyhow::Error> {
|
||||||
|
self.stream.write_all(b"i3-ipc").await?;
|
||||||
|
self.stream.write_u32_le(message.len() as u32).await?;
|
||||||
|
self.stream.write_u32_le(*message_type as u32).await?;
|
||||||
|
self.stream.write_all(message).await?;
|
||||||
|
self.stream.flush().await?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn receive_message(&mut self) -> Result<(MessageType, Vec<u8>), anyhow::Error> {
|
||||||
|
let mut buffer = vec![0u8; 6];
|
||||||
|
self.stream.read_exact(&mut buffer).await?;
|
||||||
|
if buffer != b"i3-ipc" {
|
||||||
|
return Err(tokio::io::Error::new(tokio::io::ErrorKind::Other, ""))?;
|
||||||
|
}
|
||||||
|
let message_len = self.stream.read_u32_le().await?;
|
||||||
|
let message_type = self.stream.read_u32_le().await?.try_into().unwrap();
|
||||||
|
let mut buffer = vec![0u8; message_len as usize];
|
||||||
|
self.stream.read_exact(&mut buffer).await?;
|
||||||
|
Ok((message_type, buffer))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn communicate(
|
||||||
|
&mut self,
|
||||||
|
message_type: &MessageType,
|
||||||
|
message: &[u8],
|
||||||
|
) -> Result<(MessageType, serde_json::Value), anyhow::Error> {
|
||||||
|
self.send_message(message_type, message).await?;
|
||||||
|
let (message_type, response) = self.receive_message().await?;
|
||||||
|
Ok((
|
||||||
|
message_type,
|
||||||
|
serde_json::from_str(String::from_utf8_lossy(response.as_ref()).as_ref())?,
|
||||||
|
))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn subscribe(
|
||||||
|
&mut self,
|
||||||
|
events: &[MessageType],
|
||||||
|
callback: &'a SubscriptionCallback,
|
||||||
|
) -> Result<(), anyhow::Error> {
|
||||||
|
let json = serde_json::to_string(events)?;
|
||||||
|
let (message_type, response) = self
|
||||||
|
.communicate(&MessageType::Subscribe, json.as_bytes())
|
||||||
|
.await?;
|
||||||
|
for s in events {
|
||||||
|
self.subscriptions.insert(*s, Box::new(callback));
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn call_callback(
|
||||||
|
&mut self,
|
||||||
|
subscription: &MessageType,
|
||||||
|
response: serde_json::Value,
|
||||||
|
) -> Vec<(MessageType, Vec<u8>)> {
|
||||||
|
let cb = self.subscriptions.get(subscription);
|
||||||
|
if cb.is_none() {
|
||||||
|
return Vec::new();
|
||||||
|
}
|
||||||
|
(*cb.unwrap())(*subscription, response).await
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn run(
|
||||||
|
&mut self,
|
||||||
|
rx: &mut tokio::sync::broadcast::Receiver<()>,
|
||||||
|
) -> Result<(), anyhow::Error> {
|
||||||
|
loop {
|
||||||
|
let stop_task = rx.recv();
|
||||||
|
let receive_message_task = self.receive_message();
|
||||||
|
let result = tokio::select! {
|
||||||
|
_ = stop_task => {return Ok(())},
|
||||||
|
result = receive_message_task => result?,
|
||||||
|
};
|
||||||
|
let (message_type, response) = result;
|
||||||
|
if !message_type.is_subscription() {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
let json_response =
|
||||||
|
serde_json::from_str(String::from_utf8_lossy(response.as_ref()).as_ref())?;
|
||||||
|
let messages: Vec<(MessageType, Vec<u8>)> =
|
||||||
|
self.call_callback(&message_type, json_response).await;
|
||||||
|
for (message_type, message) in messages {
|
||||||
|
// TODO maybe log responses?
|
||||||
|
self.communicate(&message_type, &message).await?;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a> Clone for Connection<'a> {
|
||||||
|
fn clone(&self) -> Self {
|
||||||
|
let path: std::path::PathBuf = self
|
||||||
|
.stream
|
||||||
|
.get_ref()
|
||||||
|
.peer_addr()
|
||||||
|
.unwrap()
|
||||||
|
.as_pathname()
|
||||||
|
.unwrap()
|
||||||
|
.into();
|
||||||
|
Self::connect(path.as_ref()).unwrap()
|
||||||
|
}
|
||||||
|
}
|
119
src/lisp.rs
Normal file
119
src/lisp.rs
Normal file
|
@ -0,0 +1,119 @@
|
||||||
|
use std::collections::HashMap;
|
||||||
|
|
||||||
|
use rust_lisp::model::{reference, reference::Reference, Env, FloatType, IntType, List, Value};
|
||||||
|
|
||||||
|
fn serde_lisp_value(value: &serde_json::Value) -> Value {
|
||||||
|
match value {
|
||||||
|
serde_json::Value::Null => Value::NIL,
|
||||||
|
serde_json::Value::Bool(b) => {
|
||||||
|
if *b {
|
||||||
|
Value::True
|
||||||
|
} else {
|
||||||
|
Value::False
|
||||||
|
}
|
||||||
|
}
|
||||||
|
serde_json::Value::Number(n) => {
|
||||||
|
if n.is_i64() {
|
||||||
|
Value::Int(n.as_i64().unwrap() as IntType)
|
||||||
|
} else if n.is_u64() {
|
||||||
|
Value::Int(n.as_u64().unwrap() as IntType)
|
||||||
|
} else if n.is_f64() {
|
||||||
|
Value::Float(n.as_f64().unwrap() as FloatType)
|
||||||
|
} else {
|
||||||
|
panic!("should never happen");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
serde_json::Value::String(s) => Value::String(s.clone()),
|
||||||
|
serde_json::Value::Array(a) => {
|
||||||
|
let mut l = List::NIL;
|
||||||
|
for li in a.into_iter().rev() {
|
||||||
|
l = l.cons(serde_lisp_value(li));
|
||||||
|
}
|
||||||
|
Value::List(l)
|
||||||
|
}
|
||||||
|
serde_json::Value::Object(o) => {
|
||||||
|
let mut r = HashMap::new();
|
||||||
|
for (k, v) in o.into_iter() {
|
||||||
|
let k_ = Value::String(k.clone());
|
||||||
|
let v_ = serde_lisp_value(v);
|
||||||
|
r.insert(k_, v_);
|
||||||
|
}
|
||||||
|
Value::HashMap(reference::new(r))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn env(value: &serde_json::Value) -> Env {
|
||||||
|
let mut environment = rust_lisp::default_env();
|
||||||
|
environment.define(
|
||||||
|
rust_lisp::model::Symbol::from("__input__"),
|
||||||
|
serde_lisp_value(value),
|
||||||
|
);
|
||||||
|
environment.define(
|
||||||
|
rust_lisp::model::Symbol::from("load"),
|
||||||
|
rust_lisp::model::Value::NativeClosure(reference::new(
|
||||||
|
move |e: Reference<rust_lisp::model::Env>, args: Vec<rust_lisp::model::Value>| {
|
||||||
|
let path: &String =
|
||||||
|
rust_lisp::utils::require_typed_arg::<&String>("load", &args, 0)?;
|
||||||
|
let path = (*path).as_str().split('.');
|
||||||
|
let mut i: rust_lisp::model::Value = reference::borrow(&e)
|
||||||
|
.get(&rust_lisp::model::Symbol::from("__input__"))
|
||||||
|
.unwrap();
|
||||||
|
for p in path
|
||||||
|
.into_iter()
|
||||||
|
.filter(|x| !(*x).eq(""))
|
||||||
|
.map(|x| rust_lisp::model::Value::String(x.into()))
|
||||||
|
{
|
||||||
|
match i {
|
||||||
|
rust_lisp::model::Value::HashMap(x) => {
|
||||||
|
if let Some(_i) = reference::borrow(&x).get(&p) {
|
||||||
|
i = _i.clone();
|
||||||
|
} else {
|
||||||
|
return Err(rust_lisp::model::RuntimeError {
|
||||||
|
msg: format!(r#"No such key {:?}"#, p).into(),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_ => {
|
||||||
|
return Err(rust_lisp::model::RuntimeError {
|
||||||
|
msg: format!(r#"No such key {:?}"#, p).into(),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
Ok(i)
|
||||||
|
},
|
||||||
|
)),
|
||||||
|
);
|
||||||
|
environment.define(
|
||||||
|
rust_lisp::model::Symbol::from("has-key"),
|
||||||
|
rust_lisp::model::Value::NativeClosure(reference::new(
|
||||||
|
move |e: Reference<rust_lisp::model::Env>, args: Vec<rust_lisp::model::Value>| {
|
||||||
|
let path: &String =
|
||||||
|
rust_lisp::utils::require_typed_arg::<&String>("has-key", &args, 0)?;
|
||||||
|
let path = (*path).as_str().split('.');
|
||||||
|
let mut i: rust_lisp::model::Value = reference::borrow(&e)
|
||||||
|
.get(&rust_lisp::model::Symbol::from("__input__"))
|
||||||
|
.unwrap();
|
||||||
|
for p in path
|
||||||
|
.into_iter()
|
||||||
|
.filter(|x| !(*x).eq(""))
|
||||||
|
.map(|x| rust_lisp::model::Value::String(x.into()))
|
||||||
|
{
|
||||||
|
match i {
|
||||||
|
rust_lisp::model::Value::HashMap(x) => {
|
||||||
|
if let Some(_i) = reference::borrow(&x).get(&p) {
|
||||||
|
i = _i.clone();
|
||||||
|
} else {
|
||||||
|
return Ok(rust_lisp::model::Value::False);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_ => return Ok(rust_lisp::model::Value::False),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
Ok(rust_lisp::model::Value::True)
|
||||||
|
},
|
||||||
|
)),
|
||||||
|
);
|
||||||
|
environment
|
||||||
|
}
|
213
src/main.rs
Normal file
213
src/main.rs
Normal file
|
@ -0,0 +1,213 @@
|
||||||
|
use std::collections::HashMap;
|
||||||
|
use std::path::PathBuf;
|
||||||
|
use std::str::FromStr;
|
||||||
|
|
||||||
|
use anyhow::Result;
|
||||||
|
use clap::Parser;
|
||||||
|
use log::{debug, info, warn};
|
||||||
|
use tokio::io::AsyncReadExt;
|
||||||
|
use tokio::time::{timeout, Duration};
|
||||||
|
|
||||||
|
mod config;
|
||||||
|
mod i3ipc;
|
||||||
|
mod lisp;
|
||||||
|
|
||||||
|
use config::{Config, ProgramEntry};
|
||||||
|
use i3ipc::{Connection, MessageType};
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Parser)]
|
||||||
|
#[command(author, version, about, long_about = None)]
|
||||||
|
struct Args {
|
||||||
|
#[arg(short, long, value_name = "FILE")]
|
||||||
|
config: Option<PathBuf>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Args {
|
||||||
|
fn finish(&mut self) {
|
||||||
|
// TODO maybe return separate type
|
||||||
|
if self.config.is_none() {
|
||||||
|
self.config = Some(
|
||||||
|
xdg::BaseDirectories::with_prefix("i3toolwait")
|
||||||
|
.unwrap()
|
||||||
|
.get_config_file("config.yaml"),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn new_window_cb(
|
||||||
|
_b: MessageType,
|
||||||
|
c: serde_json::Value,
|
||||||
|
config: &Config,
|
||||||
|
_args: &Args,
|
||||||
|
programs: &std::sync::Arc<tokio::sync::Mutex<Vec<ProgramEntry>>>,
|
||||||
|
tx: &tokio::sync::broadcast::Sender<()>,
|
||||||
|
) -> futures::future::BoxFuture<'static, Vec<(MessageType, Vec<u8>)>> {
|
||||||
|
let config_ = config.clone();
|
||||||
|
let tx_ = tx.clone();
|
||||||
|
let programs_ = programs.clone();
|
||||||
|
Box::pin(async move {
|
||||||
|
let mut command = None;
|
||||||
|
let mut index = None;
|
||||||
|
debug!("Received window event: {}", &c);
|
||||||
|
for (i, p) in programs_.lock().await.iter().enumerate() {
|
||||||
|
match p {
|
||||||
|
ProgramEntry::Program(p) => {
|
||||||
|
debug!("Evaluating program: {}", &p.match_);
|
||||||
|
let e = lisp::env(&c);
|
||||||
|
let init: Vec<rust_lisp::model::Value> = config_.init.clone().into();
|
||||||
|
let prog: Vec<rust_lisp::model::Value> = p.match_.clone().into();
|
||||||
|
let m = init.into_iter().chain(prog.into_iter());
|
||||||
|
let result =
|
||||||
|
rust_lisp::interpreter::eval_block(rust_lisp::model::reference::new(e), m);
|
||||||
|
if let Ok(v) = &result {
|
||||||
|
debug!("Received result: {}", v);
|
||||||
|
if *v == rust_lisp::model::Value::False {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
debug!("Match found");
|
||||||
|
let mut vars = HashMap::with_capacity(1);
|
||||||
|
vars.insert("result".to_string(), v.to_string());
|
||||||
|
let cmd = strfmt::strfmt(&p.cmd, &vars).unwrap();
|
||||||
|
debug!("Command: {}", &cmd);
|
||||||
|
|
||||||
|
index = Some(i);
|
||||||
|
command = Some(cmd);
|
||||||
|
break;
|
||||||
|
} else {
|
||||||
|
warn!("Program produced an error: {:?}", &result);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_ => {
|
||||||
|
// Ignore signal entries
|
||||||
|
()
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
if let Some(index) = index {
|
||||||
|
let mut plock = programs_.lock().await;
|
||||||
|
plock.remove(index);
|
||||||
|
if plock.len() == 0 {
|
||||||
|
tx_.send(()).unwrap();
|
||||||
|
}
|
||||||
|
return vec![(MessageType::Command, command.unwrap().into_bytes())];
|
||||||
|
}
|
||||||
|
debug!("No match found");
|
||||||
|
Vec::new()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn run_command<'a>(
|
||||||
|
connection: &mut Connection<'a>,
|
||||||
|
command: &str,
|
||||||
|
) -> Result<(), anyhow::Error> {
|
||||||
|
let (_, responses) = connection
|
||||||
|
.communicate(&MessageType::Command, command.as_bytes())
|
||||||
|
.await?;
|
||||||
|
match responses {
|
||||||
|
serde_json::Value::Array(responses) => {
|
||||||
|
for response in responses {
|
||||||
|
if let serde_json::Value::Bool(v) = response.get("success").unwrap() {
|
||||||
|
if !v {
|
||||||
|
warn!("Failed to run command {}: {}", command, response);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_ => panic!("invalid response"),
|
||||||
|
};
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn run<'a>(connection: &mut Connection<'a>, config: &Config) -> Result<(), anyhow::Error> {
|
||||||
|
let (_, resp) = connection.communicate(&MessageType::Version, b"").await?;
|
||||||
|
info!("i3 version is {}", resp.get("human_readable").unwrap());
|
||||||
|
|
||||||
|
let mut signal_stream =
|
||||||
|
tokio::signal::unix::signal(tokio::signal::unix::SignalKind::user_defined1())?;
|
||||||
|
|
||||||
|
for p in config.programs.iter() {
|
||||||
|
match p {
|
||||||
|
ProgramEntry::Program(p) => {
|
||||||
|
if let Some(r) = &p.run {
|
||||||
|
run_command(connection, r).await?;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ProgramEntry::Signal(p) => {
|
||||||
|
if let Some(r) = &p.run {
|
||||||
|
run_command(connection, r).await?;
|
||||||
|
}
|
||||||
|
if let Err(_) =
|
||||||
|
timeout(Duration::from_millis(p.timeout), signal_stream.recv()).await
|
||||||
|
{
|
||||||
|
warn!(
|
||||||
|
"Ran into timeout when waiting for signal, program: {:?}",
|
||||||
|
p.run
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::main]
|
||||||
|
async fn main() -> Result<()> {
|
||||||
|
env_logger::init_from_env(
|
||||||
|
env_logger::Env::new()
|
||||||
|
.filter("I3TOOLWAIT_LOG")
|
||||||
|
.write_style("I3TOOLWAIT_LOG_STYLE"),
|
||||||
|
);
|
||||||
|
|
||||||
|
let mut args = Args::parse();
|
||||||
|
args.finish();
|
||||||
|
let args = std::sync::Arc::new(args);
|
||||||
|
let mut config = String::new();
|
||||||
|
if args.config.as_ref().unwrap() == &PathBuf::from_str("-").unwrap() {
|
||||||
|
tokio::io::stdin().read_to_string(&mut config).await?;
|
||||||
|
} else {
|
||||||
|
tokio::fs::File::open(args.config.as_ref().unwrap())
|
||||||
|
.await?
|
||||||
|
.read_to_string(&mut config)
|
||||||
|
.await?;
|
||||||
|
}
|
||||||
|
let config: Config = serde_yaml::from_str(&config)?;
|
||||||
|
let config = std::sync::Arc::new(config);
|
||||||
|
let programs = std::sync::Arc::new(tokio::sync::Mutex::new(config.programs.clone()));
|
||||||
|
|
||||||
|
let mut connection = Connection::connect((i3ipc::get_socket_path().await?).as_ref())?;
|
||||||
|
let mut sub_connection = connection.clone();
|
||||||
|
let cb_config = config.clone();
|
||||||
|
let cb_args = args.clone();
|
||||||
|
|
||||||
|
let (tx, mut rx) = tokio::sync::broadcast::channel::<()>(1);
|
||||||
|
|
||||||
|
let cb_programs = programs.clone();
|
||||||
|
let cb = move |a, b| new_window_cb(a, b, &cb_config, &cb_args, &cb_programs, &tx);
|
||||||
|
sub_connection
|
||||||
|
.subscribe(&[MessageType::SubWindow], &cb)
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
tokio::join!(
|
||||||
|
timeout(
|
||||||
|
Duration::from_millis(config.timeout),
|
||||||
|
sub_connection.run(&mut rx)
|
||||||
|
),
|
||||||
|
run(&mut connection, &config),
|
||||||
|
)
|
||||||
|
.1?;
|
||||||
|
{
|
||||||
|
let p = programs.lock().await;
|
||||||
|
if p.len() != 0 {
|
||||||
|
warn!("Not all programs consumed: {:?}", &p);
|
||||||
|
info!("Maybe the timouts are too short?");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(cmd) = &config.cmd {
|
||||||
|
connection
|
||||||
|
.communicate(&MessageType::Command, cmd.as_bytes())
|
||||||
|
.await?;
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
Loading…
Reference in a new issue