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"
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"

View file

@ -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"

View file

@ -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<dyn std::error::Error>> {
@ -47,6 +47,101 @@ fn main() -> Result<(), Box<dyn std::error::Error>> {
.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<dyn std::error::Error>> {
.scale_factor(1.0)
.theme(cosmic::Theme::dark());
cosmic::app::run::<App>(settings, users)?;
cosmic::app::run::<App>(settings, flags)?;
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 {
//TODO: handle errors
socket.writable().await.unwrap();
@ -156,10 +221,49 @@ async fn request(socket: Arc<UnixStream>, request: Request) -> Message {
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.
pub struct App {
core: Core,
users: Vec<(pwd::Passwd, Option<widget::image::Handle>)>,
flags: Flags,
session_names: Vec<String>,
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<widget::image::Handle>)>;
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<Self::Message>) {
fn init(mut core: Core, flags: Self::Flags) -> (Self, Command<Self::Message>) {
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)