Add session list

This commit is contained in:
Jeremy Soller 2023-10-04 09:52:13 -06:00
parent 4dac2f7ea9
commit f374bd1cc5
No known key found for this signature in database
GPG key ID: DCFCA852D3906975
3 changed files with 206 additions and 56 deletions

18
Cargo.lock generated
View file

@ -760,10 +760,12 @@ name = "cosmic-greeter"
version = "0.1.0" version = "0.1.0"
dependencies = [ dependencies = [
"env_logger", "env_logger",
"freedesktop_entry_parser",
"greetd_ipc", "greetd_ipc",
"libcosmic", "libcosmic",
"log", "log",
"pwd", "pwd",
"shlex",
"tokio", "tokio",
] ]
@ -1432,6 +1434,16 @@ dependencies = [
"xdg", "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]] [[package]]
name = "freetype-rs" name = "freetype-rs"
version = "0.26.0" version = "0.26.0"
@ -3488,6 +3500,12 @@ dependencies = [
"digest", "digest",
] ]
[[package]]
name = "shlex"
version = "1.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a7cee0529a6d40f580e7a5e6c495c8fbfe21b7b52795ed4bb5e62cdf92bc6380"
[[package]] [[package]]
name = "signal-hook-registry" name = "signal-hook-registry"
version = "1.4.1" version = "1.4.1"

View file

@ -5,8 +5,10 @@ edition = "2021"
[dependencies] [dependencies]
env_logger = "0.10" env_logger = "0.10"
freedesktop_entry_parser = "1"
log = "0.4" log = "0.4"
pwd = "1" pwd = "1"
shlex = "1"
[dependencies.greetd_ipc] [dependencies.greetd_ipc]
version = "0.9" version = "0.9"

View file

