Add session list
This commit is contained in:
parent
4dac2f7ea9
commit
f374bd1cc5
3 changed files with 206 additions and 56 deletions
18
Cargo.lock
generated
18
Cargo.lock
generated
|
|
@ -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"
|
||||||
|
|
|
||||||
|
|
@ -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"
|
||||||
|
|
|
||||||
242
src/main.rs
242
src/main.rs
|
|
@ -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)
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue