diff --git a/README.md b/README.md index 618e2a0..8d97aea 100644 --- a/README.md +++ b/README.md @@ -13,6 +13,8 @@ Options: -d, --debug [FILE] enable debug logging to the provided file, or to /tmp/tuigreet.log -c, --cmd COMMAND command to run + --env KEY=VALUE environment variables to run the default session with + (can appear more than once) -s, --sessions DIRS colon-separated list of Wayland session paths --session-wrapper 'CMD [ARGS]...' wrapper command to initialize the non-X11 session @@ -40,9 +42,7 @@ Options: minimum UID to display in the user selection menu --user-menu-max-uid UID maximum UID to display in the user selection menu - --theme SPEC - Add visual feedback when typing secrets, as one asterisk character for every - keystroke. By default, no feedback is given at all. + --theme THEME define the application theme colors --asterisks display asterisks when a secret is typed --asterisks-char CHARS characters to be used to redact secrets (default: *) @@ -52,15 +52,21 @@ Options: padding inside the main prompt container (default: 1) --prompt-padding PADDING padding between prompt rows (default: 1) + --greet-align [left|center|right] + alignment of the greeting text in the main prompt + container (default: 'center') --power-shutdown 'CMD [ARGS]...' command to run to shut down the system --power-reboot 'CMD [ARGS]...' command to run to reboot the system --power-no-setsid do not prefix power commands with setsid - --kb-[command|sessions|power] [1-12] - change the default F-key keybindings to access the - command, sessions and power menus. + --kb-command [1-12] + F-key to use to open the command menu + --kb-sessions [1-12] + F-key to use to open the sessions menu + --kb-power [1-12] + F-key to use to open the power menu ``` ## Usage diff --git a/contrib/man/tuigreet-1.scd b/contrib/man/tuigreet-1.scd index 8fde4f8..b762764 100644 --- a/contrib/man/tuigreet-1.scd +++ b/contrib/man/tuigreet-1.scd @@ -24,6 +24,9 @@ tuigreet - A graphical console greeter for greetd Specify which command to run on successful authentication. This can be overridden by manual selection within *tuigreet*. +*--env KEY=VALUE* + Environment variables to run the default session with (can appear more then once). + *-s, --sessions DIR1[:DIR2]...* Location of desktop-files to be used as Wayland session definitions. By default, Wayland sessions are fetched from */usr/share/wayland-sessions*. diff --git a/src/greeter.rs b/src/greeter.rs index 295a115..e5e940f 100644 --- a/src/greeter.rs +++ b/src/greeter.rs @@ -339,6 +339,13 @@ impl Greeter { self.config().opt_str(name) } + pub fn options_multi(&self, name: &str) -> Option> { + match self.config().opt_present(name) { + true => Some(self.config().opt_strs(name)), + false => None, + } + } + // Returns the width of the main window where content is displayed from the // provided arguments. pub fn width(&self) -> u16 { @@ -419,6 +426,7 @@ impl Greeter { opts.optflag("v", "version", "print version information"); opts.optflagopt("d", "debug", "enable debug logging to the provided file, or to /tmp/tuigreet.log", "FILE"); opts.optopt("c", "cmd", "command to run", "COMMAND"); + opts.optmulti("", "env", "environment variables to run the default session with (can appear more than once)", "KEY=VALUE"); opts.optopt("s", "sessions", "colon-separated list of Wayland session paths", "DIRS"); opts.optopt("", "session-wrapper", "wrapper command to initialize the non-X11 session", "'CMD [ARGS]...'"); opts.optopt("x", "xsessions", "colon-separated list of X11 session paths", "DIRS"); @@ -559,7 +567,7 @@ impl Greeter { // If the `--cmd` argument is provided, it will override the selected session. if let Some(command) = self.option("cmd") { - self.session_source = SessionSource::Command(command); + self.session_source = SessionSource::DefaultCommand(command, self.options_multi("env")); } if let Some(dirs) = self.option("sessions") { diff --git a/src/ipc.rs b/src/ipc.rs index b1171ec..11212e3 100644 --- a/src/ipc.rs +++ b/src/ipc.rs @@ -172,7 +172,8 @@ impl Ipc { greeter.mode = Mode::Processing; let session = Session::get_selected(greeter); - let (command, env) = wrap_session_command(greeter, session, &command); + let default = DefaultCommand(&command, greeter.session_source.env()); + let (command, env) = wrap_session_command(greeter, session, &default); #[cfg(not(debug_assertions))] self.send(Request::StartSession { cmd: vec![command.to_string()], env }).await; @@ -180,14 +181,8 @@ impl Ipc { #[cfg(debug_assertions)] { let _ = command; - let _ = env; - - self - .send(Request::StartSession { - cmd: vec!["true".to_string()], - env: vec![], - }) - .await; + + self.send(Request::StartSession { cmd: vec!["true".to_string()], env }).await; } } } @@ -234,39 +229,64 @@ fn desktop_names_to_xdg(names: &str) -> String { names.replace(';', ":").trim_end_matches(':').to_string() } -fn wrap_session_command<'a>(greeter: &Greeter, session: Option<&Session>, command: &'a str) -> (Cow<'a, str>, Vec) { +struct DefaultCommand<'a>(&'a str, Option>); + +impl<'a> DefaultCommand<'a> { + fn command(&'a self) -> &'a str { + self.0 + } + + fn env(&'a self) -> Option<&'a Vec> { + self.1.as_ref() + } +} + +fn wrap_session_command<'a>(greeter: &Greeter, session: Option<&Session>, default: &'a DefaultCommand<'a>) -> (Cow<'a, str>, Vec) { let mut env: Vec = vec![]; - if let Some(Session { - slug, - session_type, - xdg_desktop_names, - .. - }) = session - { - if let Some(slug) = slug { - env.push(format!("XDG_SESSION_DESKTOP={slug}")); - env.push(format!("DESKTOP_SESSION={slug}")); - } - if *session_type != SessionType::None { - env.push(format!("XDG_SESSION_TYPE={}", session_type.as_xdg_session_type())); - } - if let Some(xdg_desktop_names) = xdg_desktop_names { - env.push(format!("XDG_CURRENT_DESKTOP={}", desktop_names_to_xdg(xdg_desktop_names))); + match session { + // If the target is a defined session, we should be able to deduce all the + // environment we need from the desktop file. + Some(Session { + slug, + session_type, + xdg_desktop_names, + .. + }) => { + if let Some(slug) = slug { + env.push(format!("XDG_SESSION_DESKTOP={slug}")); + env.push(format!("DESKTOP_SESSION={slug}")); + } + if *session_type != SessionType::None { + env.push(format!("XDG_SESSION_TYPE={}", session_type.as_xdg_session_type())); + } + if let Some(xdg_desktop_names) = xdg_desktop_names { + env.push(format!("XDG_CURRENT_DESKTOP={}", desktop_names_to_xdg(xdg_desktop_names))); + } + + if *session_type == SessionType::X11 { + if let Some(ref wrap) = greeter.xsession_wrapper { + return (Cow::Owned(format!("{} {}", wrap, default.command())), env); + } + } else if let Some(ref wrap) = greeter.session_wrapper { + return (Cow::Owned(format!("{} {}", wrap, default.command())), env); + } } - if *session_type == SessionType::X11 { - if let Some(ref wrap) = greeter.xsession_wrapper { - return (Cow::Owned(format!("{} {}", wrap, command)), env); + _ => { + // If a wrapper script is used, assume that it is able to set up the + // required environment. + if let Some(ref wrap) = greeter.session_wrapper { + return (Cow::Owned(format!("{} {}", wrap, default.command())), env); + } + // Otherwise, set up the environment from the provided argument. + if let Some(base_env) = default.env() { + env.append(&mut base_env.clone()); } - } else if let Some(ref wrap) = greeter.session_wrapper { - return (Cow::Owned(format!("{} {}", wrap, command)), env); } - } else if let Some(ref wrap) = greeter.session_wrapper { - return (Cow::Owned(format!("{} {}", wrap, command)), env); } - (Cow::Borrowed(command), env) + (Cow::Borrowed(default.command()), env) } #[cfg(test)] @@ -274,7 +294,7 @@ mod test { use std::path::PathBuf; use crate::{ - ipc::desktop_names_to_xdg, + ipc::{desktop_names_to_xdg, DefaultCommand}, ui::sessions::{Session, SessionType}, Greeter, }; @@ -293,7 +313,8 @@ mod test { ..Default::default() }; - let (command, env) = wrap_session_command(&greeter, Some(&session), &session.command); + let default = DefaultCommand(&session.command, None); + let (command, env) = wrap_session_command(&greeter, Some(&session), &default); assert_eq!(command.as_ref(), "Session1Cmd"); assert_eq!(env, vec!["XDG_SESSION_TYPE=wayland"]); @@ -312,7 +333,8 @@ mod test { ..Default::default() }; - let (command, env) = wrap_session_command(&greeter, Some(&session), &session.command); + let default = DefaultCommand(&session.command, None); + let (command, env) = wrap_session_command(&greeter, Some(&session), &default); assert_eq!(command.as_ref(), "/wrapper.sh Session1Cmd"); assert_eq!(env, vec!["XDG_SESSION_TYPE=wayland"]); @@ -333,7 +355,8 @@ mod test { ..Default::default() }; - let (command, env) = wrap_session_command(&greeter, Some(&session), &session.command); + let default = DefaultCommand(&session.command, None); + let (command, env) = wrap_session_command(&greeter, Some(&session), &default); assert_eq!(command.as_ref(), "startx /usr/bin/env Session1Cmd"); assert_eq!( diff --git a/src/ui/sessions.rs b/src/ui/sessions.rs index e014855..65f6ba7 100644 --- a/src/ui/sessions.rs +++ b/src/ui/sessions.rs @@ -17,6 +17,7 @@ use super::common::menu::MenuItem; pub enum SessionSource { #[default] None, + DefaultCommand(String, Option>), Command(String), Session(usize), } @@ -29,6 +30,7 @@ impl SessionSource { pub fn label<'g, 'ss: 'g>(&'ss self, greeter: &'g Greeter) -> Option<&'g str> { match self { SessionSource::None => None, + SessionSource::DefaultCommand(command, _) => Some(command), SessionSource::Command(command) => Some(command), SessionSource::Session(index) => greeter.sessions.options.get(*index).map(|session| session.name.as_str()), } @@ -39,10 +41,20 @@ impl SessionSource { pub fn command<'g, 'ss: 'g>(&'ss self, greeter: &'g Greeter) -> Option<&'g str> { match self { SessionSource::None => None, + SessionSource::DefaultCommand(command, _) => Some(command.as_str()), SessionSource::Command(command) => Some(command.as_str()), SessionSource::Session(index) => greeter.sessions.options.get(*index).map(|session| session.command.as_str()), } } + + pub fn env<'g, 'ss: 'g>(&'ss self) -> Option> { + match self { + SessionSource::None => None, + SessionSource::DefaultCommand(_, env) => env.clone(), + SessionSource::Command(_) => None, + SessionSource::Session(_) => None, + } + } } // Represents the XDG type of the selected session.