@ -6,7 +6,7 @@
use cosmic::app::{message, Command, Core, Settings}; use cosmic::app::{message, Command, Core, Settings};
use cosmic::{executor, iced, widget, Element}; use cosmic::{executor, iced, widget, Element};
use greetd_ipc::{codec::SyncCodec, AuthMessageType, Request, Response}; 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; use tokio::net::UnixStream;
fn main() -> Result<(), Box<dyn std::error::Error>> { fn main() -> Result<(), Box<dyn std::error::Error>> {
@ -47,6 +47,101 @@ fn main() -> Result<(), Box<dyn std::error::Error>> {
.collect() .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() let settings = Settings::default()
.antialiasing(true) .antialiasing(true)
.client_decorations(true) .client_decorations(true)
@ -56,41 +151,11 @@ fn main() -> Result<(), Box<dyn std::error::Error>> {
.scale_factor(1.0) .scale_factor(1.0)
.theme(cosmic::Theme::dark()); .theme(cosmic::Theme::dark());
cosmic::app::run::<App>(settings, users)?; cosmic::app::run::<App>(settings, flags)?;
Ok(()) Ok(())
} }
#[derive(Clone, Debug)]
pub enum SocketState {
/// Opening GREETD_SOCK
Pending,
/// GREETD_SOCK is open
Open(Arc<UnixStream>),
/// No GREETD_SOCK variable set
NotSet,
/// Failed to open GREETD_SOCK
Error(Arc<io::Error>),
}
#[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<UnixStream>, request: Request) -> Message { async fn request(socket: Arc<UnixStream>, request: Request) -> Message {
//TODO: handle errors //TODO: handle errors
socket.writable().await.unwrap(); socket.writable().await.unwrap();
@ -156,10 +221,49 @@ async fn request(socket: Arc<UnixStream>, request: Request) -> Message {
Message::None Message::None
} }
#[derive(Clone)]
pub struct Flags {
users: Vec<(pwd::Passwd, Option<widget::image::Handle>)>,
sessions: HashMap<String, Vec<String>>,
}
#[derive(Clone, Debug)]
pub enum SocketState {
/// Opening GREETD_SOCK
Pending,
/// GREETD_SOCK is open
Open(Arc<UnixStream>),
/// No GREETD_SOCK variable set
NotSet,
/// Failed to open GREETD_SOCK
Error(Arc<io::Error>),
}
#[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. /// The [`App`] stores application-specific state.
pub struct App { pub struct App {
core: Core, core: Core,
users: Vec<(pwd::Passwd, Option<widget::image::Handle>)>, flags: Flags,
session_names: Vec<String>,
selected_session: String,
socket_state: SocketState, socket_state: SocketState,
input_state: InputState, input_state: InputState,
} }
@ -170,7 +274,7 @@ impl cosmic::Application for App {
type Executor = executor::Default; type Executor = executor::Default;
/// Argument received [`cosmic::Application::new`]. /// Argument received [`cosmic::Application::new`].
type Flags = Vec<(pwd::Passwd, Option<widget::image::Handle>)>; type Flags = Flags;
/// Message type specific to our [`App`]. /// Message type specific to our [`App`].
type Message = Message; type Message = Message;
@ -187,7 +291,7 @@ impl cosmic::Application for App {
} }
/// Creates the application, and optionally emits command on initialize. /// Creates the application, and optionally emits command on initialize.
fn init(mut core: Core, users: Self::Flags) -> (Self, Command<Self::Message>) { fn init(mut core: Core, flags: Self::Flags) -> (Self, Command<Self::Message>) {
core.window.show_window_menu = false; core.window.show_window_menu = false;
core.window.show_headerbar = false; core.window.show_headerbar = false;
core.window.sharp_corners = true; core.window.sharp_corners = true;
@ -195,10 +299,18 @@ impl cosmic::Application for App {
core.window.show_minimize = false; core.window.show_minimize = false;
core.window.use_template = 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 { App {
core, core,
users, flags,
session_names,
selected_session,
socket_state: SocketState::Pending, socket_state: SocketState::Pending,
//TODO: set to pending until socket is open? //TODO: set to pending until socket is open?
input_state: InputState::Username, input_state: InputState::Username,
@ -228,6 +340,9 @@ impl cosmic::Application for App {
Message::Input(input_state) => { Message::Input(input_state) => {
self.input_state = input_state; self.input_state = input_state;
} }
Message::Session(selected_session) => {
self.selected_session = selected_session;
}
Message::Username(username) => match &self.socket_state { Message::Username(username) => match &self.socket_state {
SocketState::Open(socket) => { SocketState::Open(socket) => {
let socket = socket.clone(); let socket = socket.clone();
@ -264,23 +379,27 @@ impl cosmic::Application for App {
}, },
Message::Login => match &self.socket_state { Message::Login => match &self.socket_state {
SocketState::Open(socket) => { SocketState::Open(socket) => {
let socket = socket.clone(); match self.flags.sessions.get(&self.selected_session).cloned() {
return Command::perform( Some(cmd) => {
async move { let socket = socket.clone();
message::app( return Command::perform(
request( async move {
socket, message::app(
//TODO: get session information from /usr/share/wayland-sessions request(
Request::StartSession { socket,
cmd: vec!["start-cosmic".to_string()], Request::StartSession {
env: vec![], cmd,
}, env: Vec::new(),
) },
.await, )
) .await,
}, )
|x| x, },
); |x| x,
);
}
None => todo!("session {:?} not found", self.selected_session),
}
} }
_ => todo!("socket not open but attempting to log in"), _ => todo!("socket not open but attempting to log in"),
}, },
@ -298,8 +417,8 @@ impl cosmic::Application for App {
widget::text("").into() widget::text("").into()
} }
InputState::Username => { InputState::Username => {
let mut row = widget::row::with_capacity(self.users.len()).spacing(12.0); let mut row = widget::row::with_capacity(self.flags.users.len()).spacing(12.0);
for (user, icon_opt) in &self.users { for (user, icon_opt) in &self.flags.users {
let mut column = widget::column::with_capacity(2).spacing(12.0); let mut column = widget::column::with_capacity(2).spacing(12.0);
match icon_opt { match icon_opt {
Some(icon) => { 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) .width(iced::Length::Fill)
.height(iced::Length::Fill) .height(iced::Length::Fill)
.align_x(iced::alignment::Horizontal::Center) .align_x(iced::alignment::Horizontal::Center)