Add breadgreet session picker for installed Wayland sessions
List .desktop sessions from the existing scan, pre-select bos when present (else the first entry), and pass the chosen Exec= argv to greetd StartSession.
This commit is contained in:
parent
80caedc7fd
commit
495fe1aaad
6 changed files with 151 additions and 50 deletions
|
|
@ -1,17 +1,45 @@
|
|||
//! Session discovery: scans the standard greetd-greeter session directories
|
||||
//! for `.desktop` entries. BOS effectively ships one session (Hyprland via
|
||||
//! `bos-session`), so v1 has no picker UI — it just auto-selects the
|
||||
//! configured default (or the only entry found) and resolves its `Exec=`
|
||||
//! line to hand to `greetd`'s `StartSession`.
|
||||
//! for `.desktop` entries, lists them for the picker, and resolves the
|
||||
//! chosen entry's `Exec=` line for `greetd`'s `StartSession`.
|
||||
//!
|
||||
//! Default selection matches by `.desktop` file stem (`bos` compiled-in,
|
||||
//! overridable via `[sessions].default`). If that stem is missing, the
|
||||
//! first entry from `wayland_dirs` then `xsessions_dirs` is used.
|
||||
|
||||
use breadlock_ui::desktop_entry::{scan_dir, DesktopEntry};
|
||||
use breadlock_ui::desktop_entry::scan_dir;
|
||||
use std::path::Path;
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub struct Session {
|
||||
/// `.desktop` file stem (`bos` for `bos.desktop`) — used to match
|
||||
/// `[sessions].default`.
|
||||
pub stem: String,
|
||||
pub name: String,
|
||||
pub exec: Vec<String>,
|
||||
}
|
||||
|
||||
/// Every installed session, `wayland_dirs` first then `xsessions_dirs`.
|
||||
/// Each directory is sorted by stem (see [`scan_dir`]).
|
||||
pub fn list(wayland_dirs: &[String], xsessions_dirs: &[String]) -> Vec<Session> {
|
||||
let mut all = Vec::new();
|
||||
for dir in wayland_dirs.iter().chain(xsessions_dirs) {
|
||||
for (stem, entry) in scan_dir(Path::new(dir)) {
|
||||
all.push(Session {
|
||||
stem,
|
||||
name: entry.name,
|
||||
exec: split_exec(&entry.exec),
|
||||
});
|
||||
}
|
||||
}
|
||||
all
|
||||
}
|
||||
|
||||
/// Index of the configured default stem, or `0` if it is absent. Callers
|
||||
/// with an empty list should not use this as a subscript.
|
||||
pub fn default_index(sessions: &[Session], default: &str) -> usize {
|
||||
sessions.iter().position(|s| s.stem == default).unwrap_or(0)
|
||||
}
|
||||
|
||||
/// Scans `wayland_dirs` then `xsessions_dirs` (in that order) and returns
|
||||
/// the entry matching `default` (by `.desktop` file stem), falling back to
|
||||
/// the first entry found in either directory. `None` if nothing is
|
||||
|
|
@ -21,20 +49,9 @@ pub fn discover(
|
|||
xsessions_dirs: &[String],
|
||||
default: &str,
|
||||
) -> Option<Session> {
|
||||
let mut all: Vec<(String, DesktopEntry)> = Vec::new();
|
||||
for dir in wayland_dirs.iter().chain(xsessions_dirs) {
|
||||
all.extend(scan_dir(Path::new(dir)));
|
||||
}
|
||||
|
||||
let chosen = all
|
||||
.iter()
|
||||
.find(|(stem, _)| stem == default)
|
||||
.or_else(|| all.first())?;
|
||||
|
||||
Some(Session {
|
||||
name: chosen.1.name.clone(),
|
||||
exec: split_exec(&chosen.1.exec),
|
||||
})
|
||||
let all = list(wayland_dirs, xsessions_dirs);
|
||||
let idx = default_index(&all, default);
|
||||
all.into_iter().nth(idx)
|
||||
}
|
||||
|
||||
/// Splits a `.desktop` `Exec=` line into an argv. Only handles plain
|
||||
|
|
@ -69,26 +86,72 @@ mod tests {
|
|||
.is_none());
|
||||
}
|
||||
|
||||
fn write_fixture(dir: &std::path::Path, stem: &str, name: &str, exec: &str) {
|
||||
std::fs::write(
|
||||
dir.join(format!("{stem}.desktop")),
|
||||
format!("[Desktop Entry]\nName={name}\nExec={exec}\n"),
|
||||
)
|
||||
.unwrap();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn discover_prefers_configured_default_over_first_entry() {
|
||||
let dir = std::env::temp_dir().join("breadgreet-test-sessions-discover");
|
||||
let dir = std::env::temp_dir().join(format!(
|
||||
"breadgreet-test-sessions-discover-{}",
|
||||
std::process::id()
|
||||
));
|
||||
std::fs::create_dir_all(&dir).unwrap();
|
||||
std::fs::write(
|
||||
dir.join("aaa.desktop"),
|
||||
"[Desktop Entry]\nName=A\nExec=a-cmd\n",
|
||||
)
|
||||
.unwrap();
|
||||
std::fs::write(
|
||||
dir.join("hyprland.desktop"),
|
||||
"[Desktop Entry]\nName=Hyprland\nExec=Hyprland\n",
|
||||
)
|
||||
.unwrap();
|
||||
write_fixture(&dir, "aaa", "A", "a-cmd");
|
||||
write_fixture(&dir, "hyprland", "Hyprland", "Hyprland");
|
||||
|
||||
let dir_str = dir.to_str().unwrap().to_string();
|
||||
let session = discover(&[dir_str], &[], "hyprland").unwrap();
|
||||
assert_eq!(session.stem, "hyprland");
|
||||
assert_eq!(session.name, "Hyprland");
|
||||
assert_eq!(session.exec, vec!["Hyprland"]);
|
||||
|
||||
std::fs::remove_dir_all(&dir).ok();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn list_returns_all_sessions_wayland_then_x() {
|
||||
let pid = std::process::id();
|
||||
let wayland = std::env::temp_dir().join(format!("breadgreet-test-sessions-list-w-{pid}"));
|
||||
let x11 = std::env::temp_dir().join(format!("breadgreet-test-sessions-list-x-{pid}"));
|
||||
std::fs::create_dir_all(&wayland).unwrap();
|
||||
std::fs::create_dir_all(&x11).unwrap();
|
||||
write_fixture(&wayland, "bos", "BOS", "/usr/local/bin/bos-session");
|
||||
write_fixture(&wayland, "hyprland", "Hyprland", "Hyprland");
|
||||
write_fixture(&x11, "openbox", "Openbox", "openbox-session");
|
||||
|
||||
let listed = list(
|
||||
&[wayland.to_str().unwrap().to_string()],
|
||||
&[x11.to_str().unwrap().to_string()],
|
||||
);
|
||||
let stems: Vec<&str> = listed.iter().map(|s| s.stem.as_str()).collect();
|
||||
assert_eq!(stems, vec!["bos", "hyprland", "openbox"]);
|
||||
assert_eq!(listed[0].exec, vec!["/usr/local/bin/bos-session"]);
|
||||
|
||||
std::fs::remove_dir_all(&wayland).ok();
|
||||
std::fs::remove_dir_all(&x11).ok();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn default_index_prefers_bos_then_first() {
|
||||
let sessions = vec![
|
||||
Session {
|
||||
stem: "aaa".into(),
|
||||
name: "A".into(),
|
||||
exec: vec!["a".into()],
|
||||
},
|
||||
Session {
|
||||
stem: "bos".into(),
|
||||
name: "BOS".into(),
|
||||
exec: vec!["bos-session".into()],
|
||||
},
|
||||
];
|
||||
assert_eq!(default_index(&sessions, "bos"), 1);
|
||||
assert_eq!(default_index(&sessions, "missing"), 0);
|
||||
assert_eq!(default_index(&[], "bos"), 0);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue