mirror of
https://github.com/pinnacle-comp/pinnacle.git
synced 2024-12-26 21:58:10 +01:00
commit
96d5c9a70f
14 changed files with 1125 additions and 212 deletions
235
Cargo.lock
generated
235
Cargo.lock
generated
|
@ -66,6 +66,21 @@ 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 = "fc7eb209b1518d6bb87b283c20095f5228ecda460da70b44f0802523dea6da04"
|
checksum = "fc7eb209b1518d6bb87b283c20095f5228ecda460da70b44f0802523dea6da04"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "android-tzdata"
|
||||||
|
version = "0.1.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "e999941b234f3131b00bc13c22d06e8c5ff726d1b6318ac7eb276997bbb4fef0"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "android_system_properties"
|
||||||
|
version = "0.1.5"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "819e7219dbd41043ac279b19830f2efc897156490d7fd6ea916720117ee66311"
|
||||||
|
dependencies = [
|
||||||
|
"libc",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "anstream"
|
name = "anstream"
|
||||||
version = "0.6.11"
|
version = "0.6.11"
|
||||||
|
@ -295,6 +310,17 @@ dependencies = [
|
||||||
"objc2",
|
"objc2",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "bstr"
|
||||||
|
version = "1.9.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "c48f0051a4b4c5e0b6d365cd04af53aeaa209e3cc15ec2cdb69e73cc87fbd0dc"
|
||||||
|
dependencies = [
|
||||||
|
"memchr",
|
||||||
|
"regex-automata 0.4.5",
|
||||||
|
"serde",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "bumpalo"
|
name = "bumpalo"
|
||||||
version = "3.14.0"
|
version = "3.14.0"
|
||||||
|
@ -397,6 +423,20 @@ dependencies = [
|
||||||
"num-traits",
|
"num-traits",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "chrono"
|
||||||
|
version = "0.4.34"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "5bc015644b92d5890fab7489e49d21f879d5c990186827d42ec511919404f38b"
|
||||||
|
dependencies = [
|
||||||
|
"android-tzdata",
|
||||||
|
"iana-time-zone",
|
||||||
|
"js-sys",
|
||||||
|
"num-traits",
|
||||||
|
"wasm-bindgen",
|
||||||
|
"windows-targets 0.52.0",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "clap"
|
name = "clap"
|
||||||
version = "4.5.1"
|
version = "4.5.1"
|
||||||
|
@ -437,6 +477,19 @@ version = "0.7.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "98cc8fbded0c607b7ba9dd60cd98df59af97e84d24e49c8557331cfc26d301ce"
|
checksum = "98cc8fbded0c607b7ba9dd60cd98df59af97e84d24e49c8557331cfc26d301ce"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "cliclack"
|
||||||
|
version = "0.1.13"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "be29210ca32b96e4f67fe9a520d2eeacc078d94ff4027100dc6b7262fdfec5c4"
|
||||||
|
dependencies = [
|
||||||
|
"console",
|
||||||
|
"indicatif",
|
||||||
|
"once_cell",
|
||||||
|
"textwrap",
|
||||||
|
"zeroize",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "color_quant"
|
name = "color_quant"
|
||||||
version = "1.1.0"
|
version = "1.1.0"
|
||||||
|
@ -468,6 +521,19 @@ dependencies = [
|
||||||
"crossbeam-utils",
|
"crossbeam-utils",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "console"
|
||||||
|
version = "0.15.8"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "0e1f83fc076bd6dd27517eacdf25fef6c4dfe5f1d7448bafaaf3a26f13b5e4eb"
|
||||||
|
dependencies = [
|
||||||
|
"encode_unicode",
|
||||||
|
"lazy_static",
|
||||||
|
"libc",
|
||||||
|
"unicode-width",
|
||||||
|
"windows-sys 0.52.0",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "const_format"
|
name = "const_format"
|
||||||
version = "0.2.32"
|
version = "0.2.32"
|
||||||
|
@ -528,6 +594,19 @@ dependencies = [
|
||||||
"libc",
|
"libc",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "crossbeam"
|
||||||
|
version = "0.8.4"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "1137cd7e7fc0fb5d3c5a8678be38ec56e819125d8d7907411fe24ccb943faca8"
|
||||||
|
dependencies = [
|
||||||
|
"crossbeam-channel",
|
||||||
|
"crossbeam-deque",
|
||||||
|
"crossbeam-epoch",
|
||||||
|
"crossbeam-queue",
|
||||||
|
"crossbeam-utils",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "crossbeam-channel"
|
name = "crossbeam-channel"
|
||||||
version = "0.5.11"
|
version = "0.5.11"
|
||||||
|
@ -556,6 +635,15 @@ dependencies = [
|
||||||
"crossbeam-utils",
|
"crossbeam-utils",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "crossbeam-queue"
|
||||||
|
version = "0.3.11"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "df0346b5d5e76ac2fe4e327c5fd1118d6be7c51dfb18f9b7922923f287471e35"
|
||||||
|
dependencies = [
|
||||||
|
"crossbeam-utils",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "crossbeam-utils"
|
name = "crossbeam-utils"
|
||||||
version = "0.8.19"
|
version = "0.8.19"
|
||||||
|
@ -577,6 +665,17 @@ dependencies = [
|
||||||
"powerfmt",
|
"powerfmt",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "dircpy"
|
||||||
|
version = "0.3.16"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "29259db751c34980bfc44100875890c507f585323453b91936960ab1104272ca"
|
||||||
|
dependencies = [
|
||||||
|
"jwalk",
|
||||||
|
"log",
|
||||||
|
"walkdir",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "dirs"
|
name = "dirs"
|
||||||
version = "5.0.1"
|
version = "5.0.1"
|
||||||
|
@ -670,6 +769,12 @@ version = "1.9.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "a26ae43d7bcc3b814de94796a5e736d4029efb0ee900c12e2d54c993ad1a1e07"
|
checksum = "a26ae43d7bcc3b814de94796a5e736d4029efb0ee900c12e2d54c993ad1a1e07"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "encode_unicode"
|
||||||
|
version = "0.3.6"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "a357d28ed41a50f9c765dbfe56cbc04a64e53e5fc58ba79fbc34c10ef3df831f"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "encoding_rs"
|
name = "encoding_rs"
|
||||||
version = "0.8.33"
|
version = "0.8.33"
|
||||||
|
@ -1013,6 +1118,29 @@ dependencies = [
|
||||||
"tokio-io-timeout",
|
"tokio-io-timeout",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "iana-time-zone"
|
||||||
|
version = "0.1.60"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "e7ffbb5a1b541ea2561f8c41c087286cc091e21e556a4f09a8f6cbf17b69b141"
|
||||||
|
dependencies = [
|
||||||
|
"android_system_properties",
|
||||||
|
"core-foundation-sys",
|
||||||
|
"iana-time-zone-haiku",
|
||||||
|
"js-sys",
|
||||||
|
"wasm-bindgen",
|
||||||
|
"windows-core",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "iana-time-zone-haiku"
|
||||||
|
version = "0.1.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "f31827a206f56af32e590ba56d5d2d085f558508192593743f16b2306495269f"
|
||||||
|
dependencies = [
|
||||||
|
"cc",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "icrate"
|
name = "icrate"
|
||||||
version = "0.0.4"
|
version = "0.0.4"
|
||||||
|
@ -1056,6 +1184,19 @@ dependencies = [
|
||||||
"hashbrown 0.14.3",
|
"hashbrown 0.14.3",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "indicatif"
|
||||||
|
version = "0.17.8"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "763a5a8f45087d6bcea4222e7b72c291a054edf80e4ef6efd2a4979878c7bea3"
|
||||||
|
dependencies = [
|
||||||
|
"console",
|
||||||
|
"instant",
|
||||||
|
"number_prefix",
|
||||||
|
"portable-atomic",
|
||||||
|
"unicode-width",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "input"
|
name = "input"
|
||||||
version = "0.9.0"
|
version = "0.9.0"
|
||||||
|
@ -1075,6 +1216,15 @@ version = "1.18.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "bd4f5b4d1c00331c5245163aacfe5f20be75b564c7112d45893d4ae038119eb0"
|
checksum = "bd4f5b4d1c00331c5245163aacfe5f20be75b564c7112d45893d4ae038119eb0"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "instant"
|
||||||
|
version = "0.1.12"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "7a5bbe824c507c5da5956355e86a746d82e0e1464f65d862cc5e71da70e94b2c"
|
||||||
|
dependencies = [
|
||||||
|
"cfg-if",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "io-lifetimes"
|
name = "io-lifetimes"
|
||||||
version = "1.0.11"
|
version = "1.0.11"
|
||||||
|
@ -1147,6 +1297,16 @@ dependencies = [
|
||||||
"wasm-bindgen",
|
"wasm-bindgen",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "jwalk"
|
||||||
|
version = "0.8.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "2735847566356cd2179a2a38264839308f7079fa96e6bd5a42d740460e003c56"
|
||||||
|
dependencies = [
|
||||||
|
"crossbeam",
|
||||||
|
"rayon",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "khronos_api"
|
name = "khronos_api"
|
||||||
version = "3.1.0"
|
version = "3.1.0"
|
||||||
|
@ -1451,6 +1611,12 @@ dependencies = [
|
||||||
"syn",
|
"syn",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "number_prefix"
|
||||||
|
version = "0.4.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "830b246a0e5f20af87141b25c173cd1b609bd7779a4617d6ec582abaf90870f3"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "objc-sys"
|
name = "objc-sys"
|
||||||
version = "0.3.2"
|
version = "0.3.2"
|
||||||
|
@ -1503,6 +1669,15 @@ dependencies = [
|
||||||
"libredox 0.0.2",
|
"libredox 0.0.2",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "os_str_bytes"
|
||||||
|
version = "6.6.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "e2355d85b9a3786f481747ced0e0ff2ba35213a1f9bd406ed906554d7af805a1"
|
||||||
|
dependencies = [
|
||||||
|
"memchr",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "overload"
|
name = "overload"
|
||||||
version = "0.1.1"
|
version = "0.1.1"
|
||||||
|
@ -1586,7 +1761,10 @@ version = "0.0.1"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"anyhow",
|
"anyhow",
|
||||||
"bitflags 2.4.2",
|
"bitflags 2.4.2",
|
||||||
|
"chrono",
|
||||||
"clap",
|
"clap",
|
||||||
|
"cliclack",
|
||||||
|
"dircpy",
|
||||||
"image",
|
"image",
|
||||||
"nix",
|
"nix",
|
||||||
"pinnacle-api-defs",
|
"pinnacle-api-defs",
|
||||||
|
@ -1667,6 +1845,12 @@ dependencies = [
|
||||||
"windows-sys 0.52.0",
|
"windows-sys 0.52.0",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "portable-atomic"
|
||||||
|
version = "1.6.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "7170ef9988bc169ba16dd36a7fa041e5c4cbeb6a35b76d4c03daded371eae7c0"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "powerfmt"
|
name = "powerfmt"
|
||||||
version = "0.2.0"
|
version = "0.2.0"
|
||||||
|
@ -2023,7 +2207,9 @@ version = "3.1.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "da03fa3b94cc19e3ebfc88c4229c49d8f08cdbd1228870a45f0ffdf84988e14b"
|
checksum = "da03fa3b94cc19e3ebfc88c4229c49d8f08cdbd1228870a45f0ffdf84988e14b"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
|
"bstr",
|
||||||
"dirs",
|
"dirs",
|
||||||
|
"os_str_bytes",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
@ -2050,6 +2236,12 @@ version = "1.13.1"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "e6ecd384b10a64542d77071bd64bd7b231f4ed5940fba55e98c3de13824cf3d7"
|
checksum = "e6ecd384b10a64542d77071bd64bd7b231f4ed5940fba55e98c3de13824cf3d7"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "smawk"
|
||||||
|
version = "0.3.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "b7c388c1b5e93756d0c740965c41e8822f866621d41acbdf6336a6a168f8840c"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "smithay"
|
name = "smithay"
|
||||||
version = "0.3.0"
|
version = "0.3.0"
|
||||||
|
@ -2212,6 +2404,17 @@ dependencies = [
|
||||||
"windows-sys 0.52.0",
|
"windows-sys 0.52.0",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "textwrap"
|
||||||
|
version = "0.16.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "23d434d3f8967a09480fb04132ebe0a3e088c173e6d0ee7897abbdf4eab0f8b9"
|
||||||
|
dependencies = [
|
||||||
|
"smawk",
|
||||||
|
"unicode-linebreak",
|
||||||
|
"unicode-width",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "thiserror"
|
name = "thiserror"
|
||||||
version = "1.0.57"
|
version = "1.0.57"
|
||||||
|
@ -2565,12 +2768,24 @@ version = "1.0.12"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b"
|
checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "unicode-linebreak"
|
||||||
|
version = "0.1.5"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "3b09c83c3c29d37506a3e260c08c03743a6bb66a9cd432c6934ab501a190571f"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "unicode-segmentation"
|
name = "unicode-segmentation"
|
||||||
version = "1.11.0"
|
version = "1.11.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "d4c87d22b6e3f4a18d4d40ef354e97c90fcb14dd91d7dc0aa9d8a1172ebf7202"
|
checksum = "d4c87d22b6e3f4a18d4d40ef354e97c90fcb14dd91d7dc0aa9d8a1172ebf7202"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "unicode-width"
|
||||||
|
version = "0.1.11"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "e51733f11c9c4f72aa0c160008246859e340b00807569a0da0e7a1079b27ba85"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "unicode-xid"
|
name = "unicode-xid"
|
||||||
version = "0.2.4"
|
version = "0.2.4"
|
||||||
|
@ -3271,3 +3486,23 @@ dependencies = [
|
||||||
"quote",
|
"quote",
|
||||||
"syn",
|
"syn",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "zeroize"
|
||||||
|
version = "1.7.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "525b4ec142c6b68a2d10f01f7bbf6755599ca3f81ea53b8431b7dd348f5fdb2d"
|
||||||
|
dependencies = [
|
||||||
|
"zeroize_derive",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "zeroize_derive"
|
||||||
|
version = "1.4.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "ce36e65b0d2999d2aafac989fb249189a141aee1f53c612c1f37d72631959f69"
|
||||||
|
dependencies = [
|
||||||
|
"proc-macro2",
|
||||||
|
"quote",
|
||||||
|
"syn",
|
||||||
|
]
|
||||||
|
|
43
Cargo.toml
43
Cargo.toml
|
@ -7,20 +7,21 @@ edition = "2021"
|
||||||
repository = "https://github.com/pinnacle-comp/pinnacle/"
|
repository = "https://github.com/pinnacle-comp/pinnacle/"
|
||||||
|
|
||||||
[workspace.dependencies]
|
[workspace.dependencies]
|
||||||
|
# Tokio
|
||||||
tokio = { version = "1.36.0", features = ["macros", "rt-multi-thread"]}
|
tokio = { version = "1.36.0", features = ["macros", "rt-multi-thread"]}
|
||||||
tokio-stream = { version = "0.1.14", features = ["net"] }
|
tokio-stream = { version = "0.1.14", features = ["net"] }
|
||||||
|
# gRPC
|
||||||
prost = "0.12.3"
|
prost = "0.12.3"
|
||||||
tonic = "0.11.0"
|
tonic = "0.11.0"
|
||||||
tonic-reflection = "0.11.0"
|
tonic-reflection = "0.11.0"
|
||||||
tonic-build = "0.11.0"
|
tonic-build = "0.11.0"
|
||||||
|
# API definitions
|
||||||
pinnacle-api-defs = { path = "./pinnacle-api-defs" }
|
pinnacle-api-defs = { path = "./pinnacle-api-defs" }
|
||||||
|
# Misc.
|
||||||
xkbcommon = "0.7.0"
|
xkbcommon = "0.7.0"
|
||||||
xdg = "2.5.2"
|
xdg = "2.5.2"
|
||||||
|
|
||||||
#################################################################################
|
########################################################################yo😎###########
|
||||||
|
|
||||||
[package]
|
[package]
|
||||||
name = "pinnacle"
|
name = "pinnacle"
|
||||||
|
@ -34,38 +35,42 @@ repository.workspace = true
|
||||||
keywords = ["wayland", "compositor", "smithay", "lua"]
|
keywords = ["wayland", "compositor", "smithay", "lua"]
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
|
# Smithay
|
||||||
smithay = { git = "https://github.com/Smithay/smithay", rev = "418190e", default-features = false, features = ["desktop", "wayland_frontend"] }
|
smithay = { git = "https://github.com/Smithay/smithay", rev = "418190e", default-features = false, features = ["desktop", "wayland_frontend"] }
|
||||||
smithay-drm-extras = { git = "https://github.com/Smithay/smithay", rev = "418190e" }
|
smithay-drm-extras = { git = "https://github.com/Smithay/smithay", rev = "418190e" }
|
||||||
|
# Tracing
|
||||||
tracing = "0.1.40"
|
tracing = "0.1.40"
|
||||||
tracing-subscriber = { version = "0.3.18", features = ["env-filter", "registry"] }
|
tracing-subscriber = { version = "0.3.18", features = ["env-filter", "registry"] }
|
||||||
tracing-appender = "0.2.3"
|
tracing-appender = "0.2.3"
|
||||||
|
# Errors
|
||||||
anyhow = { version = "1.0.79", features = ["backtrace"] }
|
anyhow = { version = "1.0.79", features = ["backtrace"] }
|
||||||
thiserror = "1.0.57"
|
thiserror = "1.0.57"
|
||||||
|
# xcursor stuff
|
||||||
xcursor = { version = "0.3.5" }
|
xcursor = { version = "0.3.5" }
|
||||||
image = { version = "0.24.8", default-features = false }
|
image = { version = "0.24.8", default-features = false }
|
||||||
|
# gRPC
|
||||||
|
prost = { workspace = true }
|
||||||
|
tonic = { workspace = true }
|
||||||
|
tonic-reflection = { workspace = true }
|
||||||
|
# Tokio
|
||||||
|
tokio = { workspace = true, features = ["process", "io-util", "signal"] }
|
||||||
|
tokio-stream = { workspace = true }
|
||||||
|
# CLI
|
||||||
|
clap = { version = "4.5.1", features = ["derive"] }
|
||||||
|
cliclack = "0.1.13"
|
||||||
|
# Misc.
|
||||||
|
bitflags = "2.4.2"
|
||||||
serde = { version = "1.0.196", features = ["derive"] }
|
serde = { version = "1.0.196", features = ["derive"] }
|
||||||
toml = "0.8.10"
|
toml = "0.8.10"
|
||||||
shellexpand = "3.1.0"
|
shellexpand = { version = "3.1.0", features = ["path"] }
|
||||||
clap = { version = "4.5.1", features = ["derive"] }
|
|
||||||
x11rb = { version = "0.13.0", default-features = false, features = ["composite"] }
|
x11rb = { version = "0.13.0", default-features = false, features = ["composite"] }
|
||||||
xkbcommon = { workspace = true }
|
xkbcommon = { workspace = true }
|
||||||
xdg = { workspace = true }
|
xdg = { workspace = true }
|
||||||
sysinfo = "0.30.5"
|
sysinfo = "0.30.5"
|
||||||
nix = { version = "0.27.1", features = ["user", "resource"] }
|
nix = { version = "0.27.1", features = ["user", "resource"] }
|
||||||
|
|
||||||
prost = { workspace = true }
|
|
||||||
tonic = { workspace = true }
|
|
||||||
tonic-reflection = { workspace = true }
|
|
||||||
|
|
||||||
tokio = { workspace = true, features = ["process", "io-util", "signal"] }
|
|
||||||
tokio-stream = { workspace = true }
|
|
||||||
|
|
||||||
bitflags = "2.4.2"
|
|
||||||
pinnacle-api-defs = { workspace = true }
|
pinnacle-api-defs = { workspace = true }
|
||||||
|
dircpy = "0.3.16"
|
||||||
|
chrono = "0.4.34"
|
||||||
|
|
||||||
[build-dependencies]
|
[build-dependencies]
|
||||||
xdg = { workspace = true }
|
xdg = { workspace = true }
|
||||||
|
|
109
README.md
109
README.md
|
@ -13,10 +13,11 @@ https://github.com/Ottatop/pinnacle/assets/120758733/c175ba80-9796-4759-92c3-1d7
|
||||||
- [Configuration](#configuration)
|
- [Configuration](#configuration)
|
||||||
- [Out-of-the-box configurations](#out-of-the-box-configurations)
|
- [Out-of-the-box configurations](#out-of-the-box-configurations)
|
||||||
- [Custom configuration](#custom-configuration)
|
- [Custom configuration](#custom-configuration)
|
||||||
- [Lua](#lua)
|
- [Generating a config](#generating-a-config)
|
||||||
- [Lua Language Server completion](#lua-language-server-completion)
|
- [More on configuration and the `metaconfig.toml` file](#more-on-configuration-and-the-metaconfig.toml-file)
|
||||||
- [Rust](#rust)
|
- [The `metaconfig.toml` file](#the-metaconfig.toml-file)
|
||||||
- [API References](#api-references)
|
- [Lua Language Server completion](#lua-language-server-completion)
|
||||||
|
- [API references](#api-references)
|
||||||
- [Controls](#controls)
|
- [Controls](#controls)
|
||||||
- [Feature Requests, Bug Reports, Contributions, and Questions](#feature-requests-bug-reports-contributions-and-questions)
|
- [Feature Requests, Bug Reports, Contributions, and Questions](#feature-requests-bug-reports-contributions-and-questions)
|
||||||
- [Changelog](#changelog)
|
- [Changelog](#changelog)
|
||||||
|
@ -27,8 +28,6 @@ Pinnacle is a Wayland compositor built in Rust using [Smithay](https://github.co
|
||||||
It's my attempt at creating something like [AwesomeWM](https://github.com/awesomeWM/awesome)
|
It's my attempt at creating something like [AwesomeWM](https://github.com/awesomeWM/awesome)
|
||||||
for Wayland.
|
for Wayland.
|
||||||
|
|
||||||
It can be configured through either Lua or Rust.
|
|
||||||
|
|
||||||
> ### More video examples below!
|
> ### More video examples below!
|
||||||
> <details>
|
> <details>
|
||||||
>
|
>
|
||||||
|
@ -96,7 +95,9 @@ cargo build [--release]
|
||||||
> [!NOTE]
|
> [!NOTE]
|
||||||
> On build, [`build.rs`](build.rs) will:
|
> On build, [`build.rs`](build.rs) will:
|
||||||
> - Copy Protobuf definition files to `$XDG_DATA_HOME/pinnacle/protobuf`
|
> - Copy Protobuf definition files to `$XDG_DATA_HOME/pinnacle/protobuf`
|
||||||
> - Copy the [default config](api/lua/examples/default) to `$XDG_DATA_HOME/pinnacle/default_config`
|
> - Copy the [Lua default config](api/lua/examples/default) and
|
||||||
|
> [Rust default config](api/rust/examples/default_config/for_copying) to
|
||||||
|
> `$XDG_DATA_HOME/pinnacle/default_config/{lua,rust}`
|
||||||
> - `cd` into [`api/lua`](api/lua) and run `luarocks make` to install the Lua library to `~/.luarocks/share/lua/5.4`
|
> - `cd` into [`api/lua`](api/lua) and run `luarocks make` to install the Lua library to `~/.luarocks/share/lua/5.4`
|
||||||
|
|
||||||
# Running
|
# Running
|
||||||
|
@ -122,14 +123,13 @@ Pinnacle is configured in your choice of Lua or Rust.
|
||||||
## Out-of-the-box configurations
|
## Out-of-the-box configurations
|
||||||
If you just want to test Pinnacle out without copying stuff to your config directory,
|
If you just want to test Pinnacle out without copying stuff to your config directory,
|
||||||
run one of the following in the crate root:
|
run one of the following in the crate root:
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
# For a Lua configuration
|
# For a Lua configuration
|
||||||
PINNACLE_CONFIG_DIR="./api/lua/examples/default" cargo run
|
cargo run -- -c "./api/lua/examples/default"
|
||||||
# This should also have been copied to the directory below, so below will do the same
|
|
||||||
PINNACLE_CONFIG_DIR="~/.local/share/pinnacle/default_config" cargo run
|
|
||||||
|
|
||||||
# For a Rust configuration
|
# For a Rust configuration
|
||||||
PINNACLE_CONFIG_DIR="./api/rust/examples/default_config" cargo run
|
cargo run -- -c "./api/rust/examples/default_config"
|
||||||
```
|
```
|
||||||
|
|
||||||
## Custom configuration
|
## Custom configuration
|
||||||
|
@ -137,58 +137,65 @@ PINNACLE_CONFIG_DIR="./api/rust/examples/default_config" cargo run
|
||||||
> [!IMPORTANT]
|
> [!IMPORTANT]
|
||||||
> Pinnacle is under development, and there *will* be major breaking changes to these APIs
|
> Pinnacle is under development, and there *will* be major breaking changes to these APIs
|
||||||
> until I release version 0.1, at which point there will be an API stability spec in place.
|
> until I release version 0.1, at which point there will be an API stability spec in place.
|
||||||
>
|
|
||||||
> Until then, I recommend you either use the out-of-the-box configs above or prepare for
|
|
||||||
> your config to break every now and then.
|
|
||||||
|
|
||||||
Pinnacle will search for a `metaconfig.toml` file in the following directories, from top to bottom:
|
### Generating a config
|
||||||
|
|
||||||
|
Run the following command to open up the interactive config generator:
|
||||||
```sh
|
```sh
|
||||||
$PINNACLE_CONFIG_DIR
|
cargo run -- config gen
|
||||||
$XDG_CONFIG_HOME/pinnacle
|
|
||||||
~/.config/pinnacle # Only if $XDG_CONFIG_HOME is not defined
|
|
||||||
```
|
```
|
||||||
|
|
||||||
The `metaconfig.toml` file provides information on what config to run, kill and reload keybinds,
|
This will prompt you to choose a language (Lua or Rust) and directory to put the config in.
|
||||||
and any environment variables you want set. For more details, see the provided
|
It will then generate a config at that directory. If Lua is chosen and there are conflicting
|
||||||
[`metaconfig.toml`](api/lua/examples/default/metaconfig.toml) file.
|
files in the directory, the generator will prompt to rename them to a backup before continuing.
|
||||||
|
If Rust is chosen, the directory must be manually emptied to continue.
|
||||||
|
|
||||||
If no `metaconfig.toml` file is found, the default Lua config will be loaded.
|
Run `cargo run -- config gen --help` for information on the command.
|
||||||
|
|
||||||
### Lua
|
## More on configuration and the `metaconfig.toml` file
|
||||||
For custom configuration in Lua, copy the contents of `~/.local/share/pinnacle/default_config` to
|
Pinnacle is configured purely through IPC using [gRPC](https://grpc.io/). This is done through
|
||||||
`~/.config/pinnacle` (or `$XDG_CONFIG_HOME/pinnacle`):
|
configuration clients that use the [Lua](api/lua) and [Rust](api/rust) interface libraries.
|
||||||
```sh
|
|
||||||
mkdir ~/.config/pinnacle
|
|
||||||
cp -r ~/.local/share/pinnacle/default_config/. ~/.config/pinnacle
|
|
||||||
```
|
|
||||||
Note: there is a `.luarc.json` file that may not get copied if you do `cp <path>/* ~/.config/pinnacle`.
|
|
||||||
The above command takes that into account.
|
|
||||||
|
|
||||||
> If you rename `default_config.lua`, make sure `command` in your `metaconfig.toml` is updated to reflect that.
|
As the compositor has no direct integration with these clients, it must know what it needs to run
|
||||||
> If it isn't, the compositor will load the default config instead.
|
through a separate file, aptly called the `metaconfig.toml` file.
|
||||||
|
|
||||||
#### Lua Language Server completion
|
To start a config, Pinnacle will search for a `metaconfig.toml` file in the first directory
|
||||||
|
that exists from the following:
|
||||||
|
|
||||||
|
1. The directory passed in through `--config-dir`/`-c`
|
||||||
|
2. `$PINNACLE_CONFIG_DIR`
|
||||||
|
3. `$XDG_CONFIG_HOME/pinnacle`
|
||||||
|
4. `~/.config/pinnacle` if $XDG_CONFIG_HOME is not defined
|
||||||
|
|
||||||
|
If there is no `metaconfig.toml` file in that directory, Pinnacle will start the default Lua config
|
||||||
|
at `$XDG_DATA_HOME/pinnacle/default_config/lua` (typically `~/.local/share/pinnacle/default_config/lua`).
|
||||||
|
|
||||||
|
Additionally, if your config crashes, Pinnacle will also start the default Lua config.
|
||||||
|
|
||||||
|
> [!NOTE]
|
||||||
|
> If you have not run `eval $(luarocks path --lua-version 5.4)`, Pinnacle will go into an endless loop of
|
||||||
|
> starting the default Lua config only for it to crash because it can't find the Lua library.
|
||||||
|
|
||||||
|
### The `metaconfig.toml` file
|
||||||
|
A `metaconfig.toml` file must contain the following entries:
|
||||||
|
- `command`: An array denoting the program and arguments Pinnacle will run to start a config.
|
||||||
|
- `reload_keybind`: A table denoting a keybind that Pinnacle will hardcode to restart your config.
|
||||||
|
- `kill_keybind`: A table denoting a keybind that Pinnacle will hardcode to quit the compositor.
|
||||||
|
- The two keybinds above prevent you from getting locked in the compositor if the default config fails to start.
|
||||||
|
|
||||||
|
It also has the following optional entries:
|
||||||
|
- `socket_dir`: A directory that Pinnacle will place its IPC socket in (this defaults to `$XDG_RUNTIME_DIR`,
|
||||||
|
falling back to `/tmp` if that doesn't exist).
|
||||||
|
- `[envs]`: A table of environment variables that Pinnacle will start the config with.
|
||||||
|
|
||||||
|
For the specifics, see the default [`metaconfig.toml`](api/lua/examples/default/metaconfig.toml) file.
|
||||||
|
|
||||||
|
## Lua Language Server completion
|
||||||
A [`.luarc.json`](api/lua/examples/default/.luarc.json) file is included with the default Lua config
|
A [`.luarc.json`](api/lua/examples/default/.luarc.json) file is included with the default Lua config
|
||||||
and will set the correct workspace library files for use with the
|
and will set the correct workspace library files for use with the
|
||||||
[Lua language server](https://github.com/LuaLS/lua-language-server).
|
[Lua language server](https://github.com/LuaLS/lua-language-server).
|
||||||
|
|
||||||
### Rust
|
## API references
|
||||||
If you want to use Rust to configure Pinnacle, follow these steps:
|
|
||||||
1. In `~/.config/pinnacle`, run `cargo init`.
|
|
||||||
2. In the `Cargo.toml` file, add the following under `[dependencies]`:
|
|
||||||
```toml
|
|
||||||
pinnacle-api = { git = "http://github.com/pinnacle-comp/pinnacle" }
|
|
||||||
```
|
|
||||||
3. Create the file `metaconfig.toml` at the root. Add the following to the file:
|
|
||||||
```toml
|
|
||||||
command = ["cargo", "run"]
|
|
||||||
reload_keybind = { modifiers = ["Ctrl", "Alt"], key = "r" }
|
|
||||||
kill_keybind = { modifiers = ["Ctrl", "Alt", "Shift"], key = "escape" }
|
|
||||||
```
|
|
||||||
4. Copy the [default config](api/rust/examples/default_config/main.rs) to `src/main.rs`.
|
|
||||||
5. Run Pinnacle! (You may want to run `cargo build` beforehand so you don't have to wait for your config to compile.)
|
|
||||||
|
|
||||||
### API References
|
|
||||||
<b>Lua: https://pinnacle-comp.github.io/lua-reference/main.<br>
|
<b>Lua: https://pinnacle-comp.github.io/lua-reference/main.<br>
|
||||||
Rust: https://pinnacle-comp.github.io/rust-reference/main.</b>
|
Rust: https://pinnacle-comp.github.io/rust-reference/main.</b>
|
||||||
|
|
||||||
|
|
7
api/rust/examples/default_config/for_copying/Cargo.toml
Normal file
7
api/rust/examples/default_config/for_copying/Cargo.toml
Normal file
|
@ -0,0 +1,7 @@
|
||||||
|
[package]
|
||||||
|
name = "pinnacle-config"
|
||||||
|
version = "0.1.0"
|
||||||
|
edition = "2021"
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
pinnacle-api = { git = "http://github.com/pinnacle-comp/pinnacle" }
|
46
api/rust/examples/default_config/for_copying/metaconfig.toml
Normal file
46
api/rust/examples/default_config/for_copying/metaconfig.toml
Normal file
|
@ -0,0 +1,46 @@
|
||||||
|
# This metaconfig.toml file dictates what config Pinnacle will run.
|
||||||
|
#
|
||||||
|
# When running Pinnacle, the compositor will look in the following directories for a metaconfig.toml file,
|
||||||
|
# in order from top to bottom:
|
||||||
|
# $PINNACLE_CONFIG_DIR
|
||||||
|
# $XDG_CONFIG_HOME/pinnacle/
|
||||||
|
# ~/.config/pinnacle/
|
||||||
|
#
|
||||||
|
# When Pinnacle finds a metaconfig.toml file, it will execute the command provided to `command`.
|
||||||
|
# To use a Rust config, this should be changed to something like ["cargo", "run"].
|
||||||
|
#
|
||||||
|
# Because configuration is done using an external process, if it ever crashes, you lose all of your keybinds.
|
||||||
|
# The compositor will load the default config if that happens, but in the event that you don't have
|
||||||
|
# the necessary dependencies for it to run, you may get softlocked.
|
||||||
|
# In order prevent you from getting stuck in the compositor, you must define keybinds to reload your config
|
||||||
|
# and kill Pinnacle.
|
||||||
|
#
|
||||||
|
# More details on each setting can be found below.
|
||||||
|
|
||||||
|
# The command Pinnacle will run on startup and when you reload your config.
|
||||||
|
# Paths are relative to the directory the metaconfig.toml file is in.
|
||||||
|
# This must be an array.
|
||||||
|
command = ["cargo", "run"]
|
||||||
|
|
||||||
|
### Keybinds ###
|
||||||
|
# Each keybind takes in a table with two fields: `modifiers` and `key`.
|
||||||
|
# - `modifiers` can be one of "Ctrl", "Alt", "Shift", or "Super".
|
||||||
|
# - `key` can be a string of any lowercase letter, number,
|
||||||
|
# "numN" where N is a number for numpad keys, or "esc"/"escape".
|
||||||
|
# Support for any xkbcommon key is planned for a future update.
|
||||||
|
|
||||||
|
# The keybind that will reload your config.
|
||||||
|
reload_keybind = { modifiers = ["Ctrl", "Alt"], key = "r" }
|
||||||
|
# The keybind that will kill Pinnacle.
|
||||||
|
kill_keybind = { modifiers = ["Ctrl", "Alt", "Shift"], key = "escape" }
|
||||||
|
|
||||||
|
### Socket directory ###
|
||||||
|
# Pinnacle will open a Unix socket at `$XDG_RUNTIME_DIR` by default, falling back to `/tmp` if it doesn't exist.
|
||||||
|
# If you want/need to change this, use the `socket_dir` setting set to the directory of your choosing.
|
||||||
|
#
|
||||||
|
# socket_dir = "/your/dir/here/"
|
||||||
|
|
||||||
|
### Environment Variables ###
|
||||||
|
# If you need to spawn your config with any environment variables, list them here.
|
||||||
|
[envs]
|
||||||
|
# key = "value"
|
1
api/rust/examples/default_config/for_copying/src/main.rs
Symbolic link
1
api/rust/examples/default_config/for_copying/src/main.rs
Symbolic link
|
@ -0,0 +1 @@
|
||||||
|
../../main.rs
|
67
build.rs
67
build.rs
|
@ -1,4 +1,6 @@
|
||||||
fn main() {
|
use std::process::Command;
|
||||||
|
|
||||||
|
fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||||
println!("cargo:rerun-if-changed=api/lua");
|
println!("cargo:rerun-if-changed=api/lua");
|
||||||
println!("cargo:rerun-if-changed=api/protocol");
|
println!("cargo:rerun-if-changed=api/protocol");
|
||||||
|
|
||||||
|
@ -6,51 +8,60 @@ fn main() {
|
||||||
|
|
||||||
let proto_dir = xdg.place_data_file("protobuf").unwrap();
|
let proto_dir = xdg.place_data_file("protobuf").unwrap();
|
||||||
let default_config_dir = xdg.place_data_file("default_config").unwrap();
|
let default_config_dir = xdg.place_data_file("default_config").unwrap();
|
||||||
|
let default_lua_config_dir = default_config_dir.join("lua");
|
||||||
|
let default_rust_config_dir = default_config_dir.join("rust");
|
||||||
|
|
||||||
let remove_protos = format!("rm -r {proto_dir:?}");
|
let remove_protos = format!("rm -r {proto_dir:?}");
|
||||||
let copy_protos = format!("cp -r ./api/protocol {proto_dir:?}");
|
let copy_protos = format!("cp -r ./api/protocol {proto_dir:?}");
|
||||||
|
|
||||||
let remove_default_config = format!("rm -r {default_config_dir:?}");
|
let remove_default_config_dir = format!("rm -r {default_config_dir:?}");
|
||||||
let copy_default_config = format!("cp -r ./api/lua/examples/default {default_config_dir:?}");
|
|
||||||
|
|
||||||
std::process::Command::new("/bin/sh")
|
let copy_default_lua_config =
|
||||||
|
format!("cp -r ./api/lua/examples/default {default_lua_config_dir:?}");
|
||||||
|
|
||||||
|
let copy_default_rust_config = format!(
|
||||||
|
"cp -LR ./api/rust/examples/default_config/for_copying {default_rust_config_dir:?}"
|
||||||
|
);
|
||||||
|
|
||||||
|
Command::new("/bin/sh")
|
||||||
.arg("-c")
|
.arg("-c")
|
||||||
.arg(&remove_protos)
|
.arg(&remove_protos)
|
||||||
.spawn()
|
.spawn()?
|
||||||
.unwrap()
|
.wait()?;
|
||||||
.wait()
|
|
||||||
.unwrap();
|
|
||||||
|
|
||||||
std::process::Command::new("/bin/sh")
|
Command::new("/bin/sh")
|
||||||
.arg("-c")
|
.arg("-c")
|
||||||
.arg(©_protos)
|
.arg(©_protos)
|
||||||
.spawn()
|
.spawn()?
|
||||||
.unwrap()
|
.wait()?;
|
||||||
.wait()
|
|
||||||
.unwrap();
|
|
||||||
|
|
||||||
std::process::Command::new("/bin/sh")
|
Command::new("/bin/sh")
|
||||||
.arg("-c")
|
.arg("-c")
|
||||||
.arg(&remove_default_config)
|
.arg(&remove_default_config_dir)
|
||||||
.spawn()
|
.spawn()?
|
||||||
.unwrap()
|
.wait()?;
|
||||||
.wait()
|
|
||||||
.unwrap();
|
|
||||||
|
|
||||||
std::process::Command::new("/bin/sh")
|
std::fs::create_dir_all(&default_config_dir)?;
|
||||||
|
|
||||||
|
Command::new("/bin/sh")
|
||||||
.arg("-c")
|
.arg("-c")
|
||||||
.arg(©_default_config)
|
.arg(©_default_lua_config)
|
||||||
.spawn()
|
.spawn()?
|
||||||
.unwrap()
|
.wait()?;
|
||||||
.wait()
|
|
||||||
.unwrap();
|
Command::new("/bin/sh")
|
||||||
|
.arg("-c")
|
||||||
|
.arg(©_default_rust_config)
|
||||||
|
.spawn()?
|
||||||
|
.wait()?;
|
||||||
|
|
||||||
std::env::set_current_dir("api/lua").unwrap();
|
std::env::set_current_dir("api/lua").unwrap();
|
||||||
std::process::Command::new("luarocks")
|
Command::new("luarocks")
|
||||||
.arg("make")
|
.arg("make")
|
||||||
.arg("--local")
|
.arg("--local")
|
||||||
.spawn()
|
.spawn()
|
||||||
.expect("Luarocks is not installed")
|
.expect("Luarocks is not installed")
|
||||||
.wait()
|
.wait()?;
|
||||||
.unwrap();
|
|
||||||
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,7 +3,7 @@
|
||||||
use std::{
|
use std::{
|
||||||
collections::{HashMap, HashSet},
|
collections::{HashMap, HashSet},
|
||||||
ffi::OsString,
|
ffi::OsString,
|
||||||
path::Path,
|
path::{Path, PathBuf},
|
||||||
time::Duration,
|
time::Duration,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -236,7 +236,7 @@ impl BackendData for Udev {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn run_udev() -> anyhow::Result<()> {
|
pub fn run_udev(no_config: bool, config_dir: Option<PathBuf>) -> anyhow::Result<()> {
|
||||||
let mut event_loop = EventLoop::try_new()?;
|
let mut event_loop = EventLoop::try_new()?;
|
||||||
let display = Display::new()?;
|
let display = Display::new()?;
|
||||||
|
|
||||||
|
@ -283,6 +283,8 @@ pub fn run_udev() -> anyhow::Result<()> {
|
||||||
display,
|
display,
|
||||||
event_loop.get_signal(),
|
event_loop.get_signal(),
|
||||||
event_loop.handle(),
|
event_loop.handle(),
|
||||||
|
no_config,
|
||||||
|
config_dir,
|
||||||
)?;
|
)?;
|
||||||
|
|
||||||
// Initialize the udev backend
|
// Initialize the udev backend
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
|
||||||
use std::{ffi::OsString, time::Duration};
|
use std::{ffi::OsString, path::PathBuf, time::Duration};
|
||||||
|
|
||||||
use smithay::{
|
use smithay::{
|
||||||
backend::{
|
backend::{
|
||||||
|
@ -62,7 +62,7 @@ impl Backend {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Start Pinnacle as a window in a graphical environment.
|
/// Start Pinnacle as a window in a graphical environment.
|
||||||
pub fn run_winit() -> anyhow::Result<()> {
|
pub fn run_winit(no_config: bool, config_dir: Option<PathBuf>) -> anyhow::Result<()> {
|
||||||
let mut event_loop: EventLoop<State> = EventLoop::try_new()?;
|
let mut event_loop: EventLoop<State> = EventLoop::try_new()?;
|
||||||
|
|
||||||
let display: Display<State> = Display::new()?;
|
let display: Display<State> = Display::new()?;
|
||||||
|
@ -153,16 +153,20 @@ pub fn run_winit() -> anyhow::Result<()> {
|
||||||
tracing::info!("EGL hardware-acceleration enabled");
|
tracing::info!("EGL hardware-acceleration enabled");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let backend = Backend::Winit(Winit {
|
||||||
|
backend: winit_backend,
|
||||||
|
damage_tracker: OutputDamageTracker::from_output(&output),
|
||||||
|
dmabuf_state,
|
||||||
|
full_redraw: 0,
|
||||||
|
});
|
||||||
|
|
||||||
let mut state = State::init(
|
let mut state = State::init(
|
||||||
Backend::Winit(Winit {
|
backend,
|
||||||
backend: winit_backend,
|
|
||||||
damage_tracker: OutputDamageTracker::from_output(&output),
|
|
||||||
dmabuf_state,
|
|
||||||
full_redraw: 0,
|
|
||||||
}),
|
|
||||||
display,
|
display,
|
||||||
event_loop.get_signal(),
|
event_loop.get_signal(),
|
||||||
evt_loop_handle,
|
evt_loop_handle,
|
||||||
|
no_config,
|
||||||
|
config_dir,
|
||||||
)?;
|
)?;
|
||||||
|
|
||||||
state.output_focus_stack.set_focus(output.clone());
|
state.output_focus_stack.set_focus(output.clone());
|
||||||
|
|
567
src/cli.rs
Normal file
567
src/cli.rs
Normal file
|
@ -0,0 +1,567 @@
|
||||||
|
use std::{io::IsTerminal, path::PathBuf};
|
||||||
|
|
||||||
|
use clap::{Parser, ValueHint};
|
||||||
|
use tracing::{error, warn};
|
||||||
|
|
||||||
|
/// Valid backends that Pinnacle can run.
|
||||||
|
#[derive(clap::ValueEnum, Debug, Clone, Copy)]
|
||||||
|
pub enum Backend {
|
||||||
|
/// Run Pinnacle in a window in your graphical environment
|
||||||
|
Winit,
|
||||||
|
/// Run Pinnacle from a tty
|
||||||
|
Udev,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// The main CLI struct.
|
||||||
|
#[derive(clap::Parser, Debug)]
|
||||||
|
#[command(author, version, about, long_about = None)]
|
||||||
|
pub struct Cli {
|
||||||
|
/// Start Pinnacle with the config at this directory
|
||||||
|
#[arg(short, long, value_name("DIR"), value_hint(ValueHint::DirPath))]
|
||||||
|
pub config_dir: Option<PathBuf>,
|
||||||
|
|
||||||
|
/// Run Pinnacle with the specified backend
|
||||||
|
///
|
||||||
|
/// This is usually not necessary, but if your environment variables are mucked up
|
||||||
|
/// then this can be used to choose a backend.
|
||||||
|
#[arg(short, long)]
|
||||||
|
pub backend: Option<Backend>,
|
||||||
|
|
||||||
|
/// Force Pinnacle to run with the provided backend
|
||||||
|
#[arg(long, requires = "backend")]
|
||||||
|
pub force: bool,
|
||||||
|
|
||||||
|
/// Allow running Pinnacle as root (this is NOT recommended)
|
||||||
|
#[arg(long)]
|
||||||
|
pub allow_root: bool,
|
||||||
|
|
||||||
|
/// Start Pinnacle without a config
|
||||||
|
///
|
||||||
|
/// This is meant to be used for debugging.
|
||||||
|
/// Additionally, Pinnacle will not load the
|
||||||
|
/// default config if a manually spawned one
|
||||||
|
/// crashes or exits.
|
||||||
|
#[arg(long)]
|
||||||
|
pub no_config: bool,
|
||||||
|
|
||||||
|
/// Cli subcommands
|
||||||
|
#[command(subcommand)]
|
||||||
|
subcommand: Option<CliSubcommand>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Cli {
|
||||||
|
pub fn parse_and_prompt() -> Option<Self> {
|
||||||
|
let mut cli = Cli::parse();
|
||||||
|
|
||||||
|
// oh my god rustfmt is starting to piss me off
|
||||||
|
|
||||||
|
cli.config_dir = cli.config_dir.and_then(|dir| {
|
||||||
|
let new_dir = shellexpand::path::full(&dir);
|
||||||
|
match new_dir {
|
||||||
|
Ok(new_dir) => Some(new_dir.to_path_buf()),
|
||||||
|
Err(err) => {
|
||||||
|
warn!("Could not shellexpand `--config-dir`'s argument: {err}; unsetting `--config-dir`");
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
if let Some(subcommand) = &cli.subcommand {
|
||||||
|
match subcommand {
|
||||||
|
CliSubcommand::Config(ConfigSubcommand::Gen(config_gen)) => {
|
||||||
|
if let Err(err) = generate_config(config_gen.clone()) {
|
||||||
|
error!("Error generating config: {err}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
|
||||||
|
Some(cli)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Cli subcommands.
|
||||||
|
#[derive(clap::Subcommand, Debug)]
|
||||||
|
enum CliSubcommand {
|
||||||
|
/// Commands dealing with configuration
|
||||||
|
#[command(subcommand)]
|
||||||
|
Config(ConfigSubcommand),
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Config subcommands
|
||||||
|
#[derive(clap::Subcommand, Debug)]
|
||||||
|
enum ConfigSubcommand {
|
||||||
|
/// Generate a config
|
||||||
|
///
|
||||||
|
/// If not all flags are provided, this will launch an
|
||||||
|
/// interactive prompt unless `--non-interactive` is passed
|
||||||
|
/// or this is run in a non-interactive shell.
|
||||||
|
Gen(ConfigGen),
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Config arguments.
|
||||||
|
#[derive(clap::Args, Debug, Clone, PartialEq)]
|
||||||
|
struct ConfigGen {
|
||||||
|
/// Generate a config in a specific language
|
||||||
|
#[arg(short, long)]
|
||||||
|
pub lang: Option<Lang>,
|
||||||
|
|
||||||
|
/// Generate a config at this directory
|
||||||
|
#[arg(short, long, value_hint(ValueHint::DirPath))]
|
||||||
|
pub dir: Option<PathBuf>,
|
||||||
|
|
||||||
|
/// Do not show interactive prompts if both `--lang` and `--dir` are set
|
||||||
|
///
|
||||||
|
/// This does nothing inside of non-interactive shells.
|
||||||
|
#[arg(
|
||||||
|
short,
|
||||||
|
long,
|
||||||
|
requires("lang"),
|
||||||
|
requires("dir"),
|
||||||
|
default_value_t = !std::io::stdout().is_terminal()
|
||||||
|
)]
|
||||||
|
pub non_interactive: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Possible languages for configuration.
|
||||||
|
#[derive(clap::ValueEnum, Debug, Clone, Copy, PartialEq, Eq)]
|
||||||
|
enum Lang {
|
||||||
|
/// Generate a Lua config
|
||||||
|
Lua,
|
||||||
|
/// Generate a Rust config
|
||||||
|
Rust,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl std::fmt::Display for Lang {
|
||||||
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
|
write!(f, "{self:?}")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
|
/// Generate a new config.
|
||||||
|
///
|
||||||
|
/// If `--non-interactive` is passed or the shell is non-interactive, this will not
|
||||||
|
/// output interactive prompts.
|
||||||
|
fn generate_config(args: ConfigGen) -> anyhow::Result<()> {
|
||||||
|
let interactive = !args.non_interactive;
|
||||||
|
|
||||||
|
if !interactive && (args.lang.is_none() || args.dir.is_none()) {
|
||||||
|
eprintln!("Error: both `--lang` and `--dir` must be set in a non-interactive shell.");
|
||||||
|
return Ok(());
|
||||||
|
}
|
||||||
|
|
||||||
|
if interactive {
|
||||||
|
cliclack::intro("Welcome to the interactive config generator!")?;
|
||||||
|
tokio::spawn(async {
|
||||||
|
tokio::signal::ctrl_c()
|
||||||
|
.await
|
||||||
|
.expect("failed to listen for ctrl-c");
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
enum Level {
|
||||||
|
Info,
|
||||||
|
Success,
|
||||||
|
}
|
||||||
|
|
||||||
|
let message = |msg: &str, level: Level| -> anyhow::Result<()> {
|
||||||
|
if interactive {
|
||||||
|
Ok(match level {
|
||||||
|
Level::Info => cliclack::log::info(msg),
|
||||||
|
Level::Success => cliclack::log::success(msg),
|
||||||
|
}?)
|
||||||
|
} else {
|
||||||
|
println!("{msg}");
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
let exit_message = |msg: &str| {
|
||||||
|
if interactive {
|
||||||
|
cliclack::outro_cancel(msg).expect("failed to display outro_cancel");
|
||||||
|
} else {
|
||||||
|
eprintln!("{msg}, exiting");
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
let lang = match args.lang {
|
||||||
|
Some(lang) => {
|
||||||
|
let msg = format!("Select a language:\n{lang} (from -l/--lang)");
|
||||||
|
message(&msg, Level::Success)?;
|
||||||
|
|
||||||
|
lang
|
||||||
|
}
|
||||||
|
None => {
|
||||||
|
assert!(interactive);
|
||||||
|
|
||||||
|
cliclack::select("Select a language:")
|
||||||
|
.items(&[(Lang::Lua, "Lua", ""), (Lang::Rust, "Rust", "")])
|
||||||
|
.interact()?
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
let default_dir = xdg::BaseDirectories::with_prefix("pinnacle")?.get_config_home();
|
||||||
|
|
||||||
|
let default_dir_clone = default_dir.clone();
|
||||||
|
|
||||||
|
let dir_validator = move |s: &String| {
|
||||||
|
let mut target_dir = if s.is_empty() {
|
||||||
|
default_dir_clone.clone()
|
||||||
|
} else {
|
||||||
|
PathBuf::from(
|
||||||
|
shellexpand::full(s)
|
||||||
|
.map_err(|err| format!("Directory expansion failed: {err}"))?
|
||||||
|
.to_string(),
|
||||||
|
)
|
||||||
|
};
|
||||||
|
|
||||||
|
if target_dir.is_relative() {
|
||||||
|
let mut new_dir = std::env::current_dir().map_err(|err| {
|
||||||
|
format!("Failed to get the current dir to resolve relative path: {err}")
|
||||||
|
})?;
|
||||||
|
new_dir.push(target_dir);
|
||||||
|
target_dir = new_dir;
|
||||||
|
}
|
||||||
|
|
||||||
|
match target_dir.try_exists() {
|
||||||
|
Ok(exists) => {
|
||||||
|
if exists {
|
||||||
|
if !target_dir.is_dir() {
|
||||||
|
Err(format!(
|
||||||
|
"`{}` exists but is not a directory",
|
||||||
|
target_dir.display()
|
||||||
|
))
|
||||||
|
} else if lang == Lang::Rust
|
||||||
|
&& std::fs::read_dir(&target_dir)
|
||||||
|
.map_err(|err| {
|
||||||
|
format!(
|
||||||
|
"Failed to check if `{}` is empty: {err}",
|
||||||
|
target_dir.display()
|
||||||
|
)
|
||||||
|
})?
|
||||||
|
.next()
|
||||||
|
.is_some()
|
||||||
|
{
|
||||||
|
Err(format!(
|
||||||
|
"`{}` exists but is not empty. Empty it to generate a Rust config in it.",
|
||||||
|
target_dir.display()
|
||||||
|
))
|
||||||
|
} else {
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Err(err) => Err(format!(
|
||||||
|
"Failed to check if `{}` exists: {err}",
|
||||||
|
target_dir.display()
|
||||||
|
)),
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
let target_dir = match args.dir {
|
||||||
|
Some(dir) => {
|
||||||
|
let msg = format!(
|
||||||
|
"Choose a directory to place the config in:\n{} (from -d/--dir)",
|
||||||
|
dir.display()
|
||||||
|
);
|
||||||
|
|
||||||
|
message(&msg, Level::Success)?;
|
||||||
|
|
||||||
|
if lang == Lang::Rust && matches!(dir.try_exists(), Ok(true)) {
|
||||||
|
exit_message("Directory must be empty to create a Rust config in it");
|
||||||
|
anyhow::bail!("{msg}");
|
||||||
|
}
|
||||||
|
|
||||||
|
dir
|
||||||
|
}
|
||||||
|
None => {
|
||||||
|
assert!(interactive);
|
||||||
|
|
||||||
|
let dir: PathBuf = cliclack::input("Choose a directory to place the config in:")
|
||||||
|
.default_input(default_dir.to_string_lossy().as_ref())
|
||||||
|
.validate_interactively(dir_validator)
|
||||||
|
.interact()?;
|
||||||
|
|
||||||
|
let mut dir = shellexpand::path::full(&dir)?.to_path_buf();
|
||||||
|
|
||||||
|
if dir.is_relative() {
|
||||||
|
let mut new_dir = std::env::current_dir()?;
|
||||||
|
new_dir.push(dir);
|
||||||
|
dir = new_dir;
|
||||||
|
}
|
||||||
|
|
||||||
|
dir
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
if let Ok(false) = target_dir.try_exists() {
|
||||||
|
let msg = format!(
|
||||||
|
"`{}` doesn't exist and will be created.",
|
||||||
|
target_dir.display()
|
||||||
|
);
|
||||||
|
message(&msg, Level::Info)?;
|
||||||
|
}
|
||||||
|
|
||||||
|
if interactive {
|
||||||
|
let confirm_creation = cliclack::confirm(format!(
|
||||||
|
"Create a {} config inside `{}`?",
|
||||||
|
lang,
|
||||||
|
target_dir.display()
|
||||||
|
))
|
||||||
|
.initial_value(false)
|
||||||
|
.interact()?;
|
||||||
|
|
||||||
|
if !confirm_creation {
|
||||||
|
exit_message("Config generation cancelled.");
|
||||||
|
return Ok(());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
std::fs::create_dir_all(&target_dir)?;
|
||||||
|
|
||||||
|
// Generate the config
|
||||||
|
|
||||||
|
let xdg_base_dirs = xdg::BaseDirectories::with_prefix("pinnacle")?;
|
||||||
|
let mut default_config_dir = xdg_base_dirs.get_data_file("default_config");
|
||||||
|
|
||||||
|
// %F = %Y-%m-%d or year-month-day in ISO 8601
|
||||||
|
// %T = %H:%M:%S
|
||||||
|
let now = format!("{}", chrono::Local::now().format("%F.%T"));
|
||||||
|
|
||||||
|
match lang {
|
||||||
|
Lang::Lua => {
|
||||||
|
default_config_dir.push("lua");
|
||||||
|
|
||||||
|
let mut files_to_backup: Vec<(String, String)> = Vec::new();
|
||||||
|
|
||||||
|
for file in std::fs::read_dir(&default_config_dir)? {
|
||||||
|
let file = file?;
|
||||||
|
let name = file.file_name();
|
||||||
|
let target_file = target_dir.join(&name);
|
||||||
|
if let Ok(true) = target_file.try_exists() {
|
||||||
|
let backup_name = format!("{}.{now}.bak", name.to_string_lossy());
|
||||||
|
files_to_backup.push((name.to_string_lossy().to_string(), backup_name));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if !files_to_backup.is_empty() {
|
||||||
|
let msg = files_to_backup
|
||||||
|
.iter()
|
||||||
|
.map(|(src, dst)| format!("{src} -> {dst}"))
|
||||||
|
.collect::<Vec<_>>()
|
||||||
|
.join("\n");
|
||||||
|
|
||||||
|
if interactive {
|
||||||
|
cliclack::note("The following files will be renamed:", msg)?;
|
||||||
|
let r#continue = cliclack::confirm("Continue?").interact()?;
|
||||||
|
|
||||||
|
if !r#continue {
|
||||||
|
exit_message("Config generation cancelled.");
|
||||||
|
return Ok(());
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
println!("The following files will be renamed:");
|
||||||
|
println!("{msg}");
|
||||||
|
}
|
||||||
|
|
||||||
|
for (src, dst) in files_to_backup.iter() {
|
||||||
|
std::fs::rename(target_dir.join(src), target_dir.join(dst))?;
|
||||||
|
}
|
||||||
|
|
||||||
|
message("Renamed old files", Level::Info)?;
|
||||||
|
}
|
||||||
|
|
||||||
|
dircpy::copy_dir(&default_config_dir, &target_dir)?;
|
||||||
|
}
|
||||||
|
Lang::Rust => {
|
||||||
|
default_config_dir.push("rust");
|
||||||
|
|
||||||
|
assert!(
|
||||||
|
std::fs::read_dir(&target_dir)?.next().is_none(),
|
||||||
|
"target directory was not empty"
|
||||||
|
);
|
||||||
|
|
||||||
|
dircpy::copy_dir(&default_config_dir, &target_dir)?;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
message("Copied new config over", Level::Info)?;
|
||||||
|
|
||||||
|
let mut outro_msg = format!("{lang} config created in {}!", target_dir.display());
|
||||||
|
if lang == Lang::Rust {
|
||||||
|
outro_msg = format!(
|
||||||
|
"{outro_msg}\nYou may want to run `cargo build` in the \
|
||||||
|
config directory beforehand to avoid waiting when starting up Pinnacle."
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if interactive {
|
||||||
|
cliclack::outro(outro_msg)?;
|
||||||
|
} else {
|
||||||
|
println!("{outro_msg}");
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
// TODO: find a way to test the interactive bits programmatically
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn cli_config_gen_parses_correctly() -> anyhow::Result<()> {
|
||||||
|
let temp_dir = tempfile::tempdir()?;
|
||||||
|
let temp_dir = temp_dir.path().join("cli_config_gen_parses_correctly");
|
||||||
|
|
||||||
|
let cli = Cli::parse_from([
|
||||||
|
"pinnacle",
|
||||||
|
"config",
|
||||||
|
"gen",
|
||||||
|
"--lang",
|
||||||
|
"rust",
|
||||||
|
"--dir",
|
||||||
|
temp_dir
|
||||||
|
.to_str()
|
||||||
|
.ok_or(anyhow::anyhow!("not valid unicode"))?,
|
||||||
|
"--non-interactive",
|
||||||
|
]);
|
||||||
|
|
||||||
|
let expected_config_gen = ConfigGen {
|
||||||
|
lang: Some(Lang::Rust),
|
||||||
|
dir: Some(temp_dir.to_path_buf()),
|
||||||
|
non_interactive: true,
|
||||||
|
};
|
||||||
|
|
||||||
|
let Some(CliSubcommand::Config(ConfigSubcommand::Gen(config_gen))) = cli.subcommand else {
|
||||||
|
anyhow::bail!("cli.subcommand config_gen doesn't exist");
|
||||||
|
};
|
||||||
|
|
||||||
|
assert_eq!(config_gen, expected_config_gen);
|
||||||
|
|
||||||
|
let cli = Cli::parse_from(["pinnacle", "config", "gen", "--lang", "lua"]);
|
||||||
|
|
||||||
|
let expected_config_gen = ConfigGen {
|
||||||
|
lang: Some(Lang::Lua),
|
||||||
|
dir: None,
|
||||||
|
non_interactive: !std::io::stdout().is_terminal(),
|
||||||
|
};
|
||||||
|
|
||||||
|
let Some(CliSubcommand::Config(ConfigSubcommand::Gen(config_gen))) = cli.subcommand else {
|
||||||
|
anyhow::bail!("cli.subcommand config_gen doesn't exist");
|
||||||
|
};
|
||||||
|
|
||||||
|
assert_eq!(config_gen, expected_config_gen);
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn non_interactive_config_gen_lua_works() -> anyhow::Result<()> {
|
||||||
|
let temp_dir = tempfile::tempdir()?;
|
||||||
|
let temp_dir = temp_dir.path().join("non_interactive_config_gen_lua_works");
|
||||||
|
|
||||||
|
let config_gen = ConfigGen {
|
||||||
|
lang: Some(Lang::Lua),
|
||||||
|
dir: Some(temp_dir.clone()),
|
||||||
|
non_interactive: true,
|
||||||
|
};
|
||||||
|
|
||||||
|
generate_config(config_gen)?;
|
||||||
|
|
||||||
|
assert!(matches!(
|
||||||
|
temp_dir.join("default_config.lua").try_exists(),
|
||||||
|
Ok(true)
|
||||||
|
));
|
||||||
|
assert!(matches!(
|
||||||
|
temp_dir.join("metaconfig.toml").try_exists(),
|
||||||
|
Ok(true)
|
||||||
|
));
|
||||||
|
assert!(matches!(
|
||||||
|
temp_dir.join(".luarc.json").try_exists(),
|
||||||
|
Ok(true)
|
||||||
|
));
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn non_interactive_config_gen_rust_works() -> anyhow::Result<()> {
|
||||||
|
let temp_dir = tempfile::tempdir()?;
|
||||||
|
let temp_dir = temp_dir
|
||||||
|
.path()
|
||||||
|
.join("non_interactive_config_gen_rust_works");
|
||||||
|
|
||||||
|
let config_gen = ConfigGen {
|
||||||
|
lang: Some(Lang::Rust),
|
||||||
|
dir: Some(temp_dir.clone()),
|
||||||
|
non_interactive: true,
|
||||||
|
};
|
||||||
|
|
||||||
|
generate_config(config_gen)?;
|
||||||
|
|
||||||
|
assert!(matches!(
|
||||||
|
temp_dir.join("src/main.rs").try_exists(),
|
||||||
|
Ok(true)
|
||||||
|
));
|
||||||
|
assert!(matches!(
|
||||||
|
temp_dir.join("metaconfig.toml").try_exists(),
|
||||||
|
Ok(true)
|
||||||
|
));
|
||||||
|
assert!(matches!(temp_dir.join("Cargo.toml").try_exists(), Ok(true)));
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn non_interactive_config_gen_lua_backup_works() -> anyhow::Result<()> {
|
||||||
|
let temp_dir = tempfile::tempdir()?;
|
||||||
|
let temp_dir = temp_dir
|
||||||
|
.path()
|
||||||
|
.join("non_interactive_config_gen_lua_backup_works");
|
||||||
|
|
||||||
|
let config_gen = ConfigGen {
|
||||||
|
lang: Some(Lang::Lua),
|
||||||
|
dir: Some(temp_dir.clone()),
|
||||||
|
non_interactive: true,
|
||||||
|
};
|
||||||
|
|
||||||
|
generate_config(config_gen.clone())?;
|
||||||
|
generate_config(config_gen)?;
|
||||||
|
|
||||||
|
let generated_file_count = std::fs::read_dir(&temp_dir)?
|
||||||
|
.collect::<Result<Vec<_>, _>>()?
|
||||||
|
.len();
|
||||||
|
|
||||||
|
// 3 for original, 3 for backups
|
||||||
|
assert_eq!(generated_file_count, 6);
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn non_interactive_config_gen_rust_nonempty_dir_does_not_work() -> anyhow::Result<()> {
|
||||||
|
let temp_dir = tempfile::tempdir()?;
|
||||||
|
let temp_dir = temp_dir
|
||||||
|
.path()
|
||||||
|
.join("non_interactive_config_gen_rust_nonempty_dir_does_not_work");
|
||||||
|
|
||||||
|
let config_gen = ConfigGen {
|
||||||
|
lang: Some(Lang::Rust),
|
||||||
|
dir: Some(temp_dir),
|
||||||
|
non_interactive: true,
|
||||||
|
};
|
||||||
|
|
||||||
|
generate_config(config_gen.clone())?;
|
||||||
|
|
||||||
|
assert!(generate_config(config_gen.clone()).is_err());
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
151
src/config.rs
151
src/config.rs
|
@ -33,6 +33,7 @@ use sysinfo::ProcessRefreshKind;
|
||||||
use tokio::task::JoinHandle;
|
use tokio::task::JoinHandle;
|
||||||
use toml::Table;
|
use toml::Table;
|
||||||
|
|
||||||
|
use tracing::{debug, error, info};
|
||||||
use xdg::BaseDirectories;
|
use xdg::BaseDirectories;
|
||||||
use xkbcommon::xkb::Keysym;
|
use xkbcommon::xkb::Keysym;
|
||||||
|
|
||||||
|
@ -171,10 +172,27 @@ pub struct Config {
|
||||||
|
|
||||||
config_join_handle: Option<JoinHandle<()>>,
|
config_join_handle: Option<JoinHandle<()>>,
|
||||||
config_reload_on_crash_token: Option<RegistrationToken>,
|
config_reload_on_crash_token: Option<RegistrationToken>,
|
||||||
|
|
||||||
|
pub no_config: bool,
|
||||||
|
config_dir: Option<PathBuf>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Config {
|
impl Config {
|
||||||
pub fn clear(&mut self, loop_handle: &LoopHandle<State>) {
|
pub fn new(no_config: bool, config_dir: Option<PathBuf>) -> Self {
|
||||||
|
Config {
|
||||||
|
no_config,
|
||||||
|
config_dir,
|
||||||
|
..Default::default()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn dir(&self, xdg_base_dirs: &BaseDirectories) -> PathBuf {
|
||||||
|
self.config_dir
|
||||||
|
.clone()
|
||||||
|
.unwrap_or_else(|| get_config_dir(xdg_base_dirs))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn clear(&mut self, loop_handle: &LoopHandle<State>) {
|
||||||
self.window_rules.clear();
|
self.window_rules.clear();
|
||||||
self.connector_saved_states.clear();
|
self.connector_saved_states.clear();
|
||||||
if let Some(join_handle) = self.config_join_handle.take() {
|
if let Some(join_handle) = self.config_join_handle.take() {
|
||||||
|
@ -208,7 +226,7 @@ fn parse_metaconfig(config_dir: &Path) -> anyhow::Result<Metaconfig> {
|
||||||
|
|
||||||
/// Get the config dir. This is $PINNACLE_CONFIG_DIR, then $XDG_CONFIG_HOME/pinnacle,
|
/// Get the config dir. This is $PINNACLE_CONFIG_DIR, then $XDG_CONFIG_HOME/pinnacle,
|
||||||
/// then ~/.config/pinnacle.
|
/// then ~/.config/pinnacle.
|
||||||
pub fn get_config_dir(xdg_base_dirs: &BaseDirectories) -> PathBuf {
|
fn get_config_dir(xdg_base_dirs: &BaseDirectories) -> PathBuf {
|
||||||
let config_dir = std::env::var("PINNACLE_CONFIG_DIR")
|
let config_dir = std::env::var("PINNACLE_CONFIG_DIR")
|
||||||
.ok()
|
.ok()
|
||||||
.and_then(|s| Some(PathBuf::from(shellexpand::full(&s).ok()?.to_string())));
|
.and_then(|s| Some(PathBuf::from(shellexpand::full(&s).ok()?.to_string())));
|
||||||
|
@ -221,28 +239,34 @@ impl State {
|
||||||
///
|
///
|
||||||
/// If this method is called while a config is already running, it will be replaced.
|
/// If this method is called while a config is already running, it will be replaced.
|
||||||
pub fn start_config(&mut self, config_dir: impl AsRef<Path>) -> anyhow::Result<()> {
|
pub fn start_config(&mut self, config_dir: impl AsRef<Path>) -> anyhow::Result<()> {
|
||||||
let config_dir = config_dir.as_ref();
|
let mut config_dir = config_dir.as_ref();
|
||||||
|
|
||||||
tracing::info!("Starting config at {}", config_dir.display());
|
let default_lua_config_dir = self
|
||||||
|
.xdg_base_dirs
|
||||||
let default_lua_config_dir = self.xdg_base_dirs.get_data_file("default_config");
|
.get_data_file("default_config")
|
||||||
|
.join("lua");
|
||||||
|
|
||||||
let load_default_config = |state: &mut State, reason: &str| {
|
let load_default_config = |state: &mut State, reason: &str| {
|
||||||
tracing::error!(
|
error!(
|
||||||
"Unable to load config at {}: {reason}",
|
"Unable to load config at {}: {reason}",
|
||||||
config_dir.display()
|
config_dir.display()
|
||||||
);
|
);
|
||||||
tracing::info!("Falling back to default Lua config");
|
info!("Falling back to default Lua config");
|
||||||
state.start_config(&default_lua_config_dir)
|
state.start_config(&default_lua_config_dir)
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// If `--no-config` was set, still load the keybinds from the default metaconfig
|
||||||
|
if self.config.no_config {
|
||||||
|
config_dir = &default_lua_config_dir
|
||||||
|
}
|
||||||
|
|
||||||
let metaconfig = match parse_metaconfig(config_dir) {
|
let metaconfig = match parse_metaconfig(config_dir) {
|
||||||
Ok(metaconfig) => metaconfig,
|
Ok(metaconfig) => metaconfig,
|
||||||
Err(err) => {
|
Err(err) => {
|
||||||
// Stops infinite recursion if somehow the default_config dir is screwed up
|
// Stops infinite recursion if somehow the default_config dir is screwed up
|
||||||
if config_dir == default_lua_config_dir {
|
if config_dir == default_lua_config_dir {
|
||||||
tracing::error!("The metaconfig at the default Lua config directory is either malformed or missing.");
|
error!("The metaconfig at the default Lua config directory is either malformed or missing.");
|
||||||
tracing::error!(
|
error!(
|
||||||
"If you have not touched {}, this is a bug and you should file an issue (pretty please with a cherry on top?).",
|
"If you have not touched {}, this is a bug and you should file an issue (pretty please with a cherry on top?).",
|
||||||
default_lua_config_dir.display()
|
default_lua_config_dir.display()
|
||||||
);
|
);
|
||||||
|
@ -252,14 +276,35 @@ impl State {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
tracing::debug!("Clearing tags");
|
let reload_keybind = metaconfig.reload_keybind;
|
||||||
|
let kill_keybind = metaconfig.kill_keybind;
|
||||||
|
|
||||||
|
let reload_mask = ModifierMask::from(reload_keybind.modifiers);
|
||||||
|
let kill_mask = ModifierMask::from(kill_keybind.modifiers);
|
||||||
|
|
||||||
|
let reload_keybind = (reload_mask, Keysym::from(reload_keybind.key as u32));
|
||||||
|
let kill_keybind = (kill_mask, Keysym::from(kill_keybind.key as u32));
|
||||||
|
|
||||||
|
self.input_state.reload_keybind = Some(reload_keybind);
|
||||||
|
self.input_state.kill_keybind = Some(kill_keybind);
|
||||||
|
|
||||||
|
if self.config.no_config {
|
||||||
|
info!("`--no-config` was set, not spawning config");
|
||||||
|
return Ok(());
|
||||||
|
}
|
||||||
|
|
||||||
|
assert!(!self.config.no_config);
|
||||||
|
|
||||||
|
// Clear state
|
||||||
|
|
||||||
|
debug!("Clearing tags");
|
||||||
for output in self.space.outputs() {
|
for output in self.space.outputs() {
|
||||||
output.with_state(|state| state.tags.clear());
|
output.with_state(|state| state.tags.clear());
|
||||||
}
|
}
|
||||||
|
|
||||||
TagId::reset();
|
TagId::reset();
|
||||||
|
|
||||||
tracing::debug!("Clearing input state");
|
debug!("Clearing input state");
|
||||||
|
|
||||||
self.input_state.clear();
|
self.input_state.clear();
|
||||||
|
|
||||||
|
@ -293,9 +338,6 @@ impl State {
|
||||||
self.start_grpc_server(socket_dir.as_path())?;
|
self.start_grpc_server(socket_dir.as_path())?;
|
||||||
}
|
}
|
||||||
|
|
||||||
let reload_keybind = metaconfig.reload_keybind;
|
|
||||||
let kill_keybind = metaconfig.kill_keybind;
|
|
||||||
|
|
||||||
let mut command = metaconfig.command.iter();
|
let mut command = metaconfig.command.iter();
|
||||||
|
|
||||||
let arg0 = match command.next() {
|
let arg0 = match command.next() {
|
||||||
|
@ -305,32 +347,26 @@ impl State {
|
||||||
|
|
||||||
let command = command.collect::<Vec<_>>();
|
let command = command.collect::<Vec<_>>();
|
||||||
|
|
||||||
tracing::debug!(arg0, ?command);
|
debug!(arg0, ?command);
|
||||||
|
|
||||||
let envs = metaconfig
|
let envs = metaconfig
|
||||||
.envs
|
.envs
|
||||||
.unwrap_or(toml::map::Map::new())
|
.unwrap_or(toml::map::Map::new())
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.filter_map(|(key, val)| {
|
.map(|(key, val)| -> anyhow::Result<Option<(String, String)>> {
|
||||||
if let toml::Value::String(string) = val {
|
if let toml::Value::String(string) = val {
|
||||||
Some((
|
Ok(Some((key, shellexpand::full(&string)?.to_string())))
|
||||||
key,
|
|
||||||
shellexpand::full_with_context(
|
|
||||||
&string,
|
|
||||||
|| std::env::var("HOME").ok(),
|
|
||||||
// Expand nonexistent vars to an empty string instead of crashing
|
|
||||||
|var| Ok::<_, ()>(Some(std::env::var(var).unwrap_or("".to_string()))),
|
|
||||||
)
|
|
||||||
.ok()?
|
|
||||||
.to_string(),
|
|
||||||
))
|
|
||||||
} else {
|
} else {
|
||||||
None
|
Ok(None)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.collect::<Vec<_>>();
|
.collect::<Result<Vec<_>, _>>()?
|
||||||
|
.into_iter()
|
||||||
|
.flatten();
|
||||||
|
|
||||||
tracing::debug!("Config envs are {envs:?}");
|
debug!("Config envs are {envs:?}");
|
||||||
|
|
||||||
|
info!("Starting config at {}", config_dir.display());
|
||||||
|
|
||||||
let mut child = match tokio::process::Command::new(arg0)
|
let mut child = match tokio::process::Command::new(arg0)
|
||||||
.args(command)
|
.args(command)
|
||||||
|
@ -345,23 +381,14 @@ impl State {
|
||||||
Err(err) => return load_default_config(self, &err.to_string()),
|
Err(err) => return load_default_config(self, &err.to_string()),
|
||||||
};
|
};
|
||||||
|
|
||||||
tracing::info!("Started config with {:?}", metaconfig.command);
|
info!("Started config with {:?}", metaconfig.command);
|
||||||
|
|
||||||
let reload_mask = ModifierMask::from(reload_keybind.modifiers);
|
|
||||||
let kill_mask = ModifierMask::from(kill_keybind.modifiers);
|
|
||||||
|
|
||||||
let reload_keybind = (reload_mask, Keysym::from(reload_keybind.key as u32));
|
|
||||||
let kill_keybind = (kill_mask, Keysym::from(kill_keybind.key as u32));
|
|
||||||
|
|
||||||
self.input_state.reload_keybind = Some(reload_keybind);
|
|
||||||
self.input_state.kill_keybind = Some(kill_keybind);
|
|
||||||
|
|
||||||
let (pinger, ping_source) = calloop::ping::make_ping()?;
|
let (pinger, ping_source) = calloop::ping::make_ping()?;
|
||||||
|
|
||||||
let token = self
|
let token = self
|
||||||
.loop_handle
|
.loop_handle
|
||||||
.insert_source(ping_source, move |_, _, state| {
|
.insert_source(ping_source, move |_, _, state| {
|
||||||
tracing::error!("Config crashed! Falling back to default Lua config");
|
error!("Config crashed! Falling back to default Lua config");
|
||||||
state
|
state
|
||||||
.start_config(&default_lua_config_dir)
|
.start_config(&default_lua_config_dir)
|
||||||
.expect("failed to start default lua config");
|
.expect("failed to start default lua config");
|
||||||
|
@ -422,7 +449,7 @@ impl State {
|
||||||
.starts_with("pinnacle-grpc")
|
.starts_with("pinnacle-grpc")
|
||||||
})
|
})
|
||||||
{
|
{
|
||||||
tracing::debug!("Removing socket at {:?}", file.path());
|
debug!("Removing socket at {:?}", file.path());
|
||||||
std::fs::remove_file(file.path())
|
std::fs::remove_file(file.path())
|
||||||
.context(format!("Failed to remove old socket at {:?}", file.path()))?;
|
.context(format!("Failed to remove old socket at {:?}", file.path()))?;
|
||||||
}
|
}
|
||||||
|
@ -439,7 +466,7 @@ impl State {
|
||||||
self.loop_handle
|
self.loop_handle
|
||||||
.insert_source(grpc_receiver, |msg, _, state| match msg {
|
.insert_source(grpc_receiver, |msg, _, state| match msg {
|
||||||
Event::Msg(f) => f(state),
|
Event::Msg(f) => f(state),
|
||||||
Event::Closed => tracing::error!("grpc receiver was closed"),
|
Event::Closed => error!("grpc receiver was closed"),
|
||||||
})
|
})
|
||||||
.expect("failed to insert grpc_receiver into loop");
|
.expect("failed to insert grpc_receiver into loop");
|
||||||
|
|
||||||
|
@ -474,7 +501,7 @@ impl State {
|
||||||
Some(_) => {
|
Some(_) => {
|
||||||
self.grpc_server_join_handle = Some(tokio::spawn(async move {
|
self.grpc_server_join_handle = Some(tokio::spawn(async move {
|
||||||
if let Err(err) = grpc_server.serve_with_incoming(uds_stream).await {
|
if let Err(err) = grpc_server.serve_with_incoming(uds_stream).await {
|
||||||
tracing::error!("gRPC server error: {err}");
|
error!("gRPC server error: {err}");
|
||||||
}
|
}
|
||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
|
@ -486,7 +513,7 @@ impl State {
|
||||||
move |state| {
|
move |state| {
|
||||||
state.grpc_server_join_handle = Some(tokio::spawn(async move {
|
state.grpc_server_join_handle = Some(tokio::spawn(async move {
|
||||||
if let Err(err) = grpc_server.serve_with_incoming(uds_stream).await {
|
if let Err(err) = grpc_server.serve_with_incoming(uds_stream).await {
|
||||||
tracing::error!("gRPC server error: {err}");
|
error!("gRPC server error: {err}");
|
||||||
}
|
}
|
||||||
}));
|
}));
|
||||||
},
|
},
|
||||||
|
@ -503,7 +530,7 @@ mod tests {
|
||||||
use std::env::var;
|
use std::env::var;
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn config_dir_with_relative_env_works() -> anyhow::Result<()> {
|
fn get_config_dir_with_relative_env_works() -> anyhow::Result<()> {
|
||||||
let relative_path = "api/rust/examples/default_config";
|
let relative_path = "api/rust/examples/default_config";
|
||||||
|
|
||||||
temp_env::with_var("PINNACLE_CONFIG_DIR", Some(relative_path), || {
|
temp_env::with_var("PINNACLE_CONFIG_DIR", Some(relative_path), || {
|
||||||
|
@ -519,7 +546,7 @@ mod tests {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn config_dir_with_tilde_env_works() -> anyhow::Result<()> {
|
fn get_config_dir_with_tilde_env_works() -> anyhow::Result<()> {
|
||||||
temp_env::with_var("PINNACLE_CONFIG_DIR", Some("~/some/dir/somewhere/"), || {
|
temp_env::with_var("PINNACLE_CONFIG_DIR", Some("~/some/dir/somewhere/"), || {
|
||||||
let xdg_base_dirs = BaseDirectories::with_prefix("pinnacle")?;
|
let xdg_base_dirs = BaseDirectories::with_prefix("pinnacle")?;
|
||||||
let expected = PathBuf::from(var("HOME")?).join("some/dir/somewhere");
|
let expected = PathBuf::from(var("HOME")?).join("some/dir/somewhere");
|
||||||
|
@ -531,7 +558,7 @@ mod tests {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn config_dir_with_absolute_env_works() -> anyhow::Result<()> {
|
fn get_config_dir_with_absolute_env_works() -> anyhow::Result<()> {
|
||||||
let absolute_path = "/its/morbin/time";
|
let absolute_path = "/its/morbin/time";
|
||||||
|
|
||||||
temp_env::with_var("PINNACLE_CONFIG_DIR", Some(absolute_path), || {
|
temp_env::with_var("PINNACLE_CONFIG_DIR", Some(absolute_path), || {
|
||||||
|
@ -545,7 +572,7 @@ mod tests {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn config_dir_without_env_and_with_xdg_works() -> anyhow::Result<()> {
|
fn get_config_dir_without_env_and_with_xdg_works() -> anyhow::Result<()> {
|
||||||
let xdg_config_home = "/some/different/xdg/config/path";
|
let xdg_config_home = "/some/different/xdg/config/path";
|
||||||
|
|
||||||
temp_env::with_vars(
|
temp_env::with_vars(
|
||||||
|
@ -565,7 +592,7 @@ mod tests {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn config_dir_without_env_and_without_xdg_works() -> anyhow::Result<()> {
|
fn get_config_dir_without_env_and_without_xdg_works() -> anyhow::Result<()> {
|
||||||
temp_env::with_vars(
|
temp_env::with_vars(
|
||||||
[
|
[
|
||||||
("PINNACLE_CONFIG_DIR", None::<&str>),
|
("PINNACLE_CONFIG_DIR", None::<&str>),
|
||||||
|
@ -685,4 +712,26 @@ mod tests {
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Ayo can we get Kotlin style test function naming so I can do something like
|
||||||
|
// fn `config.dir with --config-dir returns correct dir`() {}
|
||||||
|
#[test]
|
||||||
|
fn config_dot_dir_with_dash_dash_config_dir_returns_correct_dir() -> anyhow::Result<()> {
|
||||||
|
let dir = PathBuf::from("/some/dir/here");
|
||||||
|
let config = Config::new(false, Some(dir.clone()));
|
||||||
|
|
||||||
|
assert_eq!(config.dir(&BaseDirectories::with_prefix("pinnacle")?), dir);
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn config_dot_dir_without_dash_dash_config_dir_returns_correct_dir() -> anyhow::Result<()> {
|
||||||
|
let config = Config::new(false, None);
|
||||||
|
let xdg_base_dirs = BaseDirectories::with_prefix("pinnacle")?;
|
||||||
|
|
||||||
|
assert_eq!(config.dir(&xdg_base_dirs), get_config_dir(&xdg_base_dirs));
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -329,7 +329,7 @@ impl State {
|
||||||
self.shutdown();
|
self.shutdown();
|
||||||
}
|
}
|
||||||
Some(KeyAction::ReloadConfig) => {
|
Some(KeyAction::ReloadConfig) => {
|
||||||
self.start_config(crate::config::get_config_dir(&self.xdg_base_dirs))
|
self.start_config(self.config.dir(&self.xdg_base_dirs))
|
||||||
.expect("failed to restart config");
|
.expect("failed to restart config");
|
||||||
}
|
}
|
||||||
None => (),
|
None => (),
|
||||||
|
|
60
src/main.rs
60
src/main.rs
|
@ -12,7 +12,7 @@
|
||||||
#![warn(clippy::unwrap_used)]
|
#![warn(clippy::unwrap_used)]
|
||||||
|
|
||||||
use anyhow::Context;
|
use anyhow::Context;
|
||||||
use clap::Parser;
|
use cli::Cli;
|
||||||
use nix::unistd::Uid;
|
use nix::unistd::Uid;
|
||||||
use tracing::{info, level_filters::LevelFilter, warn};
|
use tracing::{info, level_filters::LevelFilter, warn};
|
||||||
use tracing_appender::rolling::Rotation;
|
use tracing_appender::rolling::Rotation;
|
||||||
|
@ -21,6 +21,7 @@ use xdg::BaseDirectories;
|
||||||
|
|
||||||
mod api;
|
mod api;
|
||||||
mod backend;
|
mod backend;
|
||||||
|
mod cli;
|
||||||
mod config;
|
mod config;
|
||||||
mod cursor;
|
mod cursor;
|
||||||
mod focus;
|
mod focus;
|
||||||
|
@ -34,30 +35,6 @@ mod state;
|
||||||
mod tag;
|
mod tag;
|
||||||
mod window;
|
mod window;
|
||||||
|
|
||||||
#[derive(clap::Args, Debug)]
|
|
||||||
#[group(id = "backend", required = false, multiple = false)]
|
|
||||||
struct Backends {
|
|
||||||
#[arg(long, group = "backend")]
|
|
||||||
/// Run Pinnacle in a window in your graphical environment
|
|
||||||
winit: bool,
|
|
||||||
#[arg(long, group = "backend")]
|
|
||||||
/// Run Pinnacle from a tty
|
|
||||||
udev: bool,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(clap::Parser, Debug)]
|
|
||||||
#[command(author, version, about, long_about = None)]
|
|
||||||
struct Args {
|
|
||||||
#[command(flatten)]
|
|
||||||
backend: Backends,
|
|
||||||
#[arg(long)]
|
|
||||||
/// Allow running Pinnacle as root (this is NOT recommended)
|
|
||||||
allow_root: bool,
|
|
||||||
#[arg(long, requires = "backend")]
|
|
||||||
/// Force Pinnacle to run with the provided backend
|
|
||||||
force: bool,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[tokio::main]
|
#[tokio::main]
|
||||||
async fn main() -> anyhow::Result<()> {
|
async fn main() -> anyhow::Result<()> {
|
||||||
let xdg_state_dir = BaseDirectories::with_prefix("pinnacle")?.get_state_home();
|
let xdg_state_dir = BaseDirectories::with_prefix("pinnacle")?.get_state_home();
|
||||||
|
@ -96,13 +73,15 @@ async fn main() -> anyhow::Result<()> {
|
||||||
.with(stdout_layer)
|
.with(stdout_layer)
|
||||||
.init();
|
.init();
|
||||||
|
|
||||||
let args = Args::parse();
|
let Some(cli) = Cli::parse_and_prompt() else {
|
||||||
|
return Ok(());
|
||||||
|
};
|
||||||
|
|
||||||
if Uid::effective().is_root() {
|
if Uid::effective().is_root() {
|
||||||
if !args.allow_root {
|
if !cli.allow_root {
|
||||||
warn!("You are trying to run Pinnacle as root.");
|
warn!("You are trying to run Pinnacle as root.");
|
||||||
warn!("This is NOT recommended.");
|
warn!("This is NOT recommended.");
|
||||||
warn!("To run Pinnacle as root, pass in the --allow-root flag.");
|
warn!("To run Pinnacle as root, pass in the `--allow-root` flag.");
|
||||||
warn!("Again, this is NOT recommended.");
|
warn!("Again, this is NOT recommended.");
|
||||||
return Ok(());
|
return Ok(());
|
||||||
} else {
|
} else {
|
||||||
|
@ -118,48 +97,47 @@ async fn main() -> anyhow::Result<()> {
|
||||||
warn!("You may see LOTS of file descriptors open under Pinnacle.");
|
warn!("You may see LOTS of file descriptors open under Pinnacle.");
|
||||||
}
|
}
|
||||||
|
|
||||||
match (args.backend.winit, args.backend.udev, args.force) {
|
match (cli.backend, cli.force) {
|
||||||
(false, false, _) => {
|
(None, _) => {
|
||||||
if in_graphical_env {
|
if in_graphical_env {
|
||||||
info!("Starting winit backend");
|
info!("Starting winit backend");
|
||||||
crate::backend::winit::run_winit()?;
|
crate::backend::winit::run_winit(cli.no_config, cli.config_dir)?;
|
||||||
} else {
|
} else {
|
||||||
info!("Starting udev backend");
|
info!("Starting udev backend");
|
||||||
crate::backend::udev::run_udev()?;
|
crate::backend::udev::run_udev(cli.no_config, cli.config_dir)?;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
(true, false, force) => {
|
(Some(cli::Backend::Winit), force) => {
|
||||||
if !in_graphical_env {
|
if !in_graphical_env {
|
||||||
if force {
|
if force {
|
||||||
warn!("Starting winit backend with no detected graphical environment");
|
warn!("Starting winit backend with no detected graphical environment");
|
||||||
crate::backend::winit::run_winit()?;
|
crate::backend::winit::run_winit(cli.no_config, cli.config_dir)?;
|
||||||
} else {
|
} else {
|
||||||
warn!("Both WAYLAND_DISPLAY and DISPLAY are not set.");
|
warn!("Both WAYLAND_DISPLAY and DISPLAY are not set.");
|
||||||
warn!("If you are trying to run the winit backend in a tty, it won't work.");
|
warn!("If you are trying to run the winit backend in a tty, it won't work.");
|
||||||
warn!("If you really want to, additionally pass in the --force flag.");
|
warn!("If you really want to, additionally pass in the `--force` flag.");
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
info!("Starting winit backend");
|
info!("Starting winit backend");
|
||||||
crate::backend::winit::run_winit()?;
|
crate::backend::winit::run_winit(cli.no_config, cli.config_dir)?;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
(false, true, force) => {
|
(Some(cli::Backend::Udev), force) => {
|
||||||
if in_graphical_env {
|
if in_graphical_env {
|
||||||
if force {
|
if force {
|
||||||
warn!("Starting udev backend with a detected graphical environment");
|
warn!("Starting udev backend with a detected graphical environment");
|
||||||
crate::backend::udev::run_udev()?;
|
crate::backend::udev::run_udev(cli.no_config, cli.config_dir)?;
|
||||||
} else {
|
} else {
|
||||||
warn!("WAYLAND_DISPLAY and/or DISPLAY are set.");
|
warn!("WAYLAND_DISPLAY and/or DISPLAY are set.");
|
||||||
warn!("If you are trying to run the udev backend in a graphical environment,");
|
warn!("If you are trying to run the udev backend in a graphical environment,");
|
||||||
warn!("it won't work and may mess some things up.");
|
warn!("it won't work and may mess some things up.");
|
||||||
warn!("If you really want to, additionally pass in the --force flag.");
|
warn!("If you really want to, additionally pass in the `--force` flag.");
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
info!("Starting udev backend");
|
info!("Starting udev backend");
|
||||||
crate::backend::udev::run_udev()?;
|
crate::backend::udev::run_udev(cli.no_config, cli.config_dir)?;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
_ => unreachable!(),
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
|
|
23
src/state.rs
23
src/state.rs
|
@ -34,8 +34,9 @@ use smithay::{
|
||||||
},
|
},
|
||||||
xwayland::{X11Wm, XWayland, XWaylandEvent},
|
xwayland::{X11Wm, XWayland, XWaylandEvent},
|
||||||
};
|
};
|
||||||
use std::{cell::RefCell, sync::Arc, time::Duration};
|
use std::{cell::RefCell, path::PathBuf, sync::Arc, time::Duration};
|
||||||
use sysinfo::{ProcessRefreshKind, RefreshKind};
|
use sysinfo::{ProcessRefreshKind, RefreshKind};
|
||||||
|
use tracing::{error, info};
|
||||||
use xdg::BaseDirectories;
|
use xdg::BaseDirectories;
|
||||||
|
|
||||||
use crate::input::InputState;
|
use crate::input::InputState;
|
||||||
|
@ -108,11 +109,13 @@ impl State {
|
||||||
display: Display<Self>,
|
display: Display<Self>,
|
||||||
loop_signal: LoopSignal,
|
loop_signal: LoopSignal,
|
||||||
loop_handle: LoopHandle<'static, Self>,
|
loop_handle: LoopHandle<'static, Self>,
|
||||||
|
no_config: bool,
|
||||||
|
config_dir: Option<PathBuf>,
|
||||||
) -> anyhow::Result<Self> {
|
) -> anyhow::Result<Self> {
|
||||||
let socket = ListeningSocketSource::new_auto()?;
|
let socket = ListeningSocketSource::new_auto()?;
|
||||||
let socket_name = socket.socket_name().to_os_string();
|
let socket_name = socket.socket_name().to_os_string();
|
||||||
|
|
||||||
tracing::info!(
|
info!(
|
||||||
"Setting WAYLAND_DISPLAY to {}",
|
"Setting WAYLAND_DISPLAY to {}",
|
||||||
socket_name.to_string_lossy()
|
socket_name.to_string_lossy()
|
||||||
);
|
);
|
||||||
|
@ -124,15 +127,15 @@ impl State {
|
||||||
//
|
//
|
||||||
// To fix this, I just set the limit to be higher. As Pinnacle is the whole graphical
|
// To fix this, I just set the limit to be higher. As Pinnacle is the whole graphical
|
||||||
// environment, I *think* this is ok.
|
// environment, I *think* this is ok.
|
||||||
tracing::info!("Trying to raise file descriptor limit...");
|
info!("Trying to raise file descriptor limit...");
|
||||||
if let Err(err) = nix::sys::resource::setrlimit(
|
if let Err(err) = nix::sys::resource::setrlimit(
|
||||||
nix::sys::resource::Resource::RLIMIT_NOFILE,
|
nix::sys::resource::Resource::RLIMIT_NOFILE,
|
||||||
65536,
|
65536,
|
||||||
65536 * 2,
|
65536 * 2,
|
||||||
) {
|
) {
|
||||||
tracing::error!("Could not raise fd limit: errno {err}");
|
error!("Could not raise fd limit: errno {err}");
|
||||||
} else {
|
} else {
|
||||||
tracing::info!("Fd raise success!");
|
info!("Fd raise success!");
|
||||||
}
|
}
|
||||||
|
|
||||||
loop_handle.insert_source(socket, |stream, _metadata, data| {
|
loop_handle.insert_source(socket, |stream, _metadata, data| {
|
||||||
|
@ -158,9 +161,7 @@ impl State {
|
||||||
)?;
|
)?;
|
||||||
|
|
||||||
loop_handle.insert_idle(|state| {
|
loop_handle.insert_idle(|state| {
|
||||||
if let Err(err) =
|
if let Err(err) = state.start_config(state.config.dir(&state.xdg_base_dirs)) {
|
||||||
state.start_config(crate::config::get_config_dir(&state.xdg_base_dirs))
|
|
||||||
{
|
|
||||||
panic!("failed to start config: {err}");
|
panic!("failed to start config: {err}");
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
@ -212,7 +213,7 @@ impl State {
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
if let Err(err) = res {
|
if let Err(err) = res {
|
||||||
tracing::error!("Failed to insert XWayland source into loop: {err}");
|
error!("Failed to insert XWayland source into loop: {err}");
|
||||||
}
|
}
|
||||||
xwayland
|
xwayland
|
||||||
};
|
};
|
||||||
|
@ -254,7 +255,7 @@ impl State {
|
||||||
output_focus_stack: FocusStack::default(),
|
output_focus_stack: FocusStack::default(),
|
||||||
z_index_stack: FocusStack::default(),
|
z_index_stack: FocusStack::default(),
|
||||||
|
|
||||||
config: Config::default(),
|
config: Config::new(no_config, config_dir),
|
||||||
|
|
||||||
seat,
|
seat,
|
||||||
|
|
||||||
|
@ -302,7 +303,7 @@ impl State {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn shutdown(&self) {
|
pub fn shutdown(&self) {
|
||||||
tracing::info!("Shutting down Pinnacle");
|
info!("Shutting down Pinnacle");
|
||||||
self.loop_signal.stop();
|
self.loop_signal.stop();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue