Add option to specify environment for default session (#153)

Add argument to set the environment to run the default command with (#148).
This commit is contained in:
Antoine POPINEAU 2024-08-16 22:37:52 +02:00 committed by GitHub
parent ca4161b18f
commit 359410f67f
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
6 changed files with 128 additions and 54 deletions

View File

@ -13,6 +13,8 @@ Options:
-d, --debug [FILE] enable debug logging to the provided file, or to -d, --debug [FILE] enable debug logging to the provided file, or to
/tmp/tuigreet.log /tmp/tuigreet.log
-c, --cmd COMMAND command to run -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 -s, --sessions DIRS colon-separated list of Wayland session paths
--session-wrapper 'CMD [ARGS]...' --session-wrapper 'CMD [ARGS]...'
wrapper command to initialize the non-X11 session wrapper command to initialize the non-X11 session
@ -40,9 +42,7 @@ Options:
minimum UID to display in the user selection menu minimum UID to display in the user selection menu
--user-menu-max-uid UID --user-menu-max-uid UID
maximum UID to display in the user selection menu maximum UID to display in the user selection menu
--theme SPEC --theme THEME define the application theme colors
Add visual feedback when typing secrets, as one asterisk character for every
keystroke. By default, no feedback is given at all.
--asterisks display asterisks when a secret is typed --asterisks display asterisks when a secret is typed
--asterisks-char CHARS --asterisks-char CHARS
characters to be used to redact secrets (default: *) characters to be used to redact secrets (default: *)
@ -52,15 +52,21 @@ Options:
padding inside the main prompt container (default: 1) padding inside the main prompt container (default: 1)
--prompt-padding PADDING --prompt-padding PADDING
padding between prompt rows (default: 1) 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]...' --power-shutdown 'CMD [ARGS]...'
command to run to shut down the system command to run to shut down the system
--power-reboot 'CMD [ARGS]...' --power-reboot 'CMD [ARGS]...'
command to run to reboot the system command to run to reboot the system
--power-no-setsid --power-no-setsid
do not prefix power commands with setsid do not prefix power commands with setsid
--kb-[command|sessions|power] [1-12] --kb-command [1-12]
change the default F-key keybindings to access the F-key to use to open the command menu
command, sessions and power menus. --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 ## Usage

View File

@ -24,6 +24,9 @@ tuigreet - A graphical console greeter for greetd
Specify which command to run on successful authentication. This can be Specify which command to run on successful authentication. This can be
overridden by manual selection within *tuigreet*. 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]...* *-s, --sessions DIR1[:DIR2]...*
Location of desktop-files to be used as Wayland session definitions. By Location of desktop-files to be used as Wayland session definitions. By
default, Wayland sessions are fetched from */usr/share/wayland-sessions*. default, Wayland sessions are fetched from */usr/share/wayland-sessions*.

View File

