From 668188ff43dd99caddb57984122c25ea79c67bbd Mon Sep 17 00:00:00 2001 From: Julian Orth Date: Fri, 20 Mar 2026 18:49:39 +0100 Subject: [PATCH] cli: add config subcommand --- docs/features.md | 7 ++- src/cli.rs | 6 ++- src/cli/config.rs | 106 +++++++++++++++++++++++++++++++++++++++++ toml-config/src/lib.rs | 4 +- 4 files changed, 119 insertions(+), 4 deletions(-) create mode 100644 src/cli/config.rs diff --git a/docs/features.md b/docs/features.md index 8daca46f..f5999e95 100644 --- a/docs/features.md +++ b/docs/features.md @@ -37,6 +37,7 @@ Usage: jay [OPTIONS] Commands: run Run the compositor + config Create/modify the toml config generate-completion Generate shell completion scripts for jay log Open the log file set-log-level Sets the log level @@ -45,6 +46,7 @@ Commands: screenshot Take a screenshot idle Inspect/modify the idle (screensaver) settings run-privileged Run a privileged program + run-tagged Run a program with a connection tag seat-test Tests the events produced by a seat portal Run the desktop portal randr Inspect/modify graphics card and connector settings @@ -53,10 +55,13 @@ Commands: color-management Inspect/modify the color-management settings clients Inspect/manipulate the connected clients tree Inspect the surface tree + control-center Opens the control center + version Prints the Jay version and exits + pid Prints the Jay PID and exits help Print this message or the help of the given subcommand(s) Options: - --log-level The log level [default: info] [possible values: trace, debug, info, warn, error] + --log-level The log level [default: info] [possible values: trace, debug, info, warn, error, off] -h, --help Print help ``` diff --git a/src/cli.rs b/src/cli.rs index 00c66e15..3a4608e2 100644 --- a/src/cli.rs +++ b/src/cli.rs @@ -1,6 +1,7 @@ mod clients; mod color; mod color_management; +mod config; mod control_center; mod damage_tracking; mod duration; @@ -25,7 +26,7 @@ mod xwayland; use { crate::{ cli::{ - clients::ClientsArgs, color_management::ColorManagementArgs, + clients::ClientsArgs, color_management::ColorManagementArgs, config::ConfigArgs, damage_tracking::DamageTrackingArgs, idle::IdleCmd, input::InputArgs, randr::RandrArgs, reexec::ReexecArgs, run_tagged::RunTaggedArgs, tree::TreeArgs, xwayland::XwaylandArgs, }, @@ -58,6 +59,8 @@ pub struct GlobalArgs { pub enum Cmd { /// Run the compositor. Run(RunArgs), + /// Create/modify the toml config. + Config(ConfigArgs), /// Generate shell completion scripts for jay. GenerateCompletion(GenerateArgs), /// Open the log file. @@ -248,5 +251,6 @@ pub fn main() { Cmd::RunTests => crate::it::run_tests(), Cmd::Reexec(a) => reexec::main(cli.global, a), Cmd::ControlCenter => control_center::main(cli.global), + Cmd::Config(a) => config::main(cli.global, a), } } diff --git a/src/cli/config.rs b/src/cli/config.rs new file mode 100644 index 00000000..f6aaa3da --- /dev/null +++ b/src/cli/config.rs @@ -0,0 +1,106 @@ +use { + crate::{cli::GlobalArgs, compositor::config_dir, logger::Logger, utils::errorfmt::ErrorFmt}, + clap::{Args, Subcommand}, + jay_toml_config::CONFIG_TOML, + std::path::Path, + uapi::{UstrPtr, c}, +}; + +#[derive(Args, Debug)] +pub struct ConfigArgs { + #[clap(subcommand)] + pub command: ConfigCmd, +} + +#[derive(Subcommand, Debug)] +pub enum ConfigCmd { + /// Initialize the toml config file. + Init(ConfigInitArgs), + /// Print the path to the config. + Path, + /// Open the config directory with xdg-open. + OpenDir, +} + +#[derive(Args, Debug)] +pub struct ConfigInitArgs { + /// Overwrite an existing config.toml file. + /// + /// The old file will be backed up to `config.toml.`. + #[clap(long)] + overwrite: bool, +} + +pub fn main(global: GlobalArgs, args: ConfigArgs) { + Logger::install_stderr(global.log_level); + let dir = || { + let Some(dir) = config_dir() else { + fatal!("Could not determine the config directory"); + }; + dir + }; + match args.command { + ConfigCmd::Init(a) => { + let dir = dir(); + if let Err(e) = std::fs::create_dir_all(&dir) { + fatal!("Could not create config directory: {}", ErrorFmt(e)); + } + let toml_path = Path::new(&dir).join(CONFIG_TOML); + let mut write_real_path = toml_path.clone(); + if matches!(std::fs::exists(&toml_path), Ok(true)) { + if !a.overwrite { + eprintln!("{} already exists", toml_path.display()); + eprintln!("Pass --overwrite to overwrite the config file"); + return; + } + for i in 1.. { + write_real_path = toml_path.with_added_extension(i.to_string()); + if matches!(std::fs::exists(&write_real_path), Ok(false)) { + break; + } + } + } + let res = std::fs::write(&write_real_path, jay_toml_config::DEFAULT); + if let Err(e) = res { + fatal!("Could not write config: {}", ErrorFmt(e)); + } + if write_real_path != toml_path { + let res = uapi::renameat2( + c::AT_FDCWD, + &*toml_path, + c::AT_FDCWD, + &*write_real_path, + c::RENAME_EXCHANGE, + ); + if let Err(e) = res { + fatal!( + "Could not exchange {} and {}: {}", + toml_path.display(), + write_real_path.display(), + ErrorFmt(e), + ); + } + eprintln!("Backed up old config.toml to {}", write_real_path.display()); + } + eprintln!("Config written to {}", toml_path.display()); + } + ConfigCmd::Path => { + let dir = dir(); + let toml_path = Path::new(&dir).join(CONFIG_TOML); + println!("{}", toml_path.display()); + } + ConfigCmd::OpenDir => { + const XDG_OPEN: &str = "xdg-open"; + let dir = dir(); + let mut args = UstrPtr::new(); + args.push(XDG_OPEN); + args.push(&*dir); + if matches!(std::fs::exists(&dir), Ok(false)) { + fatal!("Use `jay config init` to initialize the config first"); + } + if let Err(e) = uapi::execvp(XDG_OPEN, &args) { + fatal!("Could not start xdg-open: {}", ErrorFmt(e)); + } + } + } +} diff --git a/toml-config/src/lib.rs b/toml-config/src/lib.rs index 322d451f..87fe1689 100644 --- a/toml-config/src/lib.rs +++ b/toml-config/src/lib.rs @@ -1298,7 +1298,7 @@ async fn watch_config(persistent: Rc) { } } -const CONFIG_TOML: &str = "config.toml"; +pub const CONFIG_TOML: &str = "config.toml"; fn load_config(initial_load: bool, auto_reload: bool, persistent: &Rc) { let mut path = PathBuf::from(config_dir()); @@ -1675,7 +1675,7 @@ fn create_command(exec: &Exec) -> Command { command } -const DEFAULT: &[u8] = include_bytes!("default-config.toml"); +pub const DEFAULT: &[u8] = include_bytes!("default-config.toml"); pub fn configure() { let mark_names = Default::default();