Rework
This commit is contained in:
217
Cargo.lock
generated
217
Cargo.lock
generated
@@ -4,9 +4,9 @@ version = 3
|
||||
|
||||
[[package]]
|
||||
name = "anstream"
|
||||
version = "0.6.14"
|
||||
version = "0.6.18"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "418c75fa768af9c03be99d17643f93f79bbba589895012a80e3452a19ddda15b"
|
||||
checksum = "8acc5369981196006228e28809f761875c0327210a891e941f4c683b3a99529b"
|
||||
dependencies = [
|
||||
"anstyle",
|
||||
"anstyle-parse",
|
||||
@@ -19,33 +19,33 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "anstyle"
|
||||
version = "1.0.7"
|
||||
version = "1.0.10"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "038dfcf04a5feb68e9c60b21c9625a54c2c0616e79b72b0fd87075a056ae1d1b"
|
||||
checksum = "55cc3b69f167a1ef2e161439aa98aed94e6028e5f9a59be9a6ffb47aef1651f9"
|
||||
|
||||
[[package]]
|
||||
name = "anstyle-parse"
|
||||
version = "0.2.4"
|
||||
version = "0.2.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c03a11a9034d92058ceb6ee011ce58af4a9bf61491aa7e1e59ecd24bd40d22d4"
|
||||
checksum = "3b2d16507662817a6a20a9ea92df6652ee4f94f914589377d69f3b21bc5798a9"
|
||||
dependencies = [
|
||||
"utf8parse",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "anstyle-query"
|
||||
version = "1.0.3"
|
||||
version = "1.1.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a64c907d4e79225ac72e2a354c9ce84d50ebb4586dee56c82b3ee73004f537f5"
|
||||
checksum = "79947af37f4177cfead1110013d678905c37501914fba0efea834c3fe9a8d60c"
|
||||
dependencies = [
|
||||
"windows-sys",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "anstyle-wincon"
|
||||
version = "3.0.3"
|
||||
version = "3.0.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "61a38449feb7068f52bb06c12759005cf459ee52bb4adc1d5a7c4322d716fb19"
|
||||
checksum = "2109dbce0e72be3ec00bed26e6a7479ca384ad226efdd66db8fa2e3a38c83125"
|
||||
dependencies = [
|
||||
"anstyle",
|
||||
"windows-sys",
|
||||
@@ -53,9 +53,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "clap"
|
||||
version = "4.5.4"
|
||||
version = "4.5.20"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "90bc066a67923782aa8515dbaea16946c5bcc5addbd668bb80af688e53e548a0"
|
||||
checksum = "b97f376d85a664d5837dbae44bf546e6477a679ff6610010f17276f686d867e8"
|
||||
dependencies = [
|
||||
"clap_builder",
|
||||
"clap_derive",
|
||||
@@ -63,9 +63,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "clap_builder"
|
||||
version = "4.5.2"
|
||||
version = "4.5.20"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ae129e2e766ae0ec03484e609954119f123cc1fe650337e155d03b022f24f7b4"
|
||||
checksum = "19bc80abd44e4bed93ca373a0704ccbd1b710dc5749406201bb018272808dc54"
|
||||
dependencies = [
|
||||
"anstream",
|
||||
"anstyle",
|
||||
@@ -75,9 +75,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "clap_derive"
|
||||
version = "4.5.4"
|
||||
version = "4.5.18"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "528131438037fd55894f62d6e9f068b8f45ac57ffa77517819645d10aed04f64"
|
||||
checksum = "4ac6a0c7b1a9e9a5186361f67dfa1b88213572f427fb9ab038efb2bd8c582dab"
|
||||
dependencies = [
|
||||
"heck",
|
||||
"proc-macro2",
|
||||
@@ -87,15 +87,27 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "clap_lex"
|
||||
version = "0.7.0"
|
||||
version = "0.7.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "98cc8fbded0c607b7ba9dd60cd98df59af97e84d24e49c8557331cfc26d301ce"
|
||||
checksum = "1462739cb27611015575c0c11df5df7601141071f07518d56fcc1be504cbec97"
|
||||
|
||||
[[package]]
|
||||
name = "colorchoice"
|
||||
version = "1.0.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5b63caa9aa9397e2d9480a9b13673856c78d8ac123288526c37d7839f2a86990"
|
||||
|
||||
[[package]]
|
||||
name = "equivalent"
|
||||
version = "1.0.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0b6a852b24ab71dffc585bcb46eaf7959d175cb865a7152e35b348d1b2960422"
|
||||
checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5"
|
||||
|
||||
[[package]]
|
||||
name = "hashbrown"
|
||||
version = "0.15.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3a9bfc1af68b1726ea47d3d5109de126281def866b33970e10fbab11b5dafab3"
|
||||
|
||||
[[package]]
|
||||
name = "heck"
|
||||
@@ -104,10 +116,28 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea"
|
||||
|
||||
[[package]]
|
||||
name = "is_terminal_polyfill"
|
||||
version = "1.70.0"
|
||||
name = "i3"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"serde",
|
||||
"serde_json",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "indexmap"
|
||||
version = "2.6.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f8478577c03552c21db0e2724ffb8986a5ce7af88107e6be5d2ee6e158c12800"
|
||||
checksum = "707907fe3c25f5424cce2cb7e1cbcafee6bdbe735ca90ef77c29e84591e5b9da"
|
||||
dependencies = [
|
||||
"equivalent",
|
||||
"hashbrown",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "is_terminal_polyfill"
|
||||
version = "1.70.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7943c866cc5cd64cbc25b2e01621d07fa8eb2a1a23160ee81ce38704e97b8ecf"
|
||||
|
||||
[[package]]
|
||||
name = "itoa"
|
||||
@@ -116,19 +146,25 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "49f1f14873335454500d59611f1cf4a4b0f786f9ac11f4312a78e4cf2566695b"
|
||||
|
||||
[[package]]
|
||||
name = "proc-macro2"
|
||||
version = "1.0.82"
|
||||
name = "memchr"
|
||||
version = "2.7.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8ad3d49ab951a01fbaafe34f2ec74122942fe18a3f9814c3268f1bb72042131b"
|
||||
checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3"
|
||||
|
||||
[[package]]
|
||||
name = "proc-macro2"
|
||||
version = "1.0.89"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f139b0662de085916d1fb67d2b4169d1addddda1919e696f3252b740b629986e"
|
||||
dependencies = [
|
||||
"unicode-ident",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "quote"
|
||||
version = "1.0.36"
|
||||
version = "1.0.37"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0fa76aaf39101c457836aec0ce2316dbdc3ab723cdda1c6bd4e6ad4208acaca7"
|
||||
checksum = "b5b9d34b8991d19d98081b46eacdd8eb58c6f2b201139f7c5f643cc155a633af"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
]
|
||||
@@ -144,24 +180,26 @@ name = "screencfg"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"clap",
|
||||
"i3",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"toml",
|
||||
"xrandr",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "serde"
|
||||
version = "1.0.200"
|
||||
version = "1.0.214"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ddc6f9cc94d67c0e21aaf7eda3a010fd3af78ebf6e096aa6e2e13c79749cce4f"
|
||||
checksum = "f55c3193aca71c12ad7890f1785d2b73e1b9f63a0bbc353c08ef26fe03fc56b5"
|
||||
dependencies = [
|
||||
"serde_derive",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "serde_derive"
|
||||
version = "1.0.200"
|
||||
version = "1.0.214"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "856f046b9400cee3c8c94ed572ecdb752444c24528c035cd35882aad6f492bcb"
|
||||
checksum = "de523f781f095e28fa605cdce0f8307e451cc0fd14e2eb4cd2e98a355b147766"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
@@ -170,15 +208,25 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "serde_json"
|
||||
version = "1.0.116"
|
||||
version = "1.0.132"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3e17db7126d17feb94eb3fad46bf1a96b034e8aacbc2e775fe81505f8b0b2813"
|
||||
checksum = "d726bfaff4b320266d395898905d0eba0345aae23b54aee3a737e260fd46db03"
|
||||
dependencies = [
|
||||
"itoa",
|
||||
"memchr",
|
||||
"ryu",
|
||||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "serde_spanned"
|
||||
version = "0.6.8"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "87607cb1398ed59d48732e575a4c28a7a8ebf2454b964fe3f224f2afc07909e1"
|
||||
dependencies = [
|
||||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "strsim"
|
||||
version = "0.11.1"
|
||||
@@ -187,9 +235,9 @@ checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f"
|
||||
|
||||
[[package]]
|
||||
name = "syn"
|
||||
version = "2.0.61"
|
||||
version = "2.0.87"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c993ed8ccba56ae856363b1845da7266a7cb78e1d146c8a32d54b45a8b831fc9"
|
||||
checksum = "25aa4ce346d03a6dcd68dd8b4010bcb74e54e62c90c573f394c46eae99aba32d"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
@@ -197,31 +245,65 @@ dependencies = [
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "unicode-ident"
|
||||
version = "1.0.12"
|
||||
name = "toml"
|
||||
version = "0.8.19"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b"
|
||||
checksum = "a1ed1f98e3fdc28d6d910e6737ae6ab1a93bf1985935a1193e68f93eeb68d24e"
|
||||
dependencies = [
|
||||
"serde",
|
||||
"serde_spanned",
|
||||
"toml_datetime",
|
||||
"toml_edit",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "toml_datetime"
|
||||
version = "0.6.8"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0dd7358ecb8fc2f8d014bf86f6f638ce72ba252a2c3a2572f2a795f1d23efb41"
|
||||
dependencies = [
|
||||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "toml_edit"
|
||||
version = "0.22.22"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4ae48d6208a266e853d946088ed816055e556cc6028c5e8e2b84d9fa5dd7c7f5"
|
||||
dependencies = [
|
||||
"indexmap",
|
||||
"serde",
|
||||
"serde_spanned",
|
||||
"toml_datetime",
|
||||
"winnow",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "unicode-ident"
|
||||
version = "1.0.13"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e91b56cd4cadaeb79bbf1a5645f6b4f8dc5bde8834ad5894a8db35fda9efa1fe"
|
||||
|
||||
[[package]]
|
||||
name = "utf8parse"
|
||||
version = "0.2.1"
|
||||
version = "0.2.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "711b9620af191e0cdc7468a8d14e709c3dcdb115b36f838e601583af800a370a"
|
||||
checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821"
|
||||
|
||||
[[package]]
|
||||
name = "windows-sys"
|
||||
version = "0.52.0"
|
||||
version = "0.59.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d"
|
||||
checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b"
|
||||
dependencies = [
|
||||
"windows-targets",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows-targets"
|
||||
version = "0.52.5"
|
||||
version = "0.52.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6f0713a46559409d202e70e28227288446bf7841d3211583a4b53e3f6d96e7eb"
|
||||
checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973"
|
||||
dependencies = [
|
||||
"windows_aarch64_gnullvm",
|
||||
"windows_aarch64_msvc",
|
||||
@@ -235,48 +317,61 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "windows_aarch64_gnullvm"
|
||||
version = "0.52.5"
|
||||
version = "0.52.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7088eed71e8b8dda258ecc8bac5fb1153c5cffaf2578fc8ff5d61e23578d3263"
|
||||
checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3"
|
||||
|
||||
[[package]]
|
||||
name = "windows_aarch64_msvc"
|
||||
version = "0.52.5"
|
||||
version = "0.52.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9985fd1504e250c615ca5f281c3f7a6da76213ebd5ccc9561496568a2752afb6"
|
||||
checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469"
|
||||
|
||||
[[package]]
|
||||
name = "windows_i686_gnu"
|
||||
version = "0.52.5"
|
||||
version = "0.52.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "88ba073cf16d5372720ec942a8ccbf61626074c6d4dd2e745299726ce8b89670"
|
||||
checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b"
|
||||
|
||||
[[package]]
|
||||
name = "windows_i686_gnullvm"
|
||||
version = "0.52.5"
|
||||
version = "0.52.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "87f4261229030a858f36b459e748ae97545d6f1ec60e5e0d6a3d32e0dc232ee9"
|
||||
checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66"
|
||||
|
||||
[[package]]
|
||||
name = "windows_i686_msvc"
|
||||
version = "0.52.5"
|
||||
version = "0.52.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "db3c2bf3d13d5b658be73463284eaf12830ac9a26a90c717b7f771dfe97487bf"
|
||||
checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66"
|
||||
|
||||
[[package]]
|
||||
name = "windows_x86_64_gnu"
|
||||
version = "0.52.5"
|
||||
version = "0.52.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4e4246f76bdeff09eb48875a0fd3e2af6aada79d409d33011886d3e1581517d9"
|
||||
checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78"
|
||||
|
||||
[[package]]
|
||||
name = "windows_x86_64_gnullvm"
|
||||
version = "0.52.5"
|
||||
version = "0.52.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "852298e482cd67c356ddd9570386e2862b5673c85bd5f88df9ab6802b334c596"
|
||||
checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d"
|
||||
|
||||
[[package]]
|
||||
name = "windows_x86_64_msvc"
|
||||
version = "0.52.5"
|
||||
version = "0.52.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "bec47e5bfd1bff0eeaf6d8b485cc1074891a197ab4225d504cb7a1ab88b02bf0"
|
||||
checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec"
|
||||
|
||||
[[package]]
|
||||
name = "winnow"
|
||||
version = "0.6.20"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "36c1fec1a2bb5866f07c25f68c26e565c4c200aebb96d7e55710c19d3e8ac49b"
|
||||
dependencies = [
|
||||
"memchr",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "xrandr"
|
||||
version = "0.1.0"
|
||||
|
||||
218
Cargo.toml
218
Cargo.toml
@@ -1,14 +1,222 @@
|
||||
[package]
|
||||
name = "screencfg"
|
||||
description = "Automatically configure your screen setup"
|
||||
description = "Automatically configure your screen setup with i3"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
repository = "https://github.com/hakoerber/screencfg/"
|
||||
authors = ["Hannes Körber <hannes@hkoerber.de>"]
|
||||
rust-version = "1.74.1"
|
||||
readme = "README.md"
|
||||
license-file = "LICENSE"
|
||||
keywords = ["i3", "xrandr"]
|
||||
categories = ["command-line-utilities"]
|
||||
|
||||
[dependencies]
|
||||
clap = { version = "4.5.4", default-features = false, features = ["std", "derive"] }
|
||||
serde = { version = "1.0.200", features = ["derive"] }
|
||||
serde_json = "1.0.116"
|
||||
clap = { version = "4.*", default-features = false, features = [
|
||||
"std",
|
||||
"derive",
|
||||
] }
|
||||
toml = { version = "0.8.*", default-features = false, features = ["parse"] }
|
||||
i3 = { path = "./i3" }
|
||||
xrandr = { path = "./xrandr" }
|
||||
serde = { version = "1.0.214", default-features = false, features = ["derive"] }
|
||||
|
||||
[features]
|
||||
default = ["full"]
|
||||
full = ["clap/help", "clap/color", "clap/suggestions", "clap/usage", "clap/error-context"]
|
||||
full = [
|
||||
"clap/help",
|
||||
"clap/color",
|
||||
"clap/suggestions",
|
||||
"clap/usage",
|
||||
"clap/error-context",
|
||||
]
|
||||
|
||||
[workspace]
|
||||
resolver = "2"
|
||||
members = ["i3", "xrandr"]
|
||||
|
||||
[profile.release]
|
||||
opt-level = 3
|
||||
debug = false
|
||||
strip = "symbols"
|
||||
debug-assertions = false
|
||||
overflow-checks = false
|
||||
lto = "fat"
|
||||
panic = "abort"
|
||||
codegen-units = 1
|
||||
rpath = false
|
||||
|
||||
[workspace.lints.rust]
|
||||
absolute_paths_not_starting_with_crate = "deny"
|
||||
elided_lifetimes_in_paths = "deny"
|
||||
explicit_outlives_requirements = "deny"
|
||||
keyword_idents = "deny"
|
||||
let_underscore_drop = "deny"
|
||||
non_ascii_idents = "deny"
|
||||
non_local_definitions = "deny"
|
||||
single_use_lifetimes = "deny"
|
||||
unit_bindings = "deny"
|
||||
unreachable_pub = "deny"
|
||||
unsafe_code = { level = "forbid", priority = -1 }
|
||||
unsafe_op_in_unsafe_fn = "deny"
|
||||
unstable_features = { level = "forbid", priority = -1 }
|
||||
unused_crate_dependencies = "deny"
|
||||
unused_import_braces = "deny"
|
||||
unused_lifetimes = "deny"
|
||||
unused_macro_rules = "deny"
|
||||
unused_qualifications = "deny"
|
||||
unused_results = "deny"
|
||||
variant_size_differences = "deny"
|
||||
|
||||
[workspace.lints.clippy]
|
||||
# enabled groups
|
||||
correctness = { level = "deny", priority = -1 }
|
||||
suspicious = { level = "warn", priority = -1 }
|
||||
style = { level = "warn", priority = -1 }
|
||||
complexity = { level = "warn", priority = -1 }
|
||||
perf = { level = "warn", priority = -1 }
|
||||
cargo = { level = "warn", priority = -1 }
|
||||
pedantic = { level = "warn", priority = -1 }
|
||||
nursery = { level = "warn", priority = -1 }
|
||||
|
||||
# pedantic overrides
|
||||
too_many_lines = "allow"
|
||||
must_use_candidate = "allow"
|
||||
map_unwrap_or = "allow"
|
||||
missing_errors_doc = "allow"
|
||||
|
||||
# nursery overrides
|
||||
missing_const_for_fn = "allow"
|
||||
option_if_let_else = "allow"
|
||||
|
||||
# complexity overrides
|
||||
too_many_arguments = "allow"
|
||||
|
||||
# style overrides
|
||||
new_without_default = "allow"
|
||||
|
||||
# cargo overrides
|
||||
multiple_crate_versions = "allow"
|
||||
cargo_common_metadata = "allow"
|
||||
|
||||
# selected restrictions
|
||||
allow_attributes = "warn"
|
||||
allow_attributes_without_reason = "warn"
|
||||
arithmetic_side_effects = "warn"
|
||||
as_conversions = "warn"
|
||||
assertions_on_result_states = "warn"
|
||||
cfg_not_test = "warn"
|
||||
clone_on_ref_ptr = "warn"
|
||||
create_dir = "warn"
|
||||
dbg_macro = "warn"
|
||||
decimal_literal_representation = "warn"
|
||||
default_numeric_fallback = "warn"
|
||||
deref_by_slicing = "warn"
|
||||
disallowed_script_idents = "warn"
|
||||
else_if_without_else = "warn"
|
||||
empty_drop = "warn"
|
||||
empty_enum_variants_with_brackets = "warn"
|
||||
empty_structs_with_brackets = "warn"
|
||||
exit = "warn"
|
||||
filetype_is_file = "warn"
|
||||
float_arithmetic = "warn"
|
||||
float_cmp_const = "warn"
|
||||
fn_to_numeric_cast_any = "warn"
|
||||
format_push_string = "warn"
|
||||
get_unwrap = "warn"
|
||||
indexing_slicing = "warn"
|
||||
infinite_loop = "warn"
|
||||
inline_asm_x86_att_syntax = "warn"
|
||||
inline_asm_x86_intel_syntax = "warn"
|
||||
integer_division = "warn"
|
||||
iter_over_hash_type = "warn"
|
||||
large_include_file = "warn"
|
||||
let_underscore_must_use = "warn"
|
||||
let_underscore_untyped = "warn"
|
||||
little_endian_bytes = "warn"
|
||||
lossy_float_literal = "warn"
|
||||
map_err_ignore = "warn"
|
||||
mem_forget = "warn"
|
||||
missing_assert_message = "warn"
|
||||
missing_asserts_for_indexing = "warn"
|
||||
mixed_read_write_in_expression = "warn"
|
||||
modulo_arithmetic = "warn"
|
||||
multiple_inherent_impl = "warn"
|
||||
multiple_unsafe_ops_per_block = "warn"
|
||||
mutex_atomic = "warn"
|
||||
panic = "warn"
|
||||
partial_pub_fields = "warn"
|
||||
pattern_type_mismatch = "warn"
|
||||
print_stderr = "warn"
|
||||
print_stdout = "warn"
|
||||
pub_without_shorthand = "warn"
|
||||
rc_buffer = "warn"
|
||||
rc_mutex = "warn"
|
||||
redundant_type_annotations = "warn"
|
||||
renamed_function_params = "warn"
|
||||
rest_pat_in_fully_bound_structs = "warn"
|
||||
same_name_method = "warn"
|
||||
self_named_module_files = "warn"
|
||||
semicolon_outside_block = "warn"
|
||||
str_to_string = "warn"
|
||||
string_add = "warn"
|
||||
string_lit_chars_any = "warn"
|
||||
string_slice = "warn"
|
||||
string_to_string = "warn"
|
||||
suspicious_xor_used_as_pow = "warn"
|
||||
tests_outside_test_module = "warn"
|
||||
todo = "warn"
|
||||
try_err = "warn"
|
||||
undocumented_unsafe_blocks = "warn"
|
||||
unimplemented = "warn"
|
||||
unnecessary_safety_comment = "warn"
|
||||
unnecessary_safety_doc = "warn"
|
||||
unnecessary_self_imports = "warn"
|
||||
unneeded_field_pattern = "warn"
|
||||
unseparated_literal_suffix = "warn"
|
||||
unused_result_ok = "warn"
|
||||
unwrap_used = "warn"
|
||||
use_debug = "warn"
|
||||
verbose_file_reads = "warn"
|
||||
|
||||
# restrictions explicit allows
|
||||
absolute_paths = "allow"
|
||||
alloc_instead_of_core = "allow"
|
||||
as_underscore = "allow"
|
||||
big_endian_bytes = "allow"
|
||||
default_union_representation = "allow"
|
||||
error_impl_error = "allow"
|
||||
exhaustive_enums = "allow"
|
||||
exhaustive_structs = "allow"
|
||||
expect_used = "allow"
|
||||
field_scoped_visibility_modifiers = "allow"
|
||||
host_endian_bytes = "allow"
|
||||
if_then_some_else_none = "allow"
|
||||
impl_trait_in_params = "allow"
|
||||
implicit_return = "allow"
|
||||
integer_division_remainder_used = "allow"
|
||||
min_ident_chars = "allow"
|
||||
missing_docs_in_private_items = "allow"
|
||||
missing_inline_in_public_items = "allow"
|
||||
missing_trait_methods = "allow"
|
||||
mod_module_files = "allow"
|
||||
needless_raw_strings = "allow"
|
||||
non_ascii_literal = "allow"
|
||||
panic_in_result_fn = "allow"
|
||||
pathbuf_init_then_push = "allow"
|
||||
pub_use = "allow"
|
||||
pub_with_shorthand = "allow"
|
||||
question_mark_used = "allow"
|
||||
ref_patterns = "allow"
|
||||
semicolon_inside_block = "allow"
|
||||
separated_literal_suffix = "allow"
|
||||
shadow_reuse = "allow"
|
||||
shadow_same = "allow"
|
||||
shadow_unrelated = "allow"
|
||||
single_call_fn = "allow"
|
||||
single_char_lifetime_names = "allow"
|
||||
std_instead_of_alloc = "allow"
|
||||
std_instead_of_core = "allow"
|
||||
unreachable = "allow"
|
||||
unwrap_in_result = "allow"
|
||||
wildcard_enum_match_arm = "allow"
|
||||
|
||||
25
Makefile
Normal file
25
Makefile
Normal file
@@ -0,0 +1,25 @@
|
||||
.PHONY: check
|
||||
check: | fmt lint test
|
||||
|
||||
.PHONY: docs
|
||||
docs:
|
||||
cargo watch -- cargo doc
|
||||
|
||||
.PHONY: test
|
||||
test:
|
||||
cargo hack --feature-powerset --no-dev-deps check
|
||||
cargo test --workspace --color=always
|
||||
|
||||
.PHONY: lint
|
||||
lint:
|
||||
cargo clippy --workspace --tests --color=always
|
||||
|
||||
.PHONY: fmt
|
||||
fmt:
|
||||
cargo fmt
|
||||
find -name '*.md' | xargs prettier --print-width 80 --prose-wrap always --write
|
||||
find -name '*.toml' | xargs taplo format
|
||||
|
||||
.PHONY: build-static
|
||||
build-static:
|
||||
cargo build --target x86_64-unknown-linux-musl --no-default-features --release --workspace
|
||||
11
i3/Cargo.toml
Normal file
11
i3/Cargo.toml
Normal file
@@ -0,0 +1,11 @@
|
||||
[package]
|
||||
name = "i3"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
|
||||
[dependencies]
|
||||
serde = { version = "1.*", default-features = false, features = ["derive"] }
|
||||
serde_json = { version = "1.*", default-features = false, features = ["std"] }
|
||||
|
||||
[lints]
|
||||
workspace = true
|
||||
67
i3/src/error.rs
Normal file
67
i3/src/error.rs
Normal file
@@ -0,0 +1,67 @@
|
||||
use std::{fmt, io};
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum Msg {
|
||||
Owned(String),
|
||||
Static(&'static str),
|
||||
}
|
||||
|
||||
impl From<&'static str> for Msg {
|
||||
fn from(value: &'static str) -> Self {
|
||||
Self::Static(value)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<String> for Msg {
|
||||
fn from(value: String) -> Self {
|
||||
Self::Owned(value)
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Display for Msg {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
write!(
|
||||
f,
|
||||
"{}",
|
||||
match *self {
|
||||
Self::Owned(ref s) => s.as_str(),
|
||||
Self::Static(s) => s,
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum Error {
|
||||
Connection(Msg),
|
||||
Command(Msg),
|
||||
Protocol(Msg),
|
||||
}
|
||||
|
||||
impl fmt::Display for Error {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
write!(
|
||||
f,
|
||||
"{}",
|
||||
match *self {
|
||||
Self::Connection(ref msg) => format!("connection failed: {msg}"),
|
||||
Self::Command(ref msg) => format!("command failed: {msg}"),
|
||||
Self::Protocol(ref msg) => format!("overflow: {msg}"),
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<io::Error> for Error {
|
||||
fn from(value: io::Error) -> Self {
|
||||
Self::Command(Msg::Owned(value.to_string()))
|
||||
}
|
||||
}
|
||||
|
||||
impl From<serde_json::Error> for Error {
|
||||
fn from(value: serde_json::Error) -> Self {
|
||||
Self::Connection(Msg::Owned(value.to_string()))
|
||||
}
|
||||
}
|
||||
|
||||
impl std::error::Error for Error {}
|
||||
619
i3/src/lib.rs
Normal file
619
i3/src/lib.rs
Normal file
@@ -0,0 +1,619 @@
|
||||
use std::{
|
||||
borrow::Cow,
|
||||
ffi::OsStr,
|
||||
fmt,
|
||||
io::{Read, Write},
|
||||
ops::{Deref, DerefMut, Index},
|
||||
os::unix::{ffi::OsStrExt as _, net},
|
||||
path::PathBuf,
|
||||
process,
|
||||
time::Duration,
|
||||
vec::IntoIter,
|
||||
};
|
||||
|
||||
mod error;
|
||||
pub use error::Error;
|
||||
|
||||
pub enum Command {
|
||||
Nop,
|
||||
MoveWorkspace { id: usize, output: String },
|
||||
}
|
||||
|
||||
impl From<&Command> for Cow<'static, str> {
|
||||
fn from(value: &Command) -> Self {
|
||||
match *value {
|
||||
Command::Nop => Cow::from("nop"),
|
||||
Command::MoveWorkspace { id, ref output } => Cow::from(format!(
|
||||
"[workspace=\"{id}\"] move workspace to output {output}"
|
||||
)),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub trait Conn {
|
||||
fn version(&mut self) -> Result<Version, Error>;
|
||||
fn outputs(&mut self) -> Result<Outputs, Error>;
|
||||
fn workspaces(&mut self) -> Result<Workspaces, Error>;
|
||||
fn command(&mut self, command: Command) -> Result<(), Error>;
|
||||
}
|
||||
|
||||
pub enum MockSetting {
|
||||
LaptopOnly,
|
||||
ExternalOnly(usize),
|
||||
Mixed,
|
||||
}
|
||||
|
||||
pub struct MockConnection {
|
||||
pub fail: bool,
|
||||
pub setting: MockSetting,
|
||||
}
|
||||
|
||||
impl MockConnection {
|
||||
fn check_fail(&self) -> Result<(), Error> {
|
||||
if self.fail {
|
||||
Err(Error::Connection("fail".into()))
|
||||
} else {
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Conn for MockConnection {
|
||||
fn version(&mut self) -> Result<Version, Error> {
|
||||
self.check_fail()?;
|
||||
Ok(Version {
|
||||
minor: 1,
|
||||
patch: 2,
|
||||
major: 3,
|
||||
})
|
||||
}
|
||||
|
||||
fn outputs(&mut self) -> Result<Outputs, Error> {
|
||||
self.check_fail()?;
|
||||
match self.setting {
|
||||
MockSetting::LaptopOnly => Ok(Outputs(vec![Output {
|
||||
name: "eDP-1".into(),
|
||||
active: true,
|
||||
primary: true,
|
||||
}])),
|
||||
MockSetting::ExternalOnly(num) => match num {
|
||||
1 => Ok(Outputs(vec![Output {
|
||||
name: "DP-1".into(),
|
||||
active: true,
|
||||
primary: false,
|
||||
}])),
|
||||
2 => Ok(Outputs(vec![
|
||||
Output {
|
||||
name: "DP-1".into(),
|
||||
active: true,
|
||||
primary: false,
|
||||
},
|
||||
Output {
|
||||
name: "DP-2".into(),
|
||||
active: false,
|
||||
primary: false,
|
||||
},
|
||||
])),
|
||||
#[expect(clippy::panic, reason = "just a mock")]
|
||||
_ => panic!(),
|
||||
},
|
||||
MockSetting::Mixed => Ok(Outputs(vec![
|
||||
Output {
|
||||
name: "eDP-1".into(),
|
||||
active: true,
|
||||
primary: true,
|
||||
},
|
||||
Output {
|
||||
name: "HDMI-1".into(),
|
||||
active: true,
|
||||
primary: false,
|
||||
},
|
||||
Output {
|
||||
name: "DP-1".into(),
|
||||
active: true,
|
||||
primary: false,
|
||||
},
|
||||
])),
|
||||
}
|
||||
}
|
||||
|
||||
fn workspaces(&mut self) -> Result<Workspaces, Error> {
|
||||
self.check_fail()?;
|
||||
match self.setting {
|
||||
MockSetting::LaptopOnly => Ok(Workspaces(vec![
|
||||
Workspace {
|
||||
num: 1,
|
||||
name: "num1".into(),
|
||||
output: "eDP-1".into(),
|
||||
},
|
||||
Workspace {
|
||||
num: 1,
|
||||
name: "num1".into(),
|
||||
output: "eDP-1".into(),
|
||||
},
|
||||
Workspace {
|
||||
num: 1,
|
||||
name: "num1".into(),
|
||||
output: "eDP-1".into(),
|
||||
},
|
||||
Workspace {
|
||||
num: 1,
|
||||
name: "num1".into(),
|
||||
output: "eDP-1".into(),
|
||||
},
|
||||
])),
|
||||
MockSetting::ExternalOnly(num) => match num {
|
||||
1 => Ok(Workspaces(vec![
|
||||
Workspace {
|
||||
num: 1,
|
||||
name: "num1".into(),
|
||||
output: "DP-1".into(),
|
||||
},
|
||||
Workspace {
|
||||
num: 1,
|
||||
name: "num1".into(),
|
||||
output: "DP-1".into(),
|
||||
},
|
||||
Workspace {
|
||||
num: 1,
|
||||
name: "num1".into(),
|
||||
output: "DP-1".into(),
|
||||
},
|
||||
Workspace {
|
||||
num: 1,
|
||||
name: "num1".into(),
|
||||
output: "DP-1".into(),
|
||||
},
|
||||
])),
|
||||
2 => Ok(Workspaces(vec![
|
||||
Workspace {
|
||||
num: 1,
|
||||
name: "num1".into(),
|
||||
output: "DP-1".into(),
|
||||
},
|
||||
Workspace {
|
||||
num: 1,
|
||||
name: "num1".into(),
|
||||
output: "DP-1".into(),
|
||||
},
|
||||
Workspace {
|
||||
num: 1,
|
||||
name: "num1".into(),
|
||||
output: "DP-2".into(),
|
||||
},
|
||||
Workspace {
|
||||
num: 1,
|
||||
name: "num1".into(),
|
||||
output: "DP-2".into(),
|
||||
},
|
||||
])),
|
||||
#[expect(clippy::panic, reason = "just a mock")]
|
||||
_ => panic!(),
|
||||
},
|
||||
MockSetting::Mixed => Ok(Workspaces(vec![
|
||||
Workspace {
|
||||
num: 1,
|
||||
name: "num1".into(),
|
||||
output: "eDP-1".into(),
|
||||
},
|
||||
Workspace {
|
||||
num: 1,
|
||||
name: "num1".into(),
|
||||
output: "eDP-1".into(),
|
||||
},
|
||||
Workspace {
|
||||
num: 1,
|
||||
name: "num1".into(),
|
||||
output: "DP-1".into(),
|
||||
},
|
||||
Workspace {
|
||||
num: 1,
|
||||
name: "num1".into(),
|
||||
output: "HDMI-1".into(),
|
||||
},
|
||||
])),
|
||||
}
|
||||
}
|
||||
|
||||
fn command(&mut self, _command: Command) -> Result<(), Error> {
|
||||
self.check_fail()?;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
pub struct Connection(net::UnixStream);
|
||||
|
||||
impl Conn for Connection {
|
||||
fn version(&mut self) -> Result<Version, Error> {
|
||||
Message::Version.send(self)?;
|
||||
|
||||
let response = Response::read(self)?;
|
||||
|
||||
match response {
|
||||
Response::Version(version) => Ok(version.into()),
|
||||
Response::Workspaces(_) | Response::Command(_) | Response::Outputs(_) => Err(
|
||||
Error::Connection("received invalid response from i3".into()),
|
||||
),
|
||||
}
|
||||
}
|
||||
|
||||
fn outputs(&mut self) -> Result<Outputs, Error> {
|
||||
Message::Outputs.send(self)?;
|
||||
let response = Response::read(self)?;
|
||||
|
||||
match response {
|
||||
Response::Outputs(outputs) => Ok(outputs.into()),
|
||||
Response::Version(_) | Response::Workspaces(_) | Response::Command(_) => Err(
|
||||
Error::Connection("received invalid response from i3".into()),
|
||||
),
|
||||
}
|
||||
}
|
||||
|
||||
fn workspaces(&mut self) -> Result<Workspaces, Error> {
|
||||
Message::Workspaces.send(self)?;
|
||||
|
||||
let response = Response::read(self)?;
|
||||
|
||||
match response {
|
||||
Response::Workspaces(workspaces) => Ok(workspaces.into()),
|
||||
Response::Version(_) | Response::Command(_) | Response::Outputs(_) => Err(
|
||||
Error::Connection("received invalid response from i3".into()),
|
||||
),
|
||||
}
|
||||
}
|
||||
|
||||
fn command(&mut self, command: Command) -> Result<(), Error> {
|
||||
Message::Command(command).send(self)?;
|
||||
|
||||
let response = Response::read(self)?;
|
||||
match response {
|
||||
Response::Command(commands) => {
|
||||
for payload in commands {
|
||||
if !payload.success {
|
||||
return Err(Error::Command(match payload.error {
|
||||
Some(err) => err.into(),
|
||||
None => "unknown error".into(),
|
||||
}));
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
Response::Version(_) | Response::Workspaces(_) | Response::Outputs(_) => Err(
|
||||
Error::Connection("received invalid response from i3".into()),
|
||||
),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn get_socketpath() -> Result<PathBuf, Error> {
|
||||
let cmd = process::Command::new("i3")
|
||||
.arg("--get-socketpath")
|
||||
.output()?;
|
||||
|
||||
let bytes = cmd
|
||||
.stdout
|
||||
.into_iter()
|
||||
.take_while(|c| *c != b'\n')
|
||||
.collect::<Vec<u8>>();
|
||||
|
||||
let string = OsStr::from_bytes(&bytes);
|
||||
|
||||
let path = PathBuf::from(string);
|
||||
|
||||
Ok(path)
|
||||
}
|
||||
|
||||
pub fn connect() -> Result<Connection, Error> {
|
||||
let socketpath = get_socketpath()?;
|
||||
|
||||
let socket = net::SocketAddr::from_pathname(socketpath)?;
|
||||
|
||||
let stream = net::UnixStream::connect_addr(&socket)?;
|
||||
stream.set_read_timeout(Some(Duration::from_millis(100)))?;
|
||||
|
||||
Ok(Connection(stream))
|
||||
}
|
||||
|
||||
#[derive(Debug, serde::Deserialize)]
|
||||
struct OutputPayload {
|
||||
name: String,
|
||||
active: bool,
|
||||
primary: bool,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct Output {
|
||||
pub name: String,
|
||||
pub active: bool,
|
||||
pub primary: bool,
|
||||
}
|
||||
|
||||
impl fmt::Display for Output {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
write!(f, "{}", self.name)?;
|
||||
if self.active {
|
||||
write!(f, " [active]")?;
|
||||
}
|
||||
if self.primary {
|
||||
write!(f, " [primary]")?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl From<OutputPayload> for Output {
|
||||
fn from(value: OutputPayload) -> Self {
|
||||
Self {
|
||||
name: value.name,
|
||||
active: value.active,
|
||||
primary: value.primary,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct Workspaces(Vec<Workspace>);
|
||||
|
||||
impl From<Vec<WorkspacePayload>> for Workspaces {
|
||||
fn from(value: Vec<WorkspacePayload>) -> Self {
|
||||
Self(value.into_iter().map(Into::into).collect())
|
||||
}
|
||||
}
|
||||
|
||||
impl IntoIterator for Workspaces {
|
||||
type Item = Workspace;
|
||||
type IntoIter = IntoIter<Self::Item>;
|
||||
|
||||
fn into_iter(self) -> Self::IntoIter {
|
||||
self.0.into_iter()
|
||||
}
|
||||
}
|
||||
|
||||
impl Deref for Workspaces {
|
||||
type Target = [Workspace];
|
||||
|
||||
fn deref(&self) -> &[Workspace] {
|
||||
&self.0
|
||||
}
|
||||
}
|
||||
impl DerefMut for Workspaces {
|
||||
fn deref_mut(&mut self) -> &mut [Workspace] {
|
||||
&mut self.0
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct Outputs(Vec<Output>);
|
||||
|
||||
impl From<Vec<OutputPayload>> for Outputs {
|
||||
fn from(value: Vec<OutputPayload>) -> Self {
|
||||
Self(
|
||||
value
|
||||
.into_iter()
|
||||
.filter(|output| output.name != "xroot-0")
|
||||
.map(Into::into)
|
||||
.collect(),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
impl IntoIterator for Outputs {
|
||||
type Item = Output;
|
||||
type IntoIter = <Vec<Output> as IntoIterator>::IntoIter;
|
||||
|
||||
fn into_iter(self) -> Self::IntoIter {
|
||||
self.0.into_iter()
|
||||
}
|
||||
}
|
||||
|
||||
impl Deref for Outputs {
|
||||
type Target = [Output];
|
||||
|
||||
fn deref(&self) -> &[Output] {
|
||||
&self.0
|
||||
}
|
||||
}
|
||||
impl DerefMut for Outputs {
|
||||
fn deref_mut(&mut self) -> &mut [Output] {
|
||||
&mut self.0
|
||||
}
|
||||
}
|
||||
|
||||
impl Index<usize> for Outputs {
|
||||
type Output = Output;
|
||||
|
||||
#[expect(
|
||||
clippy::indexing_slicing,
|
||||
reason = "transparent slicing, panicking is ok"
|
||||
)]
|
||||
fn index(&self, index: usize) -> &Self::Output {
|
||||
&self.0[index]
|
||||
}
|
||||
}
|
||||
|
||||
enum Message {
|
||||
Command(Command),
|
||||
Workspaces,
|
||||
Outputs,
|
||||
Version,
|
||||
}
|
||||
|
||||
impl From<Message> for u32 {
|
||||
fn from(value: Message) -> Self {
|
||||
match value {
|
||||
Message::Command(_) => 0,
|
||||
Message::Workspaces => 1,
|
||||
Message::Outputs => 3,
|
||||
Message::Version => 7,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Message {
|
||||
fn bytes(self) -> Result<Vec<u8>, Error> {
|
||||
let payload: Option<Cow<'static, str>> = match self {
|
||||
Self::Command(ref command) => Some(command.into()),
|
||||
Self::Workspaces | Self::Outputs | Self::Version => None,
|
||||
};
|
||||
|
||||
let mut message: Vec<u8> = vec![];
|
||||
let command_number: u32 = self.into();
|
||||
|
||||
message.extend_from_slice(b"i3-ipc");
|
||||
message.extend_from_slice(
|
||||
&u32::try_from(payload.as_ref().map_or(0, |l| l.len()))
|
||||
.map_err(|_err| Error::Command("payload length bigger than 4 bytes".into()))?
|
||||
.to_ne_bytes(),
|
||||
);
|
||||
message.extend_from_slice(&(command_number.to_ne_bytes()));
|
||||
if let Some(payload) = payload {
|
||||
message.extend_from_slice(payload.as_bytes());
|
||||
}
|
||||
Ok(message)
|
||||
}
|
||||
|
||||
fn send(self, socket: &mut Connection) -> Result<(), Error> {
|
||||
let message = self.bytes()?;
|
||||
socket.0.write_all(&message)?;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, serde::Deserialize)]
|
||||
#[expect(dead_code, reason = "external data defintion")]
|
||||
struct VersionPayload {
|
||||
human_readable: String,
|
||||
loaded_config_file_name: String,
|
||||
major: usize,
|
||||
minor: usize,
|
||||
patch: usize,
|
||||
}
|
||||
|
||||
pub struct Version {
|
||||
minor: usize,
|
||||
patch: usize,
|
||||
major: usize,
|
||||
}
|
||||
|
||||
impl fmt::Display for Version {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
write!(f, "{}.{}.{}", self.major, self.minor, self.patch)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<VersionPayload> for Version {
|
||||
fn from(value: VersionPayload) -> Self {
|
||||
Self {
|
||||
major: value.major,
|
||||
minor: value.minor,
|
||||
patch: value.patch,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, serde::Deserialize)]
|
||||
#[expect(dead_code, reason = "external data defintion")]
|
||||
struct WorkspacePayload {
|
||||
id: usize,
|
||||
num: usize,
|
||||
name: String,
|
||||
output: String,
|
||||
}
|
||||
|
||||
#[derive(Debug, serde::Deserialize)]
|
||||
struct CommandPayload {
|
||||
success: bool,
|
||||
error: Option<String>,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct Workspace {
|
||||
pub num: usize,
|
||||
pub name: String,
|
||||
pub output: String,
|
||||
}
|
||||
|
||||
impl fmt::Display for Workspace {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
write!(f, "{} on {}", self.num, self.output)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<WorkspacePayload> for Workspace {
|
||||
fn from(value: WorkspacePayload) -> Self {
|
||||
Self {
|
||||
num: value.num,
|
||||
name: value.name,
|
||||
output: value.output,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
enum Response {
|
||||
Version(VersionPayload),
|
||||
Workspaces(Vec<WorkspacePayload>),
|
||||
Command(Vec<CommandPayload>),
|
||||
Outputs(Vec<OutputPayload>),
|
||||
}
|
||||
|
||||
impl Response {
|
||||
fn read(stream: &mut Connection) -> Result<Self, Error> {
|
||||
let mut response = vec![
|
||||
0;
|
||||
"i3-ipc".chars().count().checked_add(4 + 4).ok_or_else(|| {
|
||||
Error::Protocol("payload length overflowed".into())
|
||||
})?
|
||||
];
|
||||
|
||||
stream.0.read_exact(&mut response)?;
|
||||
|
||||
if &response
|
||||
.get(0..6)
|
||||
.ok_or_else(|| Error::Protocol("response too short for even the magic string".into()))?
|
||||
!= b"i3-ipc"
|
||||
{
|
||||
return Err(Error::Protocol("magic string not found".into()));
|
||||
}
|
||||
let response_length = {
|
||||
let bytes = response
|
||||
.get(6..10)
|
||||
.ok_or_else(|| Error::Protocol("not enough bytes for response length".into()))?;
|
||||
|
||||
let bytes = bytes
|
||||
.try_into()
|
||||
.expect("slice of length 4 can always be converted into an array of size 4");
|
||||
|
||||
u32::from_ne_bytes(bytes)
|
||||
};
|
||||
|
||||
let response_command = {
|
||||
let bytes = response
|
||||
.get(10..14)
|
||||
.ok_or_else(|| Error::Protocol("not enough bytes for command".into()))?;
|
||||
|
||||
let bytes = bytes
|
||||
.try_into()
|
||||
.expect("slice of length 4 can always be converted into an array of size 4");
|
||||
|
||||
u32::from_ne_bytes(bytes)
|
||||
};
|
||||
|
||||
response = vec![
|
||||
0;
|
||||
response_length
|
||||
.try_into()
|
||||
.map_err(|_err| { Error::Protocol("u32 overflowed usize".into()) })?
|
||||
];
|
||||
|
||||
stream.0.read_exact(&mut response)?;
|
||||
|
||||
match response_command {
|
||||
0 => Ok(Self::Command(serde_json::from_slice(&response)?)),
|
||||
1 => Ok(Self::Workspaces(serde_json::from_slice(&response)?)),
|
||||
3 => Ok(Self::Outputs(serde_json::from_slice(&response)?)),
|
||||
7 => Ok(Self::Version(serde_json::from_slice(&response)?)),
|
||||
_ => Err(Error::Connection("unknown response type".into())),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,17 +1,12 @@
|
||||
pkgbase = screencfg-git
|
||||
pkgbase = screencfg
|
||||
pkgdesc = Automatically configure your screen setup
|
||||
pkgver = 0.1.r0.g1e8bf1d
|
||||
pkgver = 0.1
|
||||
pkgrel = 1
|
||||
url = https://github.com/hakoerber/screencfg
|
||||
arch = x86_64
|
||||
license = GPL-3.0-only
|
||||
makedepends = cargo
|
||||
makedepends = git
|
||||
depends = glibc
|
||||
depends = gcc-libs
|
||||
provides = screencfg
|
||||
conflicts = screencfg
|
||||
source = screencfg-git::git+https://github.com/hakoerber/screencfg#branch=master
|
||||
sha256sums = SKIP
|
||||
|
||||
pkgname = screencfg-git
|
||||
pkgname = screencfg
|
||||
|
||||
17
pkg/arch/grm-git/.SRCINFO
Normal file
17
pkg/arch/grm-git/.SRCINFO
Normal file
@@ -0,0 +1,17 @@
|
||||
pkgbase = screencfg-git
|
||||
pkgdesc = Automatically configure your screen setup
|
||||
pkgver = 0.1.r0.g1e8bf1d
|
||||
pkgrel = 1
|
||||
url = https://github.com/hakoerber/screencfg
|
||||
arch = x86_64
|
||||
license = GPL-3.0-only
|
||||
makedepends = cargo
|
||||
makedepends = git
|
||||
depends = glibc
|
||||
depends = gcc-libs
|
||||
provides = screencfg
|
||||
conflicts = screencfg
|
||||
source = screencfg-git::git+https://github.com/hakoerber/screencfg#branch=master
|
||||
sha256sums = SKIP
|
||||
|
||||
pkgname = screencfg-git
|
||||
4
pkg/arch/grm-git/.gitignore
vendored
Normal file
4
pkg/arch/grm-git/.gitignore
vendored
Normal file
@@ -0,0 +1,4 @@
|
||||
*
|
||||
!/.gitignore
|
||||
!/PKGBUILD
|
||||
!/.SRCINFO
|
||||
13
pkg/arch/local/.SRCINFO
Normal file
13
pkg/arch/local/.SRCINFO
Normal file
@@ -0,0 +1,13 @@
|
||||
pkgbase = screencfg
|
||||
pkgdesc = Automatically configure your screen setup
|
||||
pkgver = 0.1.r0.g1e8bf1d
|
||||
pkgrel = 1
|
||||
url = https://github.com/hakoerber/screencfg
|
||||
arch = x86_64
|
||||
license = GPL-3.0-only
|
||||
makedepends = cargo
|
||||
makedepends = git
|
||||
depends = glibc
|
||||
depends = gcc-libs
|
||||
|
||||
pkgname = screencfg
|
||||
4
pkg/arch/local/.gitignore
vendored
Normal file
4
pkg/arch/local/.gitignore
vendored
Normal file
@@ -0,0 +1,4 @@
|
||||
*
|
||||
!/.gitignore
|
||||
!/PKGBUILD
|
||||
!/.SRCINFO
|
||||
40
pkg/arch/local/PKGBUILD
Normal file
40
pkg/arch/local/PKGBUILD
Normal file
@@ -0,0 +1,40 @@
|
||||
# Maintainer: Hannes Körber <hannes@hkoerber.de>
|
||||
pkgname='screencfg'
|
||||
pkgver=0.1.r0.g1e8bf1d
|
||||
pkgrel=1
|
||||
pkgdesc='Automatically configure your screen setup'
|
||||
arch=('x86_64')
|
||||
url='https://github.com/hakoerber/screencfg'
|
||||
license=('GPL-3.0-only')
|
||||
depends=('glibc' 'gcc-libs')
|
||||
makedepends=('cargo' 'git')
|
||||
source=()
|
||||
sha256sums=()
|
||||
|
||||
prepare() {
|
||||
cd "../../../.."
|
||||
pwd
|
||||
export RUSTUP_TOOLCHAIN=stable
|
||||
cargo fetch --locked --target "$(rustc -vV | sed -n 's/host: //p')"
|
||||
}
|
||||
|
||||
build() {
|
||||
cd "../../../.."
|
||||
pwd
|
||||
export RUSTUP_TOOLCHAIN=stable
|
||||
export CARGO_TARGET_DIR=target
|
||||
cargo build --frozen --release
|
||||
}
|
||||
|
||||
check() {
|
||||
cd "../../../.."
|
||||
pwd
|
||||
export RUSTUP_TOOLCHAIN=stable
|
||||
cargo test --frozen
|
||||
}
|
||||
|
||||
package() {
|
||||
pwd
|
||||
cd "../../../.."
|
||||
install -Dm0755 -t "$pkgdir/usr/bin/" "target/release/${pkgname/-git}"
|
||||
}
|
||||
23
src/config.rs
Normal file
23
src/config.rs
Normal file
@@ -0,0 +1,23 @@
|
||||
use std::path::Path;
|
||||
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use super::Error;
|
||||
|
||||
#[derive(Clone, Debug, Serialize, Deserialize)]
|
||||
#[serde(deny_unknown_fields)]
|
||||
pub(crate) struct Config {
|
||||
pub post_commands: Option<Vec<String>>,
|
||||
}
|
||||
|
||||
pub(crate) fn from_path(path: &Path) -> Result<Option<Config>, Error> {
|
||||
let content = match std::fs::read_to_string(path) {
|
||||
Ok(p) => p,
|
||||
Err(e) => match e.kind() {
|
||||
std::io::ErrorKind::NotFound => return Ok(None),
|
||||
_ => return Err(Error::ConfigFileOpen(e)),
|
||||
},
|
||||
};
|
||||
|
||||
Ok(Some(toml::from_str(&content)?))
|
||||
}
|
||||
104
src/error.rs
Normal file
104
src/error.rs
Normal file
@@ -0,0 +1,104 @@
|
||||
use std::{fmt, io, path::PathBuf, string};
|
||||
|
||||
#[derive(Debug)]
|
||||
pub(crate) enum Msg {
|
||||
Owned(String),
|
||||
Static(&'static str),
|
||||
}
|
||||
|
||||
impl From<&'static str> for Msg {
|
||||
fn from(value: &'static str) -> Self {
|
||||
Self::Static(value)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<String> for Msg {
|
||||
fn from(value: String) -> Self {
|
||||
Self::Owned(value)
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Display for Msg {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
write!(
|
||||
f,
|
||||
"{}",
|
||||
match self {
|
||||
Msg::Owned(ref s) => s.as_str(),
|
||||
Msg::Static(s) => s,
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub(crate) enum Error {
|
||||
Generic(Msg),
|
||||
Command(Msg),
|
||||
Classify(Msg),
|
||||
Workstation(Msg),
|
||||
Plan(Msg),
|
||||
Apply(Msg),
|
||||
I3(i3::Error),
|
||||
Xrandr(xrandr::Error),
|
||||
InvalidSetup(Msg),
|
||||
InvalidConfig(Msg),
|
||||
ConfigFileOpen(io::Error),
|
||||
ConfigNotFound { path: PathBuf },
|
||||
}
|
||||
|
||||
impl fmt::Display for Error {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
write!(
|
||||
f,
|
||||
"{}",
|
||||
match *self {
|
||||
Self::Generic(ref msg) => format!("error: {msg}"),
|
||||
Self::Command(ref msg) => format!("command failed: {msg}"),
|
||||
Self::Classify(ref msg) => format!("classification failed: {msg}"),
|
||||
Self::Workstation(ref msg) => format!("workstation failed: {msg}"),
|
||||
Self::Plan(ref msg) => format!("plan failed: {msg}"),
|
||||
Self::Apply(ref msg) => format!("apply failed: {msg}"),
|
||||
Self::I3(ref e) => format!("i3: {e}"),
|
||||
Self::Xrandr(ref e) => format!("xrandr: {e}"),
|
||||
Self::InvalidSetup(ref msg) => format!("invalid setup: {msg}"),
|
||||
Self::InvalidConfig(ref msg) => format!("invalid config: {msg}"),
|
||||
Self::ConfigFileOpen(ref err) => format!("could not open config: {err}"),
|
||||
Self::ConfigNotFound { ref path } =>
|
||||
format!("could not find config file at {}", path.display()),
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<fmt::Error> for Error {
|
||||
fn from(value: fmt::Error) -> Self {
|
||||
Self::Generic(Msg::Owned(value.to_string()))
|
||||
}
|
||||
}
|
||||
|
||||
impl From<i3::Error> for Error {
|
||||
fn from(value: i3::Error) -> Self {
|
||||
Self::I3(value)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<xrandr::Error> for Error {
|
||||
fn from(value: xrandr::Error) -> Self {
|
||||
Self::Xrandr(value)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<string::FromUtf8Error> for Error {
|
||||
fn from(value: string::FromUtf8Error) -> Self {
|
||||
Self::Command(Msg::Owned(value.to_string()))
|
||||
}
|
||||
}
|
||||
|
||||
impl From<toml::de::Error> for Error {
|
||||
fn from(value: toml::de::Error) -> Self {
|
||||
Self::InvalidConfig(Msg::Owned(value.to_string()))
|
||||
}
|
||||
}
|
||||
|
||||
impl std::error::Error for Error {}
|
||||
407
src/i3.rs
407
src/i3.rs
@@ -1,407 +0,0 @@
|
||||
use std::ffi::OsStr;
|
||||
use std::fmt;
|
||||
use std::io::{self, Read, Write};
|
||||
use std::ops::{Deref, DerefMut, Index};
|
||||
use std::os::unix::ffi::OsStrExt as _;
|
||||
use std::os::unix::net;
|
||||
use std::path::PathBuf;
|
||||
use std::process;
|
||||
use std::time::Duration;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum Error {
|
||||
Connection(String),
|
||||
Command(String),
|
||||
}
|
||||
|
||||
impl fmt::Display for Error {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
write!(
|
||||
f,
|
||||
"{}",
|
||||
match self {
|
||||
Error::Connection(msg) => format!("connection failed: {msg}"),
|
||||
Error::Command(msg) => format!("command failed: {msg}"),
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<io::Error> for Error {
|
||||
fn from(value: io::Error) -> Self {
|
||||
Self::Command(value.to_string())
|
||||
}
|
||||
}
|
||||
|
||||
impl From<serde_json::Error> for Error {
|
||||
fn from(value: serde_json::Error) -> Self {
|
||||
Self::Connection(value.to_string())
|
||||
}
|
||||
}
|
||||
|
||||
impl std::error::Error for Error {}
|
||||
|
||||
pub struct Connection(net::UnixStream);
|
||||
|
||||
#[derive(Clone)]
|
||||
pub enum Command {
|
||||
Nop,
|
||||
MoveWorkspace { id: usize, output: Output },
|
||||
}
|
||||
|
||||
impl From<Command> for String {
|
||||
fn from(value: Command) -> Self {
|
||||
match value {
|
||||
Command::Nop => "nop".to_string(),
|
||||
Command::MoveWorkspace { id, output } => {
|
||||
format!(
|
||||
"[workspace=\"{id}\"] move workspace to output {}",
|
||||
output.name
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Connection {
|
||||
pub fn version(&mut self) -> Result<Version, Error> {
|
||||
Message::Version.send(self)?;
|
||||
|
||||
let response = Response::read(self)?;
|
||||
|
||||
match response {
|
||||
Response::Version(version) => Ok(version.into()),
|
||||
_ => Err(Error::Connection(
|
||||
"received invalid response from i3".into(),
|
||||
)),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn outputs(&mut self) -> Result<Outputs, Error> {
|
||||
Message::Outputs.send(self)?;
|
||||
let response = Response::read(self)?;
|
||||
|
||||
match response {
|
||||
Response::Outputs(outputs) => Ok(outputs.into()),
|
||||
_ => Err(Error::Connection(
|
||||
"received invalid response from i3".into(),
|
||||
)),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn workspaces(&mut self) -> Result<Workspaces, Error> {
|
||||
Message::Workspaces.send(self)?;
|
||||
|
||||
let response = Response::read(self)?;
|
||||
|
||||
match response {
|
||||
Response::Workspaces(workspaces) => Ok(workspaces.into()),
|
||||
_ => Err(Error::Connection(
|
||||
"received invalid response from i3".into(),
|
||||
)),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn command(&mut self, command: Command) -> Result<(), Error> {
|
||||
Message::Command(command).send(self)?;
|
||||
|
||||
let response = Response::read(self)?;
|
||||
match response {
|
||||
Response::Command(commands) => {
|
||||
for payload in commands {
|
||||
if !payload.success {
|
||||
return Err(Error::Command(
|
||||
payload.error.unwrap_or_else(|| "unknown error".into()),
|
||||
));
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
_ => Err(Error::Connection(
|
||||
"received invalid response from i3".into(),
|
||||
)),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn get_socketpath() -> Result<PathBuf, Error> {
|
||||
let cmd = process::Command::new("i3")
|
||||
.arg("--get-socketpath")
|
||||
.output()?;
|
||||
|
||||
let bytes = cmd
|
||||
.stdout
|
||||
.into_iter()
|
||||
.take_while(|c| *c != b'\n')
|
||||
.collect::<Vec<u8>>();
|
||||
|
||||
let string = OsStr::from_bytes(&bytes);
|
||||
|
||||
let path = PathBuf::from(string);
|
||||
|
||||
Ok(path)
|
||||
}
|
||||
|
||||
pub fn connect() -> Result<Connection, Error> {
|
||||
let socketpath = get_socketpath()?;
|
||||
|
||||
let socket = net::SocketAddr::from_pathname(socketpath)?;
|
||||
|
||||
let stream = net::UnixStream::connect_addr(&socket)?;
|
||||
stream.set_read_timeout(Some(Duration::from_millis(100)))?;
|
||||
|
||||
Ok(Connection(stream))
|
||||
}
|
||||
|
||||
#[derive(Debug, serde::Deserialize)]
|
||||
struct OutputPayload {
|
||||
name: String,
|
||||
active: bool,
|
||||
primary: bool,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct Output {
|
||||
name: String,
|
||||
active: bool,
|
||||
primary: bool,
|
||||
}
|
||||
|
||||
impl fmt::Display for Output {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
write!(f, "{}", self.name)?;
|
||||
if self.active {
|
||||
write!(f, " [active]")?;
|
||||
}
|
||||
if self.primary {
|
||||
write!(f, " [primary]")?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl From<OutputPayload> for Output {
|
||||
fn from(value: OutputPayload) -> Self {
|
||||
Self {
|
||||
name: value.name,
|
||||
active: value.active,
|
||||
primary: value.primary,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct Workspaces(Vec<Workspace>);
|
||||
|
||||
impl From<Vec<WorkspacePayload>> for Workspaces {
|
||||
fn from(value: Vec<WorkspacePayload>) -> Self {
|
||||
Self(value.into_iter().map(Into::into).collect())
|
||||
}
|
||||
}
|
||||
|
||||
impl IntoIterator for Workspaces {
|
||||
type Item = Workspace;
|
||||
type IntoIter = std::vec::IntoIter<Self::Item>;
|
||||
|
||||
fn into_iter(self) -> Self::IntoIter {
|
||||
self.0.into_iter()
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct Outputs(Vec<Output>);
|
||||
|
||||
impl From<Vec<OutputPayload>> for Outputs {
|
||||
fn from(value: Vec<OutputPayload>) -> Self {
|
||||
Self(
|
||||
value
|
||||
.into_iter()
|
||||
.filter(|output| output.name != "xroot-0")
|
||||
.map(Into::into)
|
||||
.collect(),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
impl IntoIterator for Outputs {
|
||||
type Item = Output;
|
||||
type IntoIter = <Vec<Output> as IntoIterator>::IntoIter;
|
||||
|
||||
fn into_iter(self) -> Self::IntoIter {
|
||||
self.0.into_iter()
|
||||
}
|
||||
}
|
||||
|
||||
impl Deref for Outputs {
|
||||
type Target = [Output];
|
||||
|
||||
fn deref(&self) -> &[Output] {
|
||||
&self.0[..]
|
||||
}
|
||||
}
|
||||
impl DerefMut for Outputs {
|
||||
fn deref_mut(&mut self) -> &mut [Output] {
|
||||
&mut self.0[..]
|
||||
}
|
||||
}
|
||||
|
||||
impl Index<usize> for Outputs {
|
||||
type Output = Output;
|
||||
|
||||
fn index(&self, index: usize) -> &Self::Output {
|
||||
&self.0[index]
|
||||
}
|
||||
}
|
||||
|
||||
enum Message {
|
||||
Command(Command),
|
||||
Workspaces,
|
||||
Outputs,
|
||||
Version,
|
||||
}
|
||||
|
||||
impl From<Message> for u32 {
|
||||
fn from(value: Message) -> Self {
|
||||
match value {
|
||||
Message::Command(_) => 0,
|
||||
Message::Workspaces => 1,
|
||||
Message::Outputs => 3,
|
||||
Message::Version => 7,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Message {
|
||||
fn bytes(self) -> Vec<u8> {
|
||||
let payload: Option<String> = match self {
|
||||
Message::Command(ref command) => Some(command.clone().into()),
|
||||
Message::Workspaces => None,
|
||||
Message::Outputs => None,
|
||||
Message::Version => None,
|
||||
};
|
||||
|
||||
let mut message: Vec<u8> = vec![];
|
||||
let command_number: u32 = self.into();
|
||||
|
||||
message.extend_from_slice("i3-ipc".as_bytes());
|
||||
message.extend_from_slice(
|
||||
&(payload.as_ref().map_or(0, |payload| payload.len()) as u32).to_ne_bytes(),
|
||||
);
|
||||
message.extend_from_slice(&(command_number.to_ne_bytes()));
|
||||
if let Some(payload) = payload {
|
||||
message.extend_from_slice(payload.as_bytes())
|
||||
}
|
||||
message
|
||||
}
|
||||
|
||||
fn send(self, socket: &mut Connection) -> Result<(), Error> {
|
||||
let message = self.bytes();
|
||||
println!("{message:?}");
|
||||
socket.0.write_all(&message)?;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
#[derive(Debug, serde::Deserialize)]
|
||||
struct VersionPayload {
|
||||
human_readable: String,
|
||||
loaded_config_file_name: String,
|
||||
major: usize,
|
||||
minor: usize,
|
||||
patch: usize,
|
||||
}
|
||||
|
||||
pub struct Version {
|
||||
minor: usize,
|
||||
patch: usize,
|
||||
major: usize,
|
||||
}
|
||||
|
||||
impl fmt::Display for Version {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
write!(f, "{}.{}.{}", self.major, self.minor, self.patch)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<VersionPayload> for Version {
|
||||
fn from(value: VersionPayload) -> Self {
|
||||
Self {
|
||||
major: value.major,
|
||||
minor: value.minor,
|
||||
patch: value.patch,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, serde::Deserialize)]
|
||||
struct WorkspacePayload {
|
||||
#[allow(dead_code)]
|
||||
id: usize,
|
||||
num: usize,
|
||||
name: String,
|
||||
output: String,
|
||||
}
|
||||
|
||||
#[derive(Debug, serde::Deserialize)]
|
||||
struct CommandPayload {
|
||||
success: bool,
|
||||
error: Option<String>,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct Workspace {
|
||||
num: usize,
|
||||
#[allow(dead_code)]
|
||||
name: String,
|
||||
output: String,
|
||||
}
|
||||
|
||||
impl fmt::Display for Workspace {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
write!(f, "{} on {}", self.num, self.output)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<WorkspacePayload> for Workspace {
|
||||
fn from(value: WorkspacePayload) -> Self {
|
||||
Self {
|
||||
num: value.num,
|
||||
name: value.name,
|
||||
output: value.output,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
#[derive(Debug)]
|
||||
enum Response {
|
||||
Version(VersionPayload),
|
||||
Workspaces(Vec<WorkspacePayload>),
|
||||
Command(Vec<CommandPayload>),
|
||||
Outputs(Vec<OutputPayload>),
|
||||
}
|
||||
|
||||
impl Response {
|
||||
fn read(stream: &mut Connection) -> Result<Self, Error> {
|
||||
let mut response = vec![0; "i3-ipc".chars().count() + 4 + 4];
|
||||
|
||||
stream.0.read_exact(&mut response)?;
|
||||
|
||||
assert_eq!(&response[0..6], "i3-ipc".as_bytes());
|
||||
let response_length = u32::from_ne_bytes(response[6..10].try_into().unwrap());
|
||||
let response_command = u32::from_ne_bytes(response[10..14].try_into().unwrap());
|
||||
|
||||
response = vec![0; response_length as usize];
|
||||
|
||||
stream.0.read_exact(&mut response)?;
|
||||
|
||||
match response_command {
|
||||
0 => Ok(Response::Command(serde_json::from_slice(&response)?)),
|
||||
1 => Ok(Response::Workspaces(serde_json::from_slice(&response)?)),
|
||||
3 => Ok(Response::Outputs(serde_json::from_slice(&response)?)),
|
||||
7 => Ok(Response::Version(serde_json::from_slice(&response)?)),
|
||||
_ => return Err(Error::Connection("unknown response type".into())),
|
||||
}
|
||||
}
|
||||
}
|
||||
1308
src/main.rs
1308
src/main.rs
File diff suppressed because it is too large
Load Diff
9
xrandr/Cargo.toml
Normal file
9
xrandr/Cargo.toml
Normal file
@@ -0,0 +1,9 @@
|
||||
[package]
|
||||
name = "xrandr"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
|
||||
[dependencies]
|
||||
|
||||
[lints]
|
||||
workspace = true
|
||||
65
xrandr/src/error.rs
Normal file
65
xrandr/src/error.rs
Normal file
@@ -0,0 +1,65 @@
|
||||
use std::{fmt, io, string};
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum Msg {
|
||||
Owned(String),
|
||||
Static(&'static str),
|
||||
}
|
||||
|
||||
impl From<&'static str> for Msg {
|
||||
fn from(value: &'static str) -> Self {
|
||||
Self::Static(value)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<String> for Msg {
|
||||
fn from(value: String) -> Self {
|
||||
Self::Owned(value)
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Display for Msg {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
write!(
|
||||
f,
|
||||
"{}",
|
||||
match *self {
|
||||
Self::Owned(ref s) => s.as_str(),
|
||||
Self::Static(s) => s,
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum Error {
|
||||
Command(Msg),
|
||||
Parse(Msg),
|
||||
}
|
||||
|
||||
impl From<io::Error> for Error {
|
||||
fn from(value: io::Error) -> Self {
|
||||
Self::Command(Msg::Owned(value.to_string()))
|
||||
}
|
||||
}
|
||||
|
||||
impl From<string::FromUtf8Error> for Error {
|
||||
fn from(value: string::FromUtf8Error) -> Self {
|
||||
Self::Parse(Msg::Owned(value.to_string()))
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Display for Error {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
write!(
|
||||
f,
|
||||
"{}",
|
||||
match *self {
|
||||
Self::Command(ref msg) => format!("command failed: {msg}"),
|
||||
Self::Parse(ref msg) => format!("parsing command output failed: {msg}"),
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
impl std::error::Error for Error {}
|
||||
57
xrandr/src/lib.rs
Normal file
57
xrandr/src/lib.rs
Normal file
@@ -0,0 +1,57 @@
|
||||
use std::{process, string::String};
|
||||
|
||||
mod error;
|
||||
|
||||
pub use error::Error;
|
||||
|
||||
#[derive(Debug, PartialEq, Eq)]
|
||||
pub enum OutputState {
|
||||
Connected,
|
||||
Disconnected,
|
||||
}
|
||||
|
||||
impl TryFrom<&str> for OutputState {
|
||||
type Error = Error;
|
||||
|
||||
fn try_from(value: &str) -> Result<Self, Self::Error> {
|
||||
match value {
|
||||
"connected" => Ok(Self::Connected),
|
||||
"disconnected" => Ok(Self::Disconnected),
|
||||
_ => Err(Error::Parse(
|
||||
format!("unknown xrandr output state: {value}").into(),
|
||||
)),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub struct Output {
|
||||
pub name: String,
|
||||
pub state: OutputState,
|
||||
}
|
||||
|
||||
impl Output {
|
||||
pub fn findall() -> Result<Vec<Self>, Error> {
|
||||
String::from_utf8(
|
||||
process::Command::new("xrandr")
|
||||
.arg("--query")
|
||||
.output()?
|
||||
.stdout,
|
||||
)?
|
||||
.lines()
|
||||
.skip(1) // skip header
|
||||
.filter(|line| line.chars().next().map_or(false, char::is_alphanumeric))
|
||||
.map(|line| {
|
||||
let mut parts = line.split_whitespace();
|
||||
match (parts.next(), parts.next()) {
|
||||
(Some(part_1), Some(part_2)) => Ok(Self {
|
||||
name: part_1.to_owned(),
|
||||
state: part_2.try_into()?,
|
||||
}),
|
||||
_ => Err(Error::Command(
|
||||
format!("not enough output information in line: {line}").into(),
|
||||
)),
|
||||
}
|
||||
})
|
||||
.collect()
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user