@ -339,6 +339,13 @@ impl Greeter {
self.config().opt_str(name) self.config().opt_str(name)
} }
pub fn options_multi(&self, name: &str) -> Option<Vec<String>> {
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 // Returns the width of the main window where content is displayed from the
// provided arguments. // provided arguments.
pub fn width(&self) -> u16 { pub fn width(&self) -> u16 {
@ -419,6 +426,7 @@ impl Greeter {
opts.optflag("v", "version", "print version information"); 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.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.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("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("", "session-wrapper", "wrapper command to initialize the non-X11 session", "'CMD [ARGS]...'");
opts.optopt("x", "xsessions", "colon-separated list of X11 session paths", "DIRS"); opts.optopt("x", "xsessions", "colon-separated list of X11 session paths", "DIRS");
@ -559,7 +567,17 @@ impl Greeter {
// If the `--cmd` argument is provided, it will override the selected session. // If the `--cmd` argument is provided, it will override the selected session.
if let Some(command) = self.option("cmd") { if let Some(command) = self.option("cmd") {
self.session_source = SessionSource::Command(command); let envs = self.options_multi("env");
if let Some(envs) = envs {
for env in envs {
if !env.contains('=') {
return Err(format!("malformed environment variable definition for '{env}'").into());
}
}
}
self.session_source = SessionSource::DefaultCommand(command, self.options_multi("env"));
} }
if let Some(dirs) = self.option("sessions") { if let Some(dirs) = self.option("sessions") {
@ -681,6 +699,10 @@ mod test {
&[ &[
"--cmd", "--cmd",
"uname", "uname",
"--env",
"A=B",
"--env",
"C=D=E",
"--asterisks", "--asterisks",
"--asterisks-char", "--asterisks-char",
".", ".",
@ -696,7 +718,13 @@ mod test {
], ],
true, true,
Some(|greeter| { Some(|greeter| {
assert!(matches!(&greeter.session_source, SessionSource::Command(cmd) if cmd == "uname")); assert!(matches!(&greeter.session_source, SessionSource::DefaultCommand(cmd, Some(env)) if cmd == "uname" && env.len() == 2));
if let SessionSource::DefaultCommand(_, Some(env)) = &greeter.session_source {
assert_eq!(env[0], "A=B");
assert_eq!(env[1], "C=D=E");
}
assert!(matches!(&greeter.secret_display, SecretDisplay::Character(c) if c == ".")); assert!(matches!(&greeter.secret_display, SecretDisplay::Character(c) if c == "."));
assert_eq!(greeter.prompt_padding(), 0); assert_eq!(greeter.prompt_padding(), 0);
assert_eq!(greeter.window_padding(), 1); assert_eq!(greeter.window_padding(), 1);
@ -727,6 +755,8 @@ mod test {
(&["--issue", "--greeting", "Hello, world!"], false, None), (&["--issue", "--greeting", "Hello, world!"], false, None),
(&["--kb-command", "F2", "--kb-sessions", "F2"], false, None), (&["--kb-command", "F2", "--kb-sessions", "F2"], false, None),
(&["--time-format", "%i %"], false, None), (&["--time-format", "%i %"], false, None),
(&["--cmd", "cmd", "--env"], false, None),
(&["--cmd", "cmd", "--env", "A"], false, None),
]; ];
for (opts, valid, check) in table { for (opts, valid, check) in table {

View File

@ -172,7 +172,8 @@ impl Ipc {
greeter.mode = Mode::Processing; greeter.mode = Mode::Processing;
let session = Session::get_selected(greeter); 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))] #[cfg(not(debug_assertions))]
self.send(Request::StartSession { cmd: vec![command.to_string()], env }).await; self.send(Request::StartSession { cmd: vec![command.to_string()], env }).await;
@ -180,14 +181,8 @@ impl Ipc {
#[cfg(debug_assertions)] #[cfg(debug_assertions)]
{ {
let _ = command; let _ = command;
let _ = env;
self self.send(Request::StartSession { cmd: vec!["true".to_string()], env }).await;
.send(Request::StartSession {
cmd: vec!["true".to_string()],
env: vec![],
})
.await;
} }
} }
} }
@ -234,39 +229,64 @@ fn desktop_names_to_xdg(names: &str) -> String {
names.replace(';', ":").trim_end_matches(':').to_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<String>) { struct DefaultCommand<'a>(&'a str, Option<Vec<String>>);
let mut env: Vec<String> = vec![];
if let Some(Session { impl<'a> DefaultCommand<'a> {
slug, fn command(&'a self) -> &'a str {
session_type, self.0
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)));
}
if *session_type == SessionType::X11 {
if let Some(ref wrap) = greeter.xsession_wrapper {
return (Cow::Owned(format!("{} {}", wrap, command)), env);
}
} 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) fn env(&'a self) -> Option<&'a Vec<String>> {
self.1.as_ref()
}
}
fn wrap_session_command<'a>(greeter: &Greeter, session: Option<&Session>, default: &'a DefaultCommand<'a>) -> (Cow<'a, str>, Vec<String>) {
let mut env: Vec<String> = vec![];
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 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());
}
}
}
(Cow::Borrowed(default.command()), env)
} }
#[cfg(test)] #[cfg(test)]
@ -274,7 +294,7 @@ mod test {
use std::path::PathBuf; use std::path::PathBuf;
use crate::{ use crate::{
ipc::desktop_names_to_xdg, ipc::{desktop_names_to_xdg, DefaultCommand},
ui::sessions::{Session, SessionType}, ui::sessions::{Session, SessionType},
Greeter, Greeter,
}; };
@ -293,7 +313,8 @@ mod test {
..Default::default() ..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!(command.as_ref(), "Session1Cmd");
assert_eq!(env, vec!["XDG_SESSION_TYPE=wayland"]); assert_eq!(env, vec!["XDG_SESSION_TYPE=wayland"]);
@ -312,7 +333,8 @@ mod test {
..Default::default() ..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!(command.as_ref(), "/wrapper.sh Session1Cmd");
assert_eq!(env, vec!["XDG_SESSION_TYPE=wayland"]); assert_eq!(env, vec!["XDG_SESSION_TYPE=wayland"]);
@ -333,7 +355,8 @@ mod test {
..Default::default() ..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!(command.as_ref(), "startx /usr/bin/env Session1Cmd");
assert_eq!( assert_eq!(

View File

@ -61,16 +61,16 @@ impl Theme {
} }
if style.time.is_none() { if style.time.is_none() {
style.time = style.text.clone(); style.time.clone_from(&style.text);
} }
if style.greet.is_none() { if style.greet.is_none() {
style.greet = style.text.clone(); style.greet.clone_from(&style.text);
} }
if style.title.is_none() { if style.title.is_none() {
style.title = style.border.clone(); style.title.clone_from(&style.border);
} }
if style.button.is_none() { if style.button.is_none() {
style.button = style.action.clone(); style.button.clone_from(&style.action);
} }
style style

View File

@ -17,6 +17,7 @@ use super::common::menu::MenuItem;
pub enum SessionSource { pub enum SessionSource {
#[default] #[default]
None, None,
DefaultCommand(String, Option<Vec<String>>),
Command(String), Command(String),
Session(usize), Session(usize),
} }
@ -29,6 +30,7 @@ impl SessionSource {
pub fn label<'g, 'ss: 'g>(&'ss self, greeter: &'g Greeter) -> Option<&'g str> { pub fn label<'g, 'ss: 'g>(&'ss self, greeter: &'g Greeter) -> Option<&'g str> {
match self { match self {
SessionSource::None => None, SessionSource::None => None,
SessionSource::DefaultCommand(command, _) => Some(command),
SessionSource::Command(command) => Some(command), SessionSource::Command(command) => Some(command),
SessionSource::Session(index) => greeter.sessions.options.get(*index).map(|session| session.name.as_str()), 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> { pub fn command<'g, 'ss: 'g>(&'ss self, greeter: &'g Greeter) -> Option<&'g str> {
match self { match self {
SessionSource::None => None, SessionSource::None => None,
SessionSource::DefaultCommand(command, _) => Some(command.as_str()),
SessionSource::Command(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()), SessionSource::Session(index) => greeter.sessions.options.get(*index).map(|session| session.command.as_str()),
} }
} }
pub fn env<'g, 'ss: 'g>(&'ss self) -> Option<Vec<String>> {
match self {
SessionSource::None => None,
SessionSource::DefaultCommand(_, env) => env.clone(),
SessionSource::Command(_) => None,
SessionSource::Session(_) => None,
}
}
} }
// Represents the XDG type of the selected session. // Represents the XDG type of the selected session.