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"
|
||||
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]]
|
||||
name = "anstream"
|
||||
version = "0.6.11"
|
||||
|
@ -295,6 +310,17 @@ dependencies = [
|
|||
"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]]
|
||||
name = "bumpalo"
|
||||
version = "3.14.0"
|
||||
|
@ -397,6 +423,20 @@ dependencies = [
|
|||
"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]]
|
||||
name = "clap"
|
||||
version = "4.5.1"
|
||||
|
@ -437,6 +477,19 @@ version = "0.7.0"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
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]]
|
||||
name = "color_quant"
|
||||
version = "1.1.0"
|
||||
|
@ -468,6 +521,19 @@ dependencies = [
|
|||
"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]]
|
||||
name = "const_format"
|
||||
version = "0.2.32"
|
||||
|
@ -528,6 +594,19 @@ dependencies = [
|
|||
"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]]
|
||||
name = "crossbeam-channel"
|
||||
version = "0.5.11"
|
||||
|
@ -556,6 +635,15 @@ dependencies = [
|
|||
"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]]
|
||||
name = "crossbeam-utils"
|
||||
version = "0.8.19"
|
||||
|
@ -577,6 +665,17 @@ dependencies = [
|
|||
"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]]
|
||||
name = "dirs"
|
||||
version = "5.0.1"
|
||||
|
@ -670,6 +769,12 @@ version = "1.9.0"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a26ae43d7bcc3b814de94796a5e736d4029efb0ee900c12e2d54c993ad1a1e07"
|
||||
|
||||
[[package]]
|
||||
name = "encode_unicode"
|
||||
version = "0.3.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a357d28ed41a50f9c765dbfe56cbc04a64e53e5fc58ba79fbc34c10ef3df831f"
|
||||
|
||||
[[package]]
|
||||
name = "encoding_rs"
|
||||
version = "0.8.33"
|
||||
|
@ -1013,6 +1118,29 @@ dependencies = [
|
|||
"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]]
|
||||
name = "icrate"
|
||||
version = "0.0.4"
|
||||
|
@ -1056,6 +1184,19 @@ dependencies = [
|
|||
"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]]
|
||||
name = "input"
|
||||
version = "0.9.0"
|
||||
|
@ -1075,6 +1216,15 @@ version = "1.18.0"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
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]]
|
||||
name = "io-lifetimes"
|
||||
version = "1.0.11"
|
||||
|
@ -1147,6 +1297,16 @@ dependencies = [
|
|||
"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]]
|
||||
name = "khronos_api"
|
||||
version = "3.1.0"
|
||||
|
@ -1451,6 +1611,12 @@ dependencies = [
|
|||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "number_prefix"
|
||||
version = "0.4.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "830b246a0e5f20af87141b25c173cd1b609bd7779a4617d6ec582abaf90870f3"
|
||||
|
||||
[[package]]
|
||||
name = "objc-sys"
|
||||
version = "0.3.2"
|
||||
|
@ -1503,6 +1669,15 @@ dependencies = [
|
|||
"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]]
|
||||
name = "overload"
|
||||
version = "0.1.1"
|
||||
|
@ -1586,7 +1761,10 @@ version = "0.0.1"
|
|||
dependencies = [
|
||||
"anyhow",
|
||||
"bitflags 2.4.2",
|
||||
"chrono",
|
||||
"clap",
|
||||
"cliclack",
|
||||
"dircpy",
|
||||
"image",
|
||||
"nix",
|
||||
"pinnacle-api-defs",
|
||||
|
@ -1667,6 +1845,12 @@ dependencies = [
|
|||
"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]]
|
||||
name = "powerfmt"
|
||||
version = "0.2.0"
|
||||
|
@ -2023,7 +2207,9 @@ version = "3.1.0"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "da03fa3b94cc19e3ebfc88c4229c49d8f08cdbd1228870a45f0ffdf84988e14b"
|
||||
dependencies = [
|
||||
"bstr",
|
||||
"dirs",
|
||||
"os_str_bytes",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
@ -2050,6 +2236,12 @@ version = "1.13.1"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e6ecd384b10a64542d77071bd64bd7b231f4ed5940fba55e98c3de13824cf3d7"
|
||||
|
||||
[[package]]
|
||||
name = "smawk"
|
||||
version = "0.3.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b7c388c1b5e93756d0c740965c41e8822f866621d41acbdf6336a6a168f8840c"
|
||||
|
||||
[[package]]
|
||||
name = "smithay"
|
||||
version = "0.3.0"
|
||||
|
@ -2212,6 +2404,17 @@ dependencies = [
|
|||
"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]]
|
||||
name = "thiserror"
|
||||
version = "1.0.57"
|
||||
|
@ -2565,12 +2768,24 @@ version = "1.0.12"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b"
|
||||
|
||||
[[package]]
|
||||
name = "unicode-linebreak"
|
||||
version = "0.1.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3b09c83c3c29d37506a3e260c08c03743a6bb66a9cd432c6934ab501a190571f"
|
||||
|
||||
[[package]]
|
||||
name = "unicode-segmentation"
|
||||
version = "1.11.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d4c87d22b6e3f4a18d4d40ef354e97c90fcb14dd91d7dc0aa9d8a1172ebf7202"
|
||||
|
||||
[[package]]
|
||||
name = "unicode-width"
|
||||
version = "0.1.11"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e51733f11c9c4f72aa0c160008246859e340b00807569a0da0e7a1079b27ba85"
|
||||
|
||||
[[package]]
|
||||
name = "unicode-xid"
|
||||
version = "0.2.4"
|
||||
|
@ -3271,3 +3486,23 @@ dependencies = [
|
|||
"quote",
|
||||
"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/"
|
||||
|
||||
[workspace.dependencies]
|
||||
# Tokio
|
||||
tokio = { version = "1.36.0", features = ["macros", "rt-multi-thread"]}
|
||||
tokio-stream = { version = "0.1.14", features = ["net"] }
|
||||
|
||||
# gRPC
|
||||
prost = "0.12.3"
|
||||
tonic = "0.11.0"
|
||||
tonic-reflection = "0.11.0"
|
||||
tonic-build = "0.11.0"
|
||||
|
||||
# API definitions
|
||||
pinnacle-api-defs = { path = "./pinnacle-api-defs" }
|
||||
|
||||
# Misc.
|
||||
xkbcommon = "0.7.0"
|
||||
xdg = "2.5.2"
|
||||
|
||||
#################################################################################
|
||||
########################################################################yo😎###########
|
||||
|
||||
[package]
|
||||
name = "pinnacle"
|
||||
|
@ -34,38 +35,42 @@ repository.workspace = true
|
|||
keywords = ["wayland", "compositor", "smithay", "lua"]
|
||||
|
||||
[dependencies]
|
||||
# Smithay
|
||||
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" }
|
||||
|
||||
# Tracing
|
||||
tracing = "0.1.40"
|
||||
tracing-subscriber = { version = "0.3.18", features = ["env-filter", "registry"] }
|
||||
tracing-appender = "0.2.3"
|
||||
|
||||
# Errors
|
||||
anyhow = { version = "1.0.79", features = ["backtrace"] }
|
||||
thiserror = "1.0.57"
|
||||
|
||||
# xcursor stuff
|
||||
xcursor = { version = "0.3.5" }
|
||||
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"] }
|
||||
toml = "0.8.10"
|
||||
shellexpand = "3.1.0"
|
||||
clap = { version = "4.5.1", features = ["derive"] }
|
||||
shellexpand = { version = "3.1.0", features = ["path"] }
|
||||
x11rb = { version = "0.13.0", default-features = false, features = ["composite"] }
|
||||
xkbcommon = { workspace = true }
|
||||
xdg = { workspace = true }
|
||||
sysinfo = "0.30.5"
|
||||
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 }
|
||||
dircpy = "0.3.16"
|
||||
chrono = "0.4.34"
|
||||
|
||||
[build-dependencies]
|
||||
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)
|
||||
- [Out-of-the-box configurations](#out-of-the-box-configurations)
|
||||
- [Custom configuration](#custom-configuration)
|
||||
- [Lua](#lua)
|
||||
- [Lua Language Server completion](#lua-language-server-completion)
|
||||
- [Rust](#rust)
|
||||
- [API References](#api-references)
|
||||
- [Generating a config](#generating-a-config)
|
||||
- [More on configuration and the `metaconfig.toml` file](#more-on-configuration-and-the-metaconfig.toml-file)
|
||||
- [The `metaconfig.toml` file](#the-metaconfig.toml-file)
|
||||
- [Lua Language Server completion](#lua-language-server-completion)
|
||||
- [API references](#api-references)
|
||||
- [Controls](#controls)
|
||||
- [Feature Requests, Bug Reports, Contributions, and Questions](#feature-requests-bug-reports-contributions-and-questions)
|
||||
- [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)
|
||||
for Wayland.
|
||||
|
||||
It can be configured through either Lua or Rust.
|
||||
|
||||
> ### More video examples below!
|
||||
> <details>
|
||||
>
|
||||
|
@ -96,7 +95,9 @@ cargo build [--release]
|
|||
> [!NOTE]
|
||||
> On build, [`build.rs`](build.rs) will:
|
||||
> - 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`
|
||||
|
||||
# Running
|
||||
|
@ -122,14 +123,13 @@ Pinnacle is configured in your choice of Lua or Rust.
|
|||
## Out-of-the-box configurations
|
||||
If you just want to test Pinnacle out without copying stuff to your config directory,
|
||||
run one of the following in the crate root:
|
||||
|
||||
```sh
|
||||
# For a Lua configuration
|
||||
PINNACLE_CONFIG_DIR="./api/lua/examples/default" cargo run
|
||||
# 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
|
||||
cargo run -- -c "./api/lua/examples/default"
|
||||
|
||||
# For a Rust configuration
|
||||
PINNACLE_CONFIG_DIR="./api/rust/examples/default_config" cargo run
|
||||
cargo run -- -c "./api/rust/examples/default_config"
|
||||
```
|
||||
|
||||
## Custom configuration
|
||||
|
@ -137,58 +137,65 @@ PINNACLE_CONFIG_DIR="./api/rust/examples/default_config" cargo run
|
|||
> [!IMPORTANT]
|
||||
> 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 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
|
||||
$PINNACLE_CONFIG_DIR
|
||||
$XDG_CONFIG_HOME/pinnacle
|
||||
~/.config/pinnacle # Only if $XDG_CONFIG_HOME is not defined
|
||||
cargo run -- config gen
|
||||
```
|
||||
|
||||
The `metaconfig.toml` file provides information on what config to run, kill and reload keybinds,
|
||||
and any environment variables you want set. For more details, see the provided
|
||||
[`metaconfig.toml`](api/lua/examples/default/metaconfig.toml) file.
|
||||
This will prompt you to choose a language (Lua or Rust) and directory to put the config in.
|
||||
It will then generate a config at that directory. If Lua is chosen and there are conflicting
|
||||
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
|
||||
For custom configuration in Lua, copy the contents of `~/.local/share/pinnacle/default_config` to
|
||||
`~/.config/pinnacle` (or `$XDG_CONFIG_HOME/pinnacle`):
|
||||
```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.
|
||||
## More on configuration and the `metaconfig.toml` file
|
||||
Pinnacle is configured purely through IPC using [gRPC](https://grpc.io/). This is done through
|
||||
configuration clients that use the [Lua](api/lua) and [Rust](api/rust) interface libraries.
|
||||
|
||||
> If you rename `default_config.lua`, make sure `command` in your `metaconfig.toml` is updated to reflect that.
|
||||
> If it isn't, the compositor will load the default config instead.
|
||||
As the compositor has no direct integration with these clients, it must know what it needs to run
|
||||
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
|
||||
and will set the correct workspace library files for use with the
|
||||
[Lua language server](https://github.com/LuaLS/lua-language-server).
|
||||
|
||||
### Rust
|
||||
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
|
||||
## API references
|
||||
<b>Lua: https://pinnacle-comp.github.io/lua-reference/main.<br>
|
||||
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/protocol");
|
||||
|
||||
|
@ -6,51 +8,60 @@ fn main() {
|
|||
|
||||
let proto_dir = xdg.place_data_file("protobuf").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 copy_protos = format!("cp -r ./api/protocol {proto_dir:?}");
|
||||
|
||||
let remove_default_config = format!("rm -r {default_config_dir:?}");
|
||||
let copy_default_config = format!("cp -r ./api/lua/examples/default {default_config_dir:?}");
|
||||
let remove_default_config_dir = format!("rm -r {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(&remove_protos)
|
||||
.spawn()
|
||||
.unwrap()
|
||||
.wait()
|
||||
.unwrap();
|
||||
.spawn()?
|
||||
.wait()?;
|
||||
|
||||
std::process::Command::new("/bin/sh")
|
||||
Command::new("/bin/sh")
|
||||
.arg("-c")
|
||||
.arg(©_protos)
|
||||
.spawn()
|
||||
.unwrap()
|
||||
.wait()
|
||||
.unwrap();
|
||||
.spawn()?
|
||||
.wait()?;
|
||||
|
||||
std::process::Command::new("/bin/sh")
|
||||
Command::new("/bin/sh")
|
||||
.arg("-c")
|
||||
.arg(&remove_default_config)
|
||||
.spawn()
|
||||
.unwrap()
|
||||
.wait()
|
||||
.unwrap();
|
||||
.arg(&remove_default_config_dir)
|
||||
.spawn()?
|
||||
.wait()?;
|
||||
|
||||
std::process::Command::new("/bin/sh")
|
||||
std::fs::create_dir_all(&default_config_dir)?;
|
||||
|
||||
Command::new("/bin/sh")
|
||||
.arg("-c")
|
||||
.arg(©_default_config)
|
||||
.spawn()
|
||||
.unwrap()
|
||||
.wait()
|
||||
.unwrap();
|
||||
.arg(©_default_lua_config)
|
||||
.spawn()?
|
||||
.wait()?;
|
||||
|
||||
Command::new("/bin/sh")
|
||||
.arg("-c")
|
||||
.arg(©_default_rust_config)
|
||||
.spawn()?
|
||||
.wait()?;
|
||||
|
||||
std::env::set_current_dir("api/lua").unwrap();
|
||||
std::process::Command::new("luarocks")
|
||||
Command::new("luarocks")
|
||||
.arg("make")
|
||||
.arg("--local")
|
||||
.spawn()
|
||||
.expect("Luarocks is not installed")
|
||||
.wait()
|
||||
.unwrap();
|
||||
.wait()?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
|
|
@ -3,7 +3,7 @@
|
|||
use std::{
|
||||
collections::{HashMap, HashSet},
|
||||
ffi::OsString,
|
||||
path::Path,
|
||||
path::{Path, PathBuf},
|
||||
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 display = Display::new()?;
|
||||
|
||||
|
@ -283,6 +283,8 @@ pub fn run_udev() -> anyhow::Result<()> {
|
|||
display,
|
||||
event_loop.get_signal(),
|
||||
event_loop.handle(),
|
||||
no_config,
|
||||
config_dir,
|
||||
)?;
|
||||
|
||||
// Initialize the udev backend
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
use std::{ffi::OsString, time::Duration};
|
||||
use std::{ffi::OsString, path::PathBuf, time::Duration};
|
||||
|
||||
use smithay::{
|
||||
backend::{
|
||||
|
@ -62,7 +62,7 @@ impl Backend {
|
|||
}
|
||||
|
||||
/// 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 display: Display<State> = Display::new()?;
|
||||
|
@ -153,16 +153,20 @@ pub fn run_winit() -> anyhow::Result<()> {
|
|||
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(
|
||||
Backend::Winit(Winit {
|
||||
backend: winit_backend,
|
||||
damage_tracker: OutputDamageTracker::from_output(&output),
|
||||
dmabuf_state,
|
||||
full_redraw: 0,
|
||||
}),
|
||||
backend,
|
||||
display,
|
||||
event_loop.get_signal(),
|
||||
evt_loop_handle,
|
||||
no_config,
|
||||
config_dir,
|
||||
)?;
|
||||
|
||||
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 toml::Table;
|
||||
|
||||
use tracing::{debug, error, info};
|
||||
use xdg::BaseDirectories;
|
||||
use xkbcommon::xkb::Keysym;
|
||||
|
||||
|
@ -171,10 +172,27 @@ pub struct Config {
|
|||
|
||||
config_join_handle: Option<JoinHandle<()>>,
|
||||
config_reload_on_crash_token: Option<RegistrationToken>,
|
||||
|
||||
pub no_config: bool,
|
||||
config_dir: Option<PathBuf>,
|
||||
}
|
||||
|
||||
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.connector_saved_states.clear();
|
||||
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,
|
||||
/// 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")
|
||||
.ok()
|
||||
.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.
|
||||
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.get_data_file("default_config");
|
||||
let default_lua_config_dir = self
|
||||
.xdg_base_dirs
|
||||
.get_data_file("default_config")
|
||||
.join("lua");
|
||||
|
||||
let load_default_config = |state: &mut State, reason: &str| {
|
||||
tracing::error!(
|
||||
error!(
|
||||
"Unable to load config at {}: {reason}",
|
||||
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)
|
||||
};
|
||||
|
||||
// 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) {
|
||||
Ok(metaconfig) => metaconfig,
|
||||
Err(err) => {
|
||||
// Stops infinite recursion if somehow the default_config dir is screwed up
|
||||
if config_dir == default_lua_config_dir {
|
||||
tracing::error!("The metaconfig at the default Lua config directory is either malformed or missing.");
|
||||
tracing::error!(
|
||||
error!("The metaconfig at the default Lua config directory is either malformed or missing.");
|
||||
error!(
|
||||
"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()
|
||||
);
|
||||
|
@ -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() {
|
||||
output.with_state(|state| state.tags.clear());
|
||||
}
|
||||
|
||||
TagId::reset();
|
||||
|
||||
tracing::debug!("Clearing input state");
|
||||
debug!("Clearing input state");
|
||||
|
||||
self.input_state.clear();
|
||||
|
||||
|
@ -293,9 +338,6 @@ impl State {
|
|||
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 arg0 = match command.next() {
|
||||
|
@ -305,32 +347,26 @@ impl State {
|
|||
|
||||
let command = command.collect::<Vec<_>>();
|
||||
|
||||
tracing::debug!(arg0, ?command);
|
||||
debug!(arg0, ?command);
|
||||
|
||||
let envs = metaconfig
|
||||
.envs
|
||||
.unwrap_or(toml::map::Map::new())
|
||||
.into_iter()
|
||||
.filter_map(|(key, val)| {
|
||||
.map(|(key, val)| -> anyhow::Result<Option<(String, String)>> {
|
||||
if let toml::Value::String(string) = val {
|
||||
Some((
|
||||
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(),
|
||||
))
|
||||
Ok(Some((key, shellexpand::full(&string)?.to_string())))
|
||||
} 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)
|
||||
.args(command)
|
||||
|
@ -345,23 +381,14 @@ impl State {
|
|||
Err(err) => return load_default_config(self, &err.to_string()),
|
||||
};
|
||||
|
||||
tracing::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);
|
||||
info!("Started config with {:?}", metaconfig.command);
|
||||
|
||||
let (pinger, ping_source) = calloop::ping::make_ping()?;
|
||||
|
||||
let token = self
|
||||
.loop_handle
|
||||
.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
|
||||
.start_config(&default_lua_config_dir)
|
||||
.expect("failed to start default lua config");
|
||||
|
@ -422,7 +449,7 @@ impl State {
|
|||
.starts_with("pinnacle-grpc")
|
||||
})
|
||||
{
|
||||
tracing::debug!("Removing socket at {:?}", file.path());
|
||||
debug!("Removing socket at {:?}", file.path());
|
||||
std::fs::remove_file(file.path())
|
||||
.context(format!("Failed to remove old socket at {:?}", file.path()))?;
|
||||
}
|
||||
|
@ -439,7 +466,7 @@ impl State {
|
|||
self.loop_handle
|
||||
.insert_source(grpc_receiver, |msg, _, state| match msg {
|
||||
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");
|
||||
|
||||
|
@ -474,7 +501,7 @@ impl State {
|
|||
Some(_) => {
|
||||
self.grpc_server_join_handle = Some(tokio::spawn(async move {
|
||||
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| {
|
||||
state.grpc_server_join_handle = Some(tokio::spawn(async move {
|
||||
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;
|
||||
|
||||
#[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";
|
||||
|
||||
temp_env::with_var("PINNACLE_CONFIG_DIR", Some(relative_path), || {
|
||||
|
@ -519,7 +546,7 @@ mod tests {
|
|||
}
|
||||
|
||||
#[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/"), || {
|
||||
let xdg_base_dirs = BaseDirectories::with_prefix("pinnacle")?;
|
||||
let expected = PathBuf::from(var("HOME")?).join("some/dir/somewhere");
|
||||
|
@ -531,7 +558,7 @@ mod tests {
|
|||
}
|
||||
|
||||
#[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";
|
||||
|
||||
temp_env::with_var("PINNACLE_CONFIG_DIR", Some(absolute_path), || {
|
||||
|
@ -545,7 +572,7 @@ mod tests {
|
|||
}
|
||||
|
||||
#[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";
|
||||
|
||||
temp_env::with_vars(
|
||||
|
@ -565,7 +592,7 @@ mod tests {
|
|||
}
|
||||
|
||||
#[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(
|
||||
[
|
||||
("PINNACLE_CONFIG_DIR", None::<&str>),
|
||||
|
@ -685,4 +712,26 @@ mod tests {
|
|||
|
||||
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();
|
||||
}
|
||||
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");
|
||||
}
|
||||
None => (),
|
||||
|
|
60
src/main.rs
60
src/main.rs
|
@ -12,7 +12,7 @@
|
|||
#![warn(clippy::unwrap_used)]
|
||||
|
||||
use anyhow::Context;
|
||||
use clap::Parser;
|
||||
use cli::Cli;
|
||||
use nix::unistd::Uid;
|
||||
use tracing::{info, level_filters::LevelFilter, warn};
|
||||
use tracing_appender::rolling::Rotation;
|
||||
|
@ -21,6 +21,7 @@ use xdg::BaseDirectories;
|
|||
|
||||
mod api;
|
||||
mod backend;
|
||||
mod cli;
|
||||
mod config;
|
||||
mod cursor;
|
||||
mod focus;
|
||||
|
@ -34,30 +35,6 @@ mod state;
|
|||
mod tag;
|
||||
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]
|
||||
async fn main() -> anyhow::Result<()> {
|
||||
let xdg_state_dir = BaseDirectories::with_prefix("pinnacle")?.get_state_home();
|
||||
|
@ -96,13 +73,15 @@ async fn main() -> anyhow::Result<()> {
|
|||
.with(stdout_layer)
|
||||
.init();
|
||||
|
||||
let args = Args::parse();
|
||||
let Some(cli) = Cli::parse_and_prompt() else {
|
||||
return Ok(());
|
||||
};
|
||||
|
||||
if Uid::effective().is_root() {
|
||||
if !args.allow_root {
|
||||
if !cli.allow_root {
|
||||
warn!("You are trying to run Pinnacle as root.");
|
||||
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.");
|
||||
return Ok(());
|
||||
} else {
|
||||
|
@ -118,48 +97,47 @@ async fn main() -> anyhow::Result<()> {
|
|||
warn!("You may see LOTS of file descriptors open under Pinnacle.");
|
||||
}
|
||||
|
||||
match (args.backend.winit, args.backend.udev, args.force) {
|
||||
(false, false, _) => {
|
||||
match (cli.backend, cli.force) {
|
||||
(None, _) => {
|
||||
if in_graphical_env {
|
||||
info!("Starting winit backend");
|
||||
crate::backend::winit::run_winit()?;
|
||||
crate::backend::winit::run_winit(cli.no_config, cli.config_dir)?;
|
||||
} else {
|
||||
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 force {
|
||||
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 {
|
||||
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 really want to, additionally pass in the --force flag.");
|
||||
warn!("If you really want to, additionally pass in the `--force` flag.");
|
||||
}
|
||||
} else {
|
||||
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 force {
|
||||
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 {
|
||||
warn!("WAYLAND_DISPLAY and/or DISPLAY are set.");
|
||||
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!("If you really want to, additionally pass in the --force flag.");
|
||||
warn!("If you really want to, additionally pass in the `--force` flag.");
|
||||
}
|
||||
} else {
|
||||
info!("Starting udev backend");
|
||||
crate::backend::udev::run_udev()?;
|
||||
crate::backend::udev::run_udev(cli.no_config, cli.config_dir)?;
|
||||
}
|
||||
}
|
||||
_ => unreachable!(),
|
||||
}
|
||||
|
||||
Ok(())
|
||||
|
|
23
src/state.rs
23
src/state.rs
|
@ -34,8 +34,9 @@ use smithay::{
|
|||
},
|
||||
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 tracing::{error, info};
|
||||
use xdg::BaseDirectories;
|
||||
|
||||
use crate::input::InputState;
|
||||
|
@ -108,11 +109,13 @@ impl State {
|
|||
display: Display<Self>,
|
||||
loop_signal: LoopSignal,
|
||||
loop_handle: LoopHandle<'static, Self>,
|
||||
no_config: bool,
|
||||
config_dir: Option<PathBuf>,
|
||||
) -> anyhow::Result<Self> {
|
||||
let socket = ListeningSocketSource::new_auto()?;
|
||||
let socket_name = socket.socket_name().to_os_string();
|
||||
|
||||
tracing::info!(
|
||||
info!(
|
||||
"Setting WAYLAND_DISPLAY to {}",
|
||||
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
|
||||
// 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(
|
||||
nix::sys::resource::Resource::RLIMIT_NOFILE,
|
||||
65536,
|
||||
65536 * 2,
|
||||
) {
|
||||
tracing::error!("Could not raise fd limit: errno {err}");
|
||||
error!("Could not raise fd limit: errno {err}");
|
||||
} else {
|
||||
tracing::info!("Fd raise success!");
|
||||
info!("Fd raise success!");
|
||||
}
|
||||
|
||||
loop_handle.insert_source(socket, |stream, _metadata, data| {
|
||||
|
@ -158,9 +161,7 @@ impl State {
|
|||
)?;
|
||||
|
||||
loop_handle.insert_idle(|state| {
|
||||
if let Err(err) =
|
||||
state.start_config(crate::config::get_config_dir(&state.xdg_base_dirs))
|
||||
{
|
||||
if let Err(err) = state.start_config(state.config.dir(&state.xdg_base_dirs)) {
|
||||
panic!("failed to start config: {err}");
|
||||
}
|
||||
});
|
||||
|
@ -212,7 +213,7 @@ impl State {
|
|||
}
|
||||
});
|
||||
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
|
||||
};
|
||||
|
@ -254,7 +255,7 @@ impl State {
|
|||
output_focus_stack: FocusStack::default(),
|
||||
z_index_stack: FocusStack::default(),
|
||||
|
||||
config: Config::default(),
|
||||
config: Config::new(no_config, config_dir),
|
||||
|
||||
seat,
|
||||
|
||||
|
@ -302,7 +303,7 @@ impl State {
|
|||
}
|
||||
|
||||
pub fn shutdown(&self) {
|
||||
tracing::info!("Shutting down Pinnacle");
|
||||
info!("Shutting down Pinnacle");
|
||||
self.loop_signal.stop();
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue