diff --git a/Cargo.lock b/Cargo.lock index c672ce2..31ad508 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -760,10 +760,12 @@ name = "cosmic-greeter" version = "0.1.0" dependencies = [ "env_logger", + "freedesktop_entry_parser", "greetd_ipc", "libcosmic", "log", "pwd", + "shlex", "tokio", ] @@ -1432,6 +1434,16 @@ dependencies = [ "xdg", ] +[[package]] +name = "freedesktop_entry_parser" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "db9c27b72f19a99a895f8ca89e2d26e4ef31013376e56fdafef697627306c3e4" +dependencies = [ + "nom", + "thiserror", +] + [[package]] name = "freetype-rs" version = "0.26.0" @@ -3488,6 +3500,12 @@ dependencies = [ "digest", ] +[[package]] +name = "shlex" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a7cee0529a6d40f580e7a5e6c495c8fbfe21b7b52795ed4bb5e62cdf92bc6380" + [[package]] name = "signal-hook-registry" version = "1.4.1" diff --git a/Cargo.toml b/Cargo.toml index c74da1b..e5e27bc 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -5,8 +5,10 @@ edition = "2021" [dependencies] env_logger = "0.10" +freedesktop_entry_parser = "1" log = "0.4" pwd = "1" +shlex = "1" [dependencies.greetd_ipc] version = "0.9" diff --git a/src/main.rs b/src/main.rs index dc83c15..40c3544 100644 --- a/src/main.rs +++ b/src/main.rs @@ -6,7 +6,7 @@ use cosmic::app::{message, Command, Core, Settings}; use cosmic::{executor, iced, widget, Element}; use greetd_ipc::{codec::SyncCodec, AuthMessageType, Request, Response}; -use std::{env, fs, io, path::Path, sync::Arc}; +use std::{collections::HashMap, env, fs, io, path::Path, sync::Arc}; use tokio::net::UnixStream; fn main() -> Result<(), Box> { @@ -47,6 +47,101 @@ fn main() -> Result<(), Box> { .collect() }; + //TODO: allow custom directories? + let session_dirs = &[ + Path::new("/usr/share/wayland-sessions"), + Path::new("/usr/share/xsessions"), + ]; + + let sessions = { + let mut sessions = HashMap::new(); + for session_dir in session_dirs { + let read_dir = match fs::read_dir(&session_dir) { + Ok(ok) => ok, + Err(err) => { + log::warn!( + "failed to read session directory {:?}: {:?}", + session_dir, + err + ); + continue; + } + }; + + for dir_entry_res in read_dir { + let dir_entry = match dir_entry_res { + Ok(ok) => ok, + Err(err) => { + log::warn!( + "failed to read session directory {:?} entry: {:?}", + session_dir, + err + ); + continue; + } + }; + + let entry = match freedesktop_entry_parser::parse_entry(dir_entry.path()) { + Ok(ok) => ok, + Err(err) => { + log::warn!( + "failed to read session file {:?}: {:?}", + dir_entry.path(), + err + ); + continue; + } + }; + + let name = match entry.section("Desktop Entry").attr("Name") { + Some(some) => some, + None => { + log::warn!( + "failed to read session file {:?}: no Desktop Entry/Name attribute", + dir_entry.path() + ); + continue; + } + }; + + let exec = match entry.section("Desktop Entry").attr("Exec") { + Some(some) => some, + None => { + log::warn!( + "failed to read session file {:?}: no Desktop Entry/Exec attribute", + dir_entry.path() + ); + continue; + } + }; + + let split = match shlex::split(exec) { + Some(some) => some, + None => { + log::warn!( + "failed to parse session file {:?} Exec field {:?}", + dir_entry.path(), + exec + ); + continue; + } + }; + + match sessions.insert(name.to_string(), split) { + Some(some) => { + log::warn!("session overwritten with command {:?}", some); + } + None => {} + } + } + } + sessions + }; + + println!("{:?}", sessions); + + let flags = Flags { users, sessions }; + let settings = Settings::default() .antialiasing(true) .client_decorations(true) @@ -56,41 +151,11 @@ fn main() -> Result<(), Box> { .scale_factor(1.0) .theme(cosmic::Theme::dark()); - cosmic::app::run::(settings, users)?; + cosmic::app::run::(settings, flags)?; Ok(()) } -#[derive(Clone, Debug)] -pub enum SocketState { - /// Opening GREETD_SOCK - Pending, - /// GREETD_SOCK is open - Open(Arc), - /// No GREETD_SOCK variable set - NotSet, - /// Failed to open GREETD_SOCK - Error(Arc), -} - -#[derive(Clone, Debug)] -pub enum InputState { - None, - Username, - Auth(bool, String, String), -} - -/// Messages that are used specifically by our [`App`]. -#[derive(Clone, Debug)] -pub enum Message { - None, - Socket(SocketState), - Input(InputState), - Username(String), - Auth(String), - Login, -} - async fn request(socket: Arc, request: Request) -> Message { //TODO: handle errors socket.writable().await.unwrap(); @@ -156,10 +221,49 @@ async fn request(socket: Arc, request: Request) -> Message { Message::None } +#[derive(Clone)] +pub struct Flags { + users: Vec<(pwd::Passwd, Option)>, + sessions: HashMap>, +} + +#[derive(Clone, Debug)] +pub enum SocketState { + /// Opening GREETD_SOCK + Pending, + /// GREETD_SOCK is open + Open(Arc), + /// No GREETD_SOCK variable set + NotSet, + /// Failed to open GREETD_SOCK + Error(Arc), +} + +#[derive(Clone, Debug)] +pub enum InputState { + None, + Username, + Auth(bool, String, String), +} + +/// Messages that are used specifically by our [`App`]. +#[derive(Clone, Debug)] +pub enum Message { + None, + Socket(SocketState), + Input(InputState), + Session(String), + Username(String), + Auth(String), + Login, +} + /// The [`App`] stores application-specific state. pub struct App { core: Core, - users: Vec<(pwd::Passwd, Option)>, + flags: Flags, + session_names: Vec, + selected_session: String, socket_state: SocketState, input_state: InputState, } @@ -170,7 +274,7 @@ impl cosmic::Application for App { type Executor = executor::Default; /// Argument received [`cosmic::Application::new`]. - type Flags = Vec<(pwd::Passwd, Option)>; + type Flags = Flags; /// Message type specific to our [`App`]. type Message = Message; @@ -187,7 +291,7 @@ impl cosmic::Application for App { } /// Creates the application, and optionally emits command on initialize. - fn init(mut core: Core, users: Self::Flags) -> (Self, Command) { + fn init(mut core: Core, flags: Self::Flags) -> (Self, Command) { core.window.show_window_menu = false; core.window.show_headerbar = false; core.window.sharp_corners = true; @@ -195,10 +299,18 @@ impl cosmic::Application for App { core.window.show_minimize = false; core.window.use_template = false; + let mut session_names: Vec<_> = flags.sessions.keys().map(|x| x.to_string()).collect(); + session_names.sort(); + + //TODO: determine default session? + let selected_session = session_names.first().cloned().unwrap_or(String::new()); + ( App { core, - users, + flags, + session_names, + selected_session, socket_state: SocketState::Pending, //TODO: set to pending until socket is open? input_state: InputState::Username, @@ -228,6 +340,9 @@ impl cosmic::Application for App { Message::Input(input_state) => { self.input_state = input_state; } + Message::Session(selected_session) => { + self.selected_session = selected_session; + } Message::Username(username) => match &self.socket_state { SocketState::Open(socket) => { let socket = socket.clone(); @@ -264,23 +379,27 @@ impl cosmic::Application for App { }, Message::Login => match &self.socket_state { SocketState::Open(socket) => { - let socket = socket.clone(); - return Command::perform( - async move { - message::app( - request( - socket, - //TODO: get session information from /usr/share/wayland-sessions - Request::StartSession { - cmd: vec!["start-cosmic".to_string()], - env: vec![], - }, - ) - .await, - ) - }, - |x| x, - ); + match self.flags.sessions.get(&self.selected_session).cloned() { + Some(cmd) => { + let socket = socket.clone(); + return Command::perform( + async move { + message::app( + request( + socket, + Request::StartSession { + cmd, + env: Vec::new(), + }, + ) + .await, + ) + }, + |x| x, + ); + } + None => todo!("session {:?} not found", self.selected_session), + } } _ => todo!("socket not open but attempting to log in"), }, @@ -298,8 +417,8 @@ impl cosmic::Application for App { widget::text("").into() } InputState::Username => { - let mut row = widget::row::with_capacity(self.users.len()).spacing(12.0); - for (user, icon_opt) in &self.users { + let mut row = widget::row::with_capacity(self.flags.users.len()).spacing(12.0); + for (user, icon_opt) in &self.flags.users { let mut column = widget::column::with_capacity(2).spacing(12.0); match icon_opt { Some(icon) => { @@ -353,7 +472,18 @@ impl cosmic::Application for App { } }; - let centered = widget::container(content) + let session_picker = widget::pick_list( + &self.session_names, + Some(self.selected_session.clone()), + Message::Session, + ); + + let column = widget::column::with_capacity(2) + .push(content) + .push(session_picker) + .spacing(12.0); + + let centered = widget::container(column) .width(iced::Length::Fill) .height(iced::Length::Fill) .align_x(iced::alignment::Horizontal::Center)