Compare commits

...

6 Commits

Author SHA1 Message Date
e6da6282b1 Add pkgbuild push script 2024-11-10 15:55:06 +01:00
80fb344128 Update PKGBUILD 2024-11-10 15:54:13 +01:00
d3bdf46ecb Merge commit '41c032be54208ec8472db1aa78652608459fe511' as 'pkg/arch/screencfg-git' 2024-11-10 15:52:32 +01:00
41c032be54 Squashed 'pkg/arch/screencfg-git/' content from commit 4bd67cf1
git-subtree-dir: pkg/arch/screencfg-git
git-subtree-split: 4bd67cf18ed592dd155ad7bbad0f67d890807fc0
2024-11-10 15:52:32 +01:00
d33f9b3ede Rework 2024-11-10 03:31:39 +01:00
4a968e5ba5 Implement i3 connection 2024-05-08 00:32:48 +02:00
21 changed files with 2373 additions and 463 deletions

246
Cargo.lock generated
View File

@@ -4,9 +4,9 @@ version = 3
[[package]] [[package]]
name = "anstream" name = "anstream"
version = "0.6.14" version = "0.6.18"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "418c75fa768af9c03be99d17643f93f79bbba589895012a80e3452a19ddda15b" checksum = "8acc5369981196006228e28809f761875c0327210a891e941f4c683b3a99529b"
dependencies = [ dependencies = [
"anstyle", "anstyle",
"anstyle-parse", "anstyle-parse",
@@ -19,33 +19,33 @@ dependencies = [
[[package]] [[package]]
name = "anstyle" name = "anstyle"
version = "1.0.7" version = "1.0.10"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "038dfcf04a5feb68e9c60b21c9625a54c2c0616e79b72b0fd87075a056ae1d1b" checksum = "55cc3b69f167a1ef2e161439aa98aed94e6028e5f9a59be9a6ffb47aef1651f9"
[[package]] [[package]]
name = "anstyle-parse" name = "anstyle-parse"
version = "0.2.4" version = "0.2.6"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c03a11a9034d92058ceb6ee011ce58af4a9bf61491aa7e1e59ecd24bd40d22d4" checksum = "3b2d16507662817a6a20a9ea92df6652ee4f94f914589377d69f3b21bc5798a9"
dependencies = [ dependencies = [
"utf8parse", "utf8parse",
] ]
[[package]] [[package]]
name = "anstyle-query" name = "anstyle-query"
version = "1.0.3" version = "1.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a64c907d4e79225ac72e2a354c9ce84d50ebb4586dee56c82b3ee73004f537f5" checksum = "79947af37f4177cfead1110013d678905c37501914fba0efea834c3fe9a8d60c"
dependencies = [ dependencies = [
"windows-sys", "windows-sys",
] ]
[[package]] [[package]]
name = "anstyle-wincon" name = "anstyle-wincon"
version = "3.0.3" version = "3.0.6"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "61a38449feb7068f52bb06c12759005cf459ee52bb4adc1d5a7c4322d716fb19" checksum = "2109dbce0e72be3ec00bed26e6a7479ca384ad226efdd66db8fa2e3a38c83125"
dependencies = [ dependencies = [
"anstyle", "anstyle",
"windows-sys", "windows-sys",
@@ -53,9 +53,9 @@ dependencies = [
[[package]] [[package]]
name = "clap" name = "clap"
version = "4.5.4" version = "4.5.20"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "90bc066a67923782aa8515dbaea16946c5bcc5addbd668bb80af688e53e548a0" checksum = "b97f376d85a664d5837dbae44bf546e6477a679ff6610010f17276f686d867e8"
dependencies = [ dependencies = [
"clap_builder", "clap_builder",
"clap_derive", "clap_derive",
@@ -63,9 +63,9 @@ dependencies = [
[[package]] [[package]]
name = "clap_builder" name = "clap_builder"
version = "4.5.2" version = "4.5.20"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ae129e2e766ae0ec03484e609954119f123cc1fe650337e155d03b022f24f7b4" checksum = "19bc80abd44e4bed93ca373a0704ccbd1b710dc5749406201bb018272808dc54"
dependencies = [ dependencies = [
"anstream", "anstream",
"anstyle", "anstyle",
@@ -75,9 +75,9 @@ dependencies = [
[[package]] [[package]]
name = "clap_derive" name = "clap_derive"
version = "4.5.4" version = "4.5.18"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "528131438037fd55894f62d6e9f068b8f45ac57ffa77517819645d10aed04f64" checksum = "4ac6a0c7b1a9e9a5186361f67dfa1b88213572f427fb9ab038efb2bd8c582dab"
dependencies = [ dependencies = [
"heck", "heck",
"proc-macro2", "proc-macro2",
@@ -87,15 +87,27 @@ dependencies = [
[[package]] [[package]]
name = "clap_lex" name = "clap_lex"
version = "0.7.0" version = "0.7.2"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "98cc8fbded0c607b7ba9dd60cd98df59af97e84d24e49c8557331cfc26d301ce" checksum = "1462739cb27611015575c0c11df5df7601141071f07518d56fcc1be504cbec97"
[[package]] [[package]]
name = "colorchoice" 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" version = "1.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index" 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]] [[package]]
name = "heck" name = "heck"
@@ -104,34 +116,115 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea"
[[package]] [[package]]
name = "is_terminal_polyfill" name = "i3"
version = "1.70.0" 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" 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"
version = "1.0.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "49f1f14873335454500d59611f1cf4a4b0f786f9ac11f4312a78e4cf2566695b"
[[package]]
name = "memchr"
version = "2.7.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3"
[[package]] [[package]]
name = "proc-macro2" name = "proc-macro2"
version = "1.0.82" version = "1.0.89"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8ad3d49ab951a01fbaafe34f2ec74122942fe18a3f9814c3268f1bb72042131b" checksum = "f139b0662de085916d1fb67d2b4169d1addddda1919e696f3252b740b629986e"
dependencies = [ dependencies = [
"unicode-ident", "unicode-ident",
] ]
[[package]] [[package]]
name = "quote" name = "quote"
version = "1.0.36" version = "1.0.37"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0fa76aaf39101c457836aec0ce2316dbdc3ab723cdda1c6bd4e6ad4208acaca7" checksum = "b5b9d34b8991d19d98081b46eacdd8eb58c6f2b201139f7c5f643cc155a633af"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
] ]
[[package]]
name = "ryu"
version = "1.0.18"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f3cb5ba0dc43242ce17de99c180e96db90b235b8a9fdc9543c96d2209116bd9f"
[[package]] [[package]]
name = "screencfg" name = "screencfg"
version = "0.1.0" version = "0.1.0"
dependencies = [ dependencies = [
"clap", "clap",
"i3",
"serde",
"toml",
"xrandr",
]
[[package]]
name = "serde"
version = "1.0.214"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f55c3193aca71c12ad7890f1785d2b73e1b9f63a0bbc353c08ef26fe03fc56b5"
dependencies = [
"serde_derive",
]
[[package]]
name = "serde_derive"
version = "1.0.214"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "de523f781f095e28fa605cdce0f8307e451cc0fd14e2eb4cd2e98a355b147766"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "serde_json"
version = "1.0.132"
source = "registry+https://github.com/rust-lang/crates.io-index"
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]] [[package]]
@@ -142,9 +235,9 @@ checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f"
[[package]] [[package]]
name = "syn" name = "syn"
version = "2.0.61" version = "2.0.87"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c993ed8ccba56ae856363b1845da7266a7cb78e1d146c8a32d54b45a8b831fc9" checksum = "25aa4ce346d03a6dcd68dd8b4010bcb74e54e62c90c573f394c46eae99aba32d"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
@@ -152,31 +245,65 @@ dependencies = [
] ]
[[package]] [[package]]
name = "unicode-ident" name = "toml"
version = "1.0.12" version = "0.8.19"
source = "registry+https://github.com/rust-lang/crates.io-index" 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]] [[package]]
name = "utf8parse" name = "utf8parse"
version = "0.2.1" version = "0.2.2"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "711b9620af191e0cdc7468a8d14e709c3dcdb115b36f838e601583af800a370a" checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821"
[[package]] [[package]]
name = "windows-sys" name = "windows-sys"
version = "0.52.0" version = "0.59.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b"
dependencies = [ dependencies = [
"windows-targets", "windows-targets",
] ]
[[package]] [[package]]
name = "windows-targets" name = "windows-targets"
version = "0.52.5" version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6f0713a46559409d202e70e28227288446bf7841d3211583a4b53e3f6d96e7eb" checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973"
dependencies = [ dependencies = [
"windows_aarch64_gnullvm", "windows_aarch64_gnullvm",
"windows_aarch64_msvc", "windows_aarch64_msvc",
@@ -190,48 +317,61 @@ dependencies = [
[[package]] [[package]]
name = "windows_aarch64_gnullvm" name = "windows_aarch64_gnullvm"
version = "0.52.5" version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7088eed71e8b8dda258ecc8bac5fb1153c5cffaf2578fc8ff5d61e23578d3263" checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3"
[[package]] [[package]]
name = "windows_aarch64_msvc" name = "windows_aarch64_msvc"
version = "0.52.5" version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9985fd1504e250c615ca5f281c3f7a6da76213ebd5ccc9561496568a2752afb6" checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469"
[[package]] [[package]]
name = "windows_i686_gnu" name = "windows_i686_gnu"
version = "0.52.5" version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "88ba073cf16d5372720ec942a8ccbf61626074c6d4dd2e745299726ce8b89670" checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b"
[[package]] [[package]]
name = "windows_i686_gnullvm" name = "windows_i686_gnullvm"
version = "0.52.5" version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "87f4261229030a858f36b459e748ae97545d6f1ec60e5e0d6a3d32e0dc232ee9" checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66"
[[package]] [[package]]
name = "windows_i686_msvc" name = "windows_i686_msvc"
version = "0.52.5" version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "db3c2bf3d13d5b658be73463284eaf12830ac9a26a90c717b7f771dfe97487bf" checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66"
[[package]] [[package]]
name = "windows_x86_64_gnu" name = "windows_x86_64_gnu"
version = "0.52.5" version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4e4246f76bdeff09eb48875a0fd3e2af6aada79d409d33011886d3e1581517d9" checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78"
[[package]] [[package]]
name = "windows_x86_64_gnullvm" name = "windows_x86_64_gnullvm"
version = "0.52.5" version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "852298e482cd67c356ddd9570386e2862b5673c85bd5f88df9ab6802b334c596" checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d"
[[package]] [[package]]
name = "windows_x86_64_msvc" name = "windows_x86_64_msvc"
version = "0.52.5" version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index" 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"

View File

@@ -1,12 +1,222 @@
[package] [package]
name = "screencfg" name = "screencfg"
description = "Automatically configure your screen setup" description = "Automatically configure your screen setup with i3"
version = "0.1.0" version = "0.1.0"
edition = "2021" 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] [dependencies]
clap = { version = "4.5.4", default-features = false, features = ["std", "derive"] } 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] [features]
default = ["full"] 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
View 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

0
README.md Normal file
View File

11
i3/Cargo.toml Normal file
View 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
View 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
View 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())),
}
}
}

View File

@@ -1,17 +1,12 @@
pkgbase = screencfg-git pkgbase = screencfg
pkgdesc = Automatically configure your screen setup pkgdesc = Automatically configure your screen setup
pkgver = 0.1.r0.g1e8bf1d pkgver = 0.1
pkgrel = 1 pkgrel = 1
url = https://github.com/hakoerber/screencfg
arch = x86_64 arch = x86_64
license = GPL-3.0-only license = GPL-3.0-only
makedepends = cargo makedepends = cargo
makedepends = git makedepends = git
depends = glibc depends = glibc
depends = gcc-libs 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

13
pkg/arch/local/.SRCINFO Normal file
View 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
View File

@@ -0,0 +1,4 @@
*
!/.gitignore
!/PKGBUILD
!/.SRCINFO

40
pkg/arch/local/PKGBUILD Normal file
View 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}"
}

View File

@@ -0,0 +1,17 @@
pkgbase = screencfg-git
pkgdesc = Automatically configure your screen setup
pkgver = 0.1.r2.gd33f9b3
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=develop
sha256sums = SKIP
pkgname = screencfg-git

4
pkg/arch/screencfg-git/.gitignore vendored Normal file
View File

@@ -0,0 +1,4 @@
*
!/.gitignore
!/PKGBUILD
!/.SRCINFO

View File

@@ -1,6 +1,6 @@
# Maintainer: Hannes Körber <hannes@hkoerber.de> # Maintainer: Hannes Körber <hannes@hkoerber.de>
pkgname='screencfg-git' pkgname='screencfg-git'
pkgver=0.1.r0.g1e8bf1d pkgver=0.1.r2.gd33f9b3
pkgrel=1 pkgrel=1
pkgdesc='Automatically configure your screen setup' pkgdesc='Automatically configure your screen setup'
arch=('x86_64') arch=('x86_64')
@@ -10,7 +10,7 @@ depends=('glibc' 'gcc-libs')
makedepends=('cargo' 'git') makedepends=('cargo' 'git')
provides=('screencfg') provides=('screencfg')
conflicts=('screencfg') conflicts=('screencfg')
source=("${pkgname}::git+https://github.com/hakoerber/screencfg#branch=master") source=("${pkgname}::git+https://github.com/hakoerber/screencfg#branch=develop")
sha256sums=('SKIP') sha256sums=('SKIP')
pkgver() { pkgver() {

23
src/config.rs Normal file
View 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
View 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 {}

File diff suppressed because it is too large Load Diff

9
update-pkgbuild.sh Executable file
View File

@@ -0,0 +1,9 @@
#!/usr/bin/env bash
if ! git remote | grep -q ^aur$ ; then
git remote add aur ssh://aur@aur.archlinux.org/screencfg-git.git
fi
git subtree push --prefix pkg/arch/screencfg-git aur master
git remote rm aur

9
xrandr/Cargo.toml Normal file
View 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
View 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
View 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()
}
}