From 16f639d0af688987a781c2a9173b3be2a32df763 Mon Sep 17 00:00:00 2001 From: Josh Megnauth Date: Thu, 22 Aug 2024 08:17:06 -0400 Subject: [PATCH] Default to user's previously selected session Closes: #109 --- Cargo.lock | 10 +++ Cargo.toml | 6 +- cosmic-greeter-config/Cargo.toml | 12 +++ cosmic-greeter-config/src/lib.rs | 48 +++++++++++ cosmic-greeter-config/src/user.rs | 20 +++++ src/greeter.rs | 129 +++++++++++++++++++++++++++--- 6 files changed, 213 insertions(+), 12 deletions(-) create mode 100644 cosmic-greeter-config/Cargo.toml create mode 100644 cosmic-greeter-config/src/lib.rs create mode 100644 cosmic-greeter-config/src/user.rs diff --git a/Cargo.lock b/Cargo.lock index 9a9ec2c..08f5fee 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -993,6 +993,7 @@ dependencies = [ "cosmic-comp-config", "cosmic-config", "cosmic-dbus-networkmanager", + "cosmic-greeter-config", "cosmic-greeter-daemon", "env_logger", "freedesktop_entry_parser", @@ -1017,6 +1018,15 @@ dependencies = [ "zbus 4.4.0", ] +[[package]] +name = "cosmic-greeter-config" +version = "0.1.0" +dependencies = [ + "cosmic-config", + "log", + "serde", +] + [[package]] name = "cosmic-greeter-daemon" version = "0.1.0" diff --git a/Cargo.toml b/Cargo.toml index 8c2c641..a62db71 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -8,6 +8,7 @@ chrono = "0.4.38" cosmic-bg-config.workspace = true cosmic-comp-config.workspace = true cosmic-config = { workspace = true, features = ["calloop", "macro"] } +cosmic-greeter-config.workspace = true cosmic-greeter-daemon = { path = "daemon" } env_logger.workspace = true freedesktop_entry_parser = "1.3.0" @@ -62,7 +63,7 @@ opt-level = 2 opt-level = 2 [workspace] -members = ["daemon"] +members = ["cosmic-greeter-config", "daemon"] resolver = "2" [workspace.package] @@ -87,6 +88,9 @@ default-features = false git = "https://github.com/pop-os/cosmic-comp" default-features = false +[workspace.dependencies.cosmic-greeter-config] +path = "cosmic-greeter-config" + [workspace.dependencies.cosmic-config] git = "https://github.com/pop-os/libcosmic" default-features = false diff --git a/cosmic-greeter-config/Cargo.toml b/cosmic-greeter-config/Cargo.toml new file mode 100644 index 0000000..d85a445 --- /dev/null +++ b/cosmic-greeter-config/Cargo.toml @@ -0,0 +1,12 @@ +[package] +name = "cosmic-greeter-config" +version = "0.1.0" +edition = "2021" +description = "Configuration for COSMIC Greeter" +repository = "https://github.com/pop-os/cosmic-greeter" +license = "GPL-3.0-only" + +[dependencies] +cosmic-config.workspace = true +log.workspace = true +serde.workspace = true diff --git a/cosmic-greeter-config/src/lib.rs b/cosmic-greeter-config/src/lib.rs new file mode 100644 index 0000000..7f52375 --- /dev/null +++ b/cosmic-greeter-config/src/lib.rs @@ -0,0 +1,48 @@ +// Copyright 2024 System76 +// SPDX-License-Identifier: GPL-3.0-only + +pub mod user; + +use std::{collections::HashMap, num::NonZeroU32}; + +use cosmic_config::{cosmic_config_derive::CosmicConfigEntry, CosmicConfigEntry}; +use serde::{Deserialize, Serialize}; + +pub const APP_ID: &str = "com.system76.CosmicGreeter"; +pub const CONFIG_VERSION: u64 = 1; + +#[derive(Debug, Clone, Default, PartialEq, CosmicConfigEntry, Deserialize, Serialize)] +#[version = 1] +#[id = "com.system76.CosmicGreeter"] +pub struct Config { + #[serde(skip_serializing_if = "HashMap::is_empty")] + pub users: HashMap, +} + +impl Config { + pub fn load() -> (Self, Option) { + crate::load() + } +} + +pub(crate) fn load() -> (C, Option) +where + C: Default + CosmicConfigEntry, +{ + match cosmic_config::Config::new(APP_ID, CONFIG_VERSION) { + Ok(handler) => { + let config = C::get_entry(&handler) + .inspect_err(|(errors, _)| { + for err in errors { + log::error!("{err}") + } + }) + .unwrap_or_else(|(_, config)| config); + (config, Some(handler)) + } + Err(e) => { + log::error!("Failed to get settings for `{APP_ID}` (v {CONFIG_VERSION}): {e:?}"); + (C::default(), None) + } + } +} diff --git a/cosmic-greeter-config/src/user.rs b/cosmic-greeter-config/src/user.rs new file mode 100644 index 0000000..97c2430 --- /dev/null +++ b/cosmic-greeter-config/src/user.rs @@ -0,0 +1,20 @@ +// Copyright 2024 System76 +// SPDX-License-Identifier: GPL-3.0-only + +use std::num::NonZeroU32; + +use serde::{Deserialize, Serialize}; + +/// Per user state for Greeter. +#[derive(Debug, Clone, PartialEq, Deserialize, Serialize)] +pub struct UserState { + #[serde(skip_serializing_if = "invalid_uid")] + pub uid: NonZeroU32, + #[serde(skip_serializing_if = "Option::is_none")] + pub last_session: Option, +} + +// Only serialize users not system accounts +const fn invalid_uid(uid: &NonZeroU32) -> bool { + uid.get() < 1000 +} diff --git a/src/greeter.rs b/src/greeter.rs index 87d3908..9b9e9a2 100644 --- a/src/greeter.rs +++ b/src/greeter.rs @@ -27,12 +27,14 @@ use cosmic::{ style, theme, widget, Element, }; use cosmic_comp_config::CosmicCompConfig; +use cosmic_greeter_config::Config as CosmicGreeterConfig; use cosmic_greeter_daemon::{UserData, WallpaperData}; use greetd_ipc::Request; use std::{ - collections::HashMap, + collections::{hash_map, HashMap}, error::Error, fs, io, + num::NonZeroU32, path::{Path, PathBuf}, process, sync::Arc, @@ -130,6 +132,14 @@ pub fn main() -> Result<(), Box> { // Sort user data by uid user_datas.sort_by(|a, b| a.uid.cmp(&b.uid)); + let (mut greeter_config, greeter_config_handler) = CosmicGreeterConfig::load(); + // Filter out users that were removed from the system since the last time we loaded config + greeter_config.users.retain(|uid, _| { + user_datas + .binary_search_by(|probe| probe.uid.cmp(&uid.get())) + .is_ok() + }); + enum SessionType { X11, Wayland, @@ -294,6 +304,8 @@ pub fn main() -> Result<(), Box> { sessions, layouts_opt, comp_config_handler, + greeter_config, + greeter_config_handler, fallback_background, }; @@ -310,6 +322,8 @@ pub struct Flags { sessions: HashMap, Vec)>, layouts_opt: Option>, comp_config_handler: Option, + greeter_config: CosmicGreeterConfig, + greeter_config_handler: Option, fallback_background: widget::image::Handle, } @@ -362,6 +376,7 @@ pub enum Dropdown { #[derive(Clone, Debug)] pub enum Message { Auth(Option), + ConfigUpdateUser, DialogCancel, DialogConfirm, DropdownToggle(Dropdown), @@ -590,12 +605,6 @@ 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: use last selected session - let selected_session = session_names.first().cloned().unwrap_or(String::new()); - //TODO: use full_name_opt let mut usernames: Vec<_> = flags .user_datas @@ -609,11 +618,25 @@ impl cosmic::Application for App { usernames.sort_by(|a, b| a.1.cmp(&b.1)); //TODO: use last selected user - let selected_username = flags + let (selected_username, uid) = flags .user_datas .first() - .map(|x| x.name.clone()) - .unwrap_or(String::new()); + .map(|x| (x.name.clone(), NonZeroU32::new(x.uid))) + .unwrap_or((String::new(), None)); + + let mut session_names: Vec<_> = flags.sessions.keys().map(|x| x.to_string()).collect(); + session_names.sort(); + + let selected_session = uid + .and_then(|uid| { + flags + .greeter_config + .users + .get(&uid) + .and_then(|user| user.last_session.clone()) + }) + .or_else(|| session_names.first().cloned()) + .unwrap_or_default(); let app = App { core, @@ -779,6 +802,23 @@ impl cosmic::Application for App { if username != self.selected_username { self.selected_username = username.clone(); self.surface_images.clear(); + if let Some(session) = self + .flags + .user_datas + .iter() + .find(|user| user.name == username) + .and_then(|UserData { uid, .. }| { + NonZeroU32::new(*uid).and_then(|uid| { + self.flags + .greeter_config + .users + .get(&uid) + .and_then(|conf| conf.last_session.as_deref()) + }) + }) + { + session.clone_into(&mut self.selected_session); + }; match &self.socket_state { SocketState::Open => { self.prompt_opt = None; @@ -788,6 +828,70 @@ impl cosmic::Application for App { } } } + Message::ConfigUpdateUser => { + let Some(user_entry) = self + .flags + .user_datas + .iter() + .find(|user| user.name == self.selected_username) + .and_then(|UserData { uid, .. }| { + NonZeroU32::new(*uid).map(|uid| self.flags.greeter_config.users.entry(uid)) + }) + else { + log::error!("Couldn't find user: {:?}", self.selected_username); + return Command::none(); + }; + + let Some(handler) = self.flags.greeter_config_handler.as_mut() else { + log::error!( + "Failed to update config for {} (UID: {}): no config handler", + self.selected_username, + user_entry.key() + ); + return Command::none(); + }; + + let uid = *user_entry.key(); + match user_entry { + hash_map::Entry::Vacant(entry) => { + let last_session = Some(self.selected_session.clone()); + entry.insert(cosmic_greeter_config::user::UserState { uid, last_session }); + } + hash_map::Entry::Occupied(mut entry) => { + let last_session = entry.get_mut().last_session.as_mut(); + if last_session + .as_ref() + .is_some_and(|session| session.as_str() == self.selected_session) + { + return Command::none(); + } + if let Some(session) = last_session { + self.selected_session.clone_into(session); + } else { + let last_session = Some(self.selected_session.clone()); + entry.insert(cosmic_greeter_config::user::UserState { + uid, + last_session, + }); + } + } + } + + // xxx Not sure why this doesn't work unless the handler is used directly + // if let Err(err) = self + // .flags + // .greeter_config + // .set_users(&handler, self.flags.greeter_config.users.clone()) + if let Err(err) = handler.set("users", &self.flags.greeter_config.users) { + log::error!( + "Failed to set {} as last selected session for {} (UID: {}): {:?}", + self.selected_session, + self.selected_username, + uid, + err + ); + } + } Message::Auth(response) => { self.prompt_opt = None; self.error_opt = None; @@ -798,7 +902,10 @@ impl cosmic::Application for App { self.error_opt = None; match self.flags.sessions.get(&self.selected_session).cloned() { Some((cmd, env)) => { - return self.send_request(Request::StartSession { cmd, env }); + return Command::batch([ + self.update(Message::ConfigUpdateUser), + self.send_request(Request::StartSession { cmd, env }), + ]); } None => todo!("session {:?} not found", self.selected_session), }