Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add option to specify environment for default session #153

Merged
merged 3 commits into from
Aug 16, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 12 additions & 6 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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: *)
Expand All @@ -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
Expand Down
3 changes: 3 additions & 0 deletions contrib/man/tuigreet-1.scd
Original file line number Diff line number Diff line change
Expand Up @@ -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*.
Expand Down
34 changes: 32 additions & 2 deletions src/greeter.rs
Original file line number Diff line number Diff line change
Expand Up @@ -339,6 +339,13 @@ impl Greeter {
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
// provided arguments.
pub fn width(&self) -> u16 {
Expand Down Expand Up @@ -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");
Expand Down Expand Up @@ -559,7 +567,17 @@ 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);
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") {
Expand Down Expand Up @@ -681,6 +699,10 @@ mod test {
&[
"--cmd",
"uname",
"--env",
"A=B",
"--env",
"C=D=E",
"--asterisks",
"--asterisks-char",
".",
Expand All @@ -696,7 +718,13 @@ mod test {
],
true,
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_eq!(greeter.prompt_padding(), 0);
assert_eq!(greeter.window_padding(), 1);
Expand Down Expand Up @@ -727,6 +755,8 @@ mod test {
(&["--issue", "--greeting", "Hello, world!"], false, None),
(&["--kb-command", "F2", "--kb-sessions", "F2"], false, None),
(&["--time-format", "%i %"], false, None),
(&["--cmd", "cmd", "--env"], false, None),
(&["--cmd", "cmd", "--env", "A"], false, None),
];

for (opts, valid, check) in table {
Expand Down
99 changes: 61 additions & 38 deletions src/ipc.rs
Original file line number Diff line number Diff line change
Expand Up @@ -172,22 +172,17 @@ 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;

#[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;
}
}
}
Expand Down Expand Up @@ -234,47 +229,72 @@ 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<String>) {
struct DefaultCommand<'a>(&'a str, Option<Vec<String>>);

impl<'a> DefaultCommand<'a> {
fn command(&'a self) -> &'a str {
self.0
}

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![];

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)]
mod test {
use std::path::PathBuf;

use crate::{
ipc::desktop_names_to_xdg,
ipc::{desktop_names_to_xdg, DefaultCommand},
ui::sessions::{Session, SessionType},
Greeter,
};
Expand All @@ -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"]);
Expand All @@ -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"]);
Expand All @@ -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!(
Expand Down
8 changes: 4 additions & 4 deletions src/ui/common/style.rs
Original file line number Diff line number Diff line change
Expand Up @@ -61,16 +61,16 @@ impl Theme {
}

if style.time.is_none() {
style.time = style.text.clone();
style.time.clone_from(&style.text);
}
if style.greet.is_none() {
style.greet = style.text.clone();
style.greet.clone_from(&style.text);
}
if style.title.is_none() {
style.title = style.border.clone();
style.title.clone_from(&style.border);
}
if style.button.is_none() {
style.button = style.action.clone();
style.button.clone_from(&style.action);
}

style
Expand Down
12 changes: 12 additions & 0 deletions src/ui/sessions.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ use super::common::menu::MenuItem;
pub enum SessionSource {
#[default]
None,
DefaultCommand(String, Option<Vec<String>>),
Command(String),
Session(usize),
}
Expand All @@ -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()),
}
Expand All @@ -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<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.
Expand Down