From fd049483c310a1a7e4ddbf8bef1d5152cfc87e3d Mon Sep 17 00:00:00 2001 From: Jeremy Soller Date: Tue, 6 Feb 2024 15:03:07 -0700 Subject: [PATCH] Add daemon to proxy user backgrounds --- .gitignore | 3 + Cargo.lock | 19 +++ Cargo.toml | 54 +++++--- daemon/Cargo.toml | 20 +++ daemon/src/lib.rs | 16 +++ daemon/src/main.rs | 181 ++++++++++++++++++++++++ dbus/com.system76.CosmicGreeter.conf | 14 ++ debian/control | 8 ++ debian/cosmic-greeter-daemon.install | 2 + debian/cosmic-greeter-daemon.service | 9 ++ debian/cosmic-greeter.install | 1 + debian/cosmic-greeter.service | 4 +- debian/rules | 3 +- justfile | 12 +- src/greeter.rs | 199 ++++++++++++++++++++++----- 15 files changed, 487 insertions(+), 58 deletions(-) create mode 100644 daemon/Cargo.toml create mode 100644 daemon/src/lib.rs create mode 100644 daemon/src/main.rs create mode 100644 dbus/com.system76.CosmicGreeter.conf create mode 100644 debian/cosmic-greeter-daemon.install create mode 100644 debian/cosmic-greeter-daemon.service diff --git a/.gitignore b/.gitignore index 699219b..c06e26a 100644 --- a/.gitignore +++ b/.gitignore @@ -2,7 +2,10 @@ /debian/*debhelper* /debian/cosmic-greeter.substvars /debian/cosmic-greeter/ +/debian/cosmic-greeter-daemon.substvars +/debian/cosmic-greeter-daemon/ /debian/files +/debian/tmp/ /target/ /vendor.tar /vendor/ diff --git a/Cargo.lock b/Cargo.lock index 47ca42c..0e4e2ef 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -811,6 +811,7 @@ dependencies = [ "cosmic-bg-config", "cosmic-config", "cosmic-dbus-networkmanager", + "cosmic-greeter-daemon", "env_logger", "freedesktop_entry_parser", "greetd_ipc", @@ -819,6 +820,7 @@ dependencies = [ "logind-zbus", "pam-client", "pwd", + "ron", "shlex", "tokio", "upower_dbus", @@ -826,6 +828,23 @@ dependencies = [ "zbus", ] +[[package]] +name = "cosmic-greeter-daemon" +version = "0.1.0" +dependencies = [ + "cosmic-bg-config", + "cosmic-config", + "env_logger", + "libc", + "libcosmic", + "log", + "pwd", + "ron", + "serde", + "tokio", + "zbus", +] + [[package]] name = "cosmic-protocols" version = "0.1.0" diff --git a/Cargo.toml b/Cargo.toml index 3befaf5..b9597b3 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -5,12 +5,19 @@ edition = "2021" [dependencies] chrono = "0.4.31" -env_logger = "0.10.0" +cosmic-bg-config.workspace = true +cosmic-config = { workspace = true, features = ["calloop", "macro"] } +cosmic-greeter-daemon = { path = "daemon" } +env_logger.workspace = true freedesktop_entry_parser = "1.3.0" -log = "0.4.20" +libcosmic = { workspace = true, features = ["tokio", "wayland"] } +log.workspace = true pam-client = "0.5.0" -pwd = "1.4.0" +pwd.workspace = true +ron.workspace = true shlex = "1.2.0" +#TODO: reduce features +tokio = { workspace = true, features = ["full"] } wayland-client = "0.31.1" # For network status using networkmanager feature cosmic-dbus-networkmanager = { git = "https://github.com/pop-os/dbus-settings-bindings", optional = true } @@ -19,29 +26,38 @@ logind-zbus = { version = "3.1.2", optional = true } # For power status with upower feature upower_dbus = { git = "https://github.com/pop-os/dbus-settings-bindings", optional = true } # Required for some features -zbus = { version = "3.14.1", optional = true } - -[dependencies.cosmic-bg-config] -git = "https://github.com/pop-os/cosmic-bg" - -[dependencies.cosmic-config] -git = "https://github.com/pop-os/libcosmic" -features = ["calloop", "macro"] +zbus = { workspace = true, optional = true } [dependencies.greetd_ipc] version = "0.9.0" features = ["sync-codec"] -[dependencies.libcosmic] -git = "https://github.com/pop-os/libcosmic" -features = ["tokio", "wayland"] - -[dependencies.tokio] -version = "1.33.0" -features = ["full"] - [features] default = ["logind", "networkmanager", "upower"] logind = ["logind-zbus", "zbus"] networkmanager = ["cosmic-dbus-networkmanager", "zbus"] upower = ["upower_dbus", "zbus"] + +[workspace] +members = ["daemon"] + +[workspace.dependencies] +env_logger = "0.10.0" +log = "0.4.20" +pwd = "1.4.0" +ron = "0.8" +serde = "1" +tokio = "1.33.0" +zbus = "3.14.1" + +[workspace.dependencies.cosmic-bg-config] +git = "https://github.com/pop-os/cosmic-bg" +default-features = false + +[workspace.dependencies.cosmic-config] +git = "https://github.com/pop-os/libcosmic" +default-features = false + +[workspace.dependencies.libcosmic] +git = "https://github.com/pop-os/libcosmic" +default-features = false diff --git a/daemon/Cargo.toml b/daemon/Cargo.toml new file mode 100644 index 0000000..526c572 --- /dev/null +++ b/daemon/Cargo.toml @@ -0,0 +1,20 @@ +[package] +name = "cosmic-greeter-daemon" +version = "0.1.0" +edition = "2021" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +cosmic-bg-config.workspace = true +cosmic-config.workspace = true +env_logger.workspace = true +libc = "0.2" +libcosmic.workspace = true +log.workspace = true +pwd.workspace = true +ron.workspace = true +serde.workspace = true +zbus.workspace = true +#TODO: reduce features +tokio = { workspace = true, features = ["full"] } diff --git a/daemon/src/lib.rs b/daemon/src/lib.rs new file mode 100644 index 0000000..e00c202 --- /dev/null +++ b/daemon/src/lib.rs @@ -0,0 +1,16 @@ +pub use cosmic_bg_config::Color; + +#[derive(Clone, Debug, serde::Deserialize, serde::Serialize)] +pub struct UserData { + pub uid: u32, + pub name: String, + pub full_name_opt: Option, + pub icon_opt: Option>, + pub wallpapers_opt: Option>, +} + +#[derive(Clone, Debug, serde::Deserialize, serde::Serialize)] +pub enum WallpaperData { + Bytes(Vec), + Color(Color), +} diff --git a/daemon/src/main.rs b/daemon/src/main.rs new file mode 100644 index 0000000..ea7f600 --- /dev/null +++ b/daemon/src/main.rs @@ -0,0 +1,181 @@ +use cosmic_bg_config::Source; +use cosmic_greeter_daemon::{UserData, WallpaperData}; +use std::{error::Error, fs, future::pending, io, path::Path}; +use zbus::{dbus_interface, ConnectionBuilder, DBusError}; + +//IMPORTANT: this function is critical to the security of this proxy. It must ensure that the +// callback is executed with the permissions of the specified user id. A good test is to see if +// the /etc/shadow file can be read with a non-root user, it should fail with EPERM. +fn run_as_user T, T>(user: &pwd::Passwd, f: F) -> Result { + if unsafe { libc::seteuid(user.uid) } != 0 { + return Err(io::Error::last_os_error()); + } + + let t = f(); + + if unsafe { libc::seteuid(0) } != 0 { + panic!("failed to restore root user id") + } + + Ok(t) +} + +#[derive(DBusError, Debug)] +#[dbus_error(prefix = "com.system76.CosmicGreeter")] +enum GreeterError { + #[dbus_error(zbus_error)] + ZBus(zbus::Error), + Ron(String), + RunAsUser(String), +} + +struct GreeterProxy; + +#[dbus_interface(name = "com.system76.CosmicGreeter")] +impl GreeterProxy { + fn get_user_data(&mut self) -> Result { + // The pwd::Passwd method is unsafe (but not labelled as such) due to using global state (libc pwent functions). + // To prevent issues, this should only be called once in the entire process space at a time + let users: Vec<_> = /* unsafe */ { + pwd::Passwd::iter() + .filter(|user| { + if user.uid < 1000 { + // Skip system accounts + return false; + } + + match Path::new(&user.shell).file_name().and_then(|x| x.to_str()) { + // Skip shell ending in false + Some("false") => false, + // Skip shell ending in nologin + Some("nologin") => false, + _ => true, + } + }) + .collect() + }; + + let mut user_datas = Vec::new(); + for user in users { + if user.uid < 1000 { + // Skip system accounts + continue; + } + + match Path::new(&user.shell).file_name().and_then(|x| x.to_str()) { + // Skip shell ending in false + Some("false") => continue, + // Skip shell ending in nologin + Some("nologin") => continue, + _ => (), + } + + //TODO: use accountsservice + //IMPORTANT: This file is owned by root and safe to read (it won't be a link to /etc/shadow for example) + // It may not exist if the user uses one of the system icons. In that case, we should read the + // information in /var/lib/AccountsService/users, and then read the icon path as the user + let icon_path = Path::new("/var/lib/AccountsService/icons").join(&user.name); + let icon_opt = if icon_path.is_file() { + match fs::read(&icon_path) { + Ok(icon_data) => Some(icon_data), + Err(err) => { + log::error!("failed to read icon {:?}: {:?}", icon_path, err); + None + } + } + } else { + None + }; + + //IMPORTANT: Assume the identity of the user to ensure we don't read wallpaper file data as root + let wallpapers_opt = run_as_user(&user, || { + //TODO: use libcosmic to find this path + let wallpapers_path = Path::new(&user.dir) + .join(".local/state/cosmic/com.system76.CosmicBackground/v1/wallpapers"); + if wallpapers_path.is_file() { + match fs::read_to_string(&wallpapers_path) { + Ok(wallpapers_ron) => { + match ron::from_str::>(&wallpapers_ron) { + Ok(sources) => { + let mut wallpaper_datas = Vec::new(); + for (output, source) in sources { + match source { + Source::Path(path) => match fs::read(&path) { + Ok(bytes) => { + wallpaper_datas.push(( + output, + WallpaperData::Bytes(bytes), + )); + } + Err(err) => { + log::error!( + "failed to read wallpaper {:?}: {:?}", + path, + err + ); + } + }, + Source::Color(color) => { + wallpaper_datas + .push((output, WallpaperData::Color(color))); + } + } + } + Some(wallpaper_datas) + } + Err(err) => { + log::error!( + "failed to parse wallpapers {:?}: {:?}", + wallpapers_path, + err + ); + None + } + } + } + Err(err) => { + log::error!( + "failed to read wallpapers {:?}: {:?}", + wallpapers_path, + err + ); + None + } + } + } else { + None + } + }) + .map_err(|err| GreeterError::RunAsUser(err.to_string()))?; + + user_datas.push(UserData { + uid: user.uid, + name: user.name, + full_name_opt: user + .gecos + .map(|gecos| gecos.split(',').next().unwrap_or_default().to_string()), + icon_opt, + //TODO: should wallpapers come from a per-user call? + wallpapers_opt, + }); + } + + //TODO: is ron the best choice for passing around background data? + ron::to_string(&user_datas).map_err(|err| GreeterError::Ron(err.to_string())) + } +} + +#[tokio::main] +async fn main() -> Result<(), Box> { + env_logger::init(); + + let _conn = ConnectionBuilder::system()? + .name("com.system76.CosmicGreeter")? + .serve_at("/com/system76/CosmicGreeter", GreeterProxy)? + .build() + .await?; + + pending::<()>().await; + + Ok(()) +} diff --git a/dbus/com.system76.CosmicGreeter.conf b/dbus/com.system76.CosmicGreeter.conf new file mode 100644 index 0000000..1862d04 --- /dev/null +++ b/dbus/com.system76.CosmicGreeter.conf @@ -0,0 +1,14 @@ + + + + + + + + + + + + diff --git a/debian/control b/debian/control index 5790cbe..ecec0f4 100644 --- a/debian/control +++ b/debian/control @@ -20,8 +20,16 @@ Depends: adduser, bash, cosmic-comp, + cosmic-greeter-daemon, greetd, ${misc:Depends}, ${shlibs:Depends} Provides: x-display-manager Description: Cosmic Greeter + +Package: cosmic-greeter-daemon +Architecture: amd64 arm64 +Depends: + ${misc:Depends}, + ${shlibs:Depends} +Description: Cosmic Greeter daemon diff --git a/debian/cosmic-greeter-daemon.install b/debian/cosmic-greeter-daemon.install new file mode 100644 index 0000000..8b2e7be --- /dev/null +++ b/debian/cosmic-greeter-daemon.install @@ -0,0 +1,2 @@ +/usr/bin/cosmic-greeter-daemon +/usr/share/dbus-1/system.d/com.system76.CosmicGreeter.conf diff --git a/debian/cosmic-greeter-daemon.service b/debian/cosmic-greeter-daemon.service new file mode 100644 index 0000000..1f93134 --- /dev/null +++ b/debian/cosmic-greeter-daemon.service @@ -0,0 +1,9 @@ +[Unit] +Description=COSMIC Greeter Daemon + +[Service] +ExecStart=/usr/bin/cosmic-greeter-daemon +Restart=on-failure + +[Install] +WantedBy=multi-user.target diff --git a/debian/cosmic-greeter.install b/debian/cosmic-greeter.install index 9dcb7b0..483a276 100644 --- a/debian/cosmic-greeter.install +++ b/debian/cosmic-greeter.install @@ -1 +1,2 @@ cosmic-greeter.toml /etc/greetd/ +/usr/bin/cosmic-greeter diff --git a/debian/cosmic-greeter.service b/debian/cosmic-greeter.service index 4d783e7..3c1451d 100644 --- a/debian/cosmic-greeter.service +++ b/debian/cosmic-greeter.service @@ -1,6 +1,6 @@ [Unit] -Description=COSMIC Greeter daemon -After=systemd-user-sessions.service plymouth-quit-wait.service +Description=COSMIC Greeter +After=systemd-user-sessions.service plymouth-quit-wait.service cosmic-greeter-daemon.service After=getty@tty1.service Conflicts=getty@tty1.service diff --git a/debian/rules b/debian/rules index 760ec89..a7ba148 100755 --- a/debian/rules +++ b/debian/rules @@ -1,6 +1,6 @@ #!/usr/bin/make -f -export DESTDIR = debian/cosmic-greeter +export DESTDIR = debian/tmp export VENDOR ?= 1 %: @@ -23,3 +23,4 @@ override_dh_auto_install: override_dh_installsystemd: dh_installsystemd -pcosmic-greeter --no-start -r cosmic-greeter.service + dh_installsystemd -pcosmic-greeter-daemon --no-start -r cosmic-greeter-daemon.service diff --git a/justfile b/justfile index 8919cc1..04ea3b3 100644 --- a/justfile +++ b/justfile @@ -11,6 +11,12 @@ export INSTALL_DIR := base-dir / 'share' bin-src := 'target' / 'release' / name bin-dst := base-dir / 'bin' / name +daemon-src := 'target' / 'release' / name + '-daemon' +daemon-dst := base-dir / 'bin' / name + '-daemon' + +dbus-src := 'dbus' / APPID + '.conf' +dbus-dst := base-dir / 'share' / 'dbus-1' / 'system.d' / APPID + '.conf' + # Default recipe which runs `just build-release` default: build-release @@ -24,7 +30,7 @@ clean-dist: clean # Compiles with debug profile build-debug *args: - cargo build {{args}} + cargo build --all {{args}} # Compiles with release profile build-release *args: (build-debug '--release' args) @@ -46,10 +52,12 @@ run *args: # Installs files install: install -Dm0755 {{bin-src}} {{bin-dst}} + install -Dm0755 {{daemon-src}} {{daemon-dst}} + install -Dm0755 {{dbus-src}} {{dbus-dst}} # Uninstalls installed files uninstall: - rm {{bin-dst}} + rm {{bin-dst}} {{daemon-dst}} {{dbus-dst}} # Vendor dependencies locally vendor: diff --git a/src/greeter.rs b/src/greeter.rs index 732bd2b..7ca301e 100644 --- a/src/greeter.rs +++ b/src/greeter.rs @@ -10,7 +10,7 @@ use cosmic::{ self, wayland::{Event as WaylandEvent, LayerEvent, OutputEvent}, }, - futures::SinkExt, + futures::{self, SinkExt}, subscription, wayland::{ actions::layer_surface::{IcedMargin, IcedOutput, SctkLayerSurfaceSettings}, @@ -23,14 +23,37 @@ use cosmic::{ iced_runtime::core::window::Id as SurfaceId, style, widget, Element, }; +use cosmic_greeter_daemon::{UserData, WallpaperData}; use greetd_ipc::{codec::SyncCodec, AuthMessageType, Request, Response}; -use std::{collections::HashMap, env, fs, io, path::Path, process, sync::Arc}; +use std::{collections::HashMap, env, error::Error, fs, io, path::Path, process, sync::Arc}; use tokio::{net::UnixStream, time}; use wayland_client::{protocol::wl_output::WlOutput, Proxy}; +use zbus::{dbus_proxy, Connection}; -pub fn main() -> Result<(), Box> { +#[dbus_proxy( + interface = "com.system76.CosmicGreeter", + default_service = "com.system76.CosmicGreeter", + default_path = "/com/system76/CosmicGreeter" +)] +trait Greeter { + async fn get_user_data(&self) -> Result; +} + +async fn user_data_dbus() -> Result, Box> { + let connection = Connection::system().await?; + + // `dbus_proxy` macro creates `MyGreaterProxy` based on `Notifications` trait. + let proxy = GreeterProxy::new(&connection).await?; + let reply = proxy.get_user_data().await?; + + let user_datas: Vec = ron::from_str(&reply)?; + Ok(user_datas) +} + +fn user_data_fallback() -> Vec { // The pwd::Passwd method is unsafe (but not labelled as such) due to using global state (libc pwent functions). - let users: Vec<_> = /* unsafe */ { + /* unsafe */ + { pwd::Passwd::iter() .filter(|user| { if user.uid < 1000 { @@ -51,7 +74,7 @@ pub fn main() -> Result<(), Box> { let icon_path = Path::new("/var/lib/AccountsService/icons").join(&user.name); let icon_opt = if icon_path.is_file() { match fs::read(&icon_path) { - Ok(icon_data) => Some(widget::image::Handle::from_memory(icon_data)), + Ok(icon_data) => Some(icon_data), Err(err) => { log::error!("failed to read {:?}: {:?}", icon_path, err); None @@ -60,11 +83,33 @@ pub fn main() -> Result<(), Box> { } else { None }; - (user, icon_opt) + + UserData { + uid: user.uid, + name: user.name, + full_name_opt: user + .gecos + .map(|gecos| gecos.split(',').next().unwrap_or_default().to_string()), + icon_opt, + wallpapers_opt: None, + } }) .collect() + } +} + +pub fn main() -> Result<(), Box> { + let mut user_datas = match futures::executor::block_on(async { user_data_dbus().await }) { + Ok(ok) => ok, + Err(err) => { + log::error!("failed to load user data from daemon: {}", err); + user_data_fallback() + } }; + // Sort user data by uid + user_datas.sort_by(|a, b| a.uid.cmp(&b.uid)); + enum SessionType { X11, Wayland, @@ -186,13 +231,13 @@ pub fn main() -> Result<(), Box> { sessions }; - //TODO: use background config - let background = widget::image::Handle::from_memory(include_bytes!("../res/background.png")); + let fallback_background = + widget::image::Handle::from_memory(include_bytes!("../res/background.png")); let flags = Flags { - users, + user_datas, sessions, - background, + fallback_background, }; let settings = Settings::default().no_main_window(true); @@ -287,9 +332,9 @@ fn request_command(socket: Arc, request: Request) -> Command)>, + user_datas: Vec, sessions: HashMap>, - background: widget::image::Handle, + fallback_background: widget::image::Handle, } #[derive(Clone, Debug)] @@ -329,6 +374,7 @@ pub struct App { flags: Flags, surface_ids: HashMap, active_surface_id_opt: Option, + surface_images: HashMap, surface_names: HashMap, text_input_ids: HashMap, network_icon_opt: Option<&'static str>, @@ -341,6 +387,55 @@ pub struct App { error_opt: Option, } +impl App { + fn update_wallpapers(&mut self) { + let username = match &self.username_opt { + Some(some) => some, + None => return, + }; + + let user_data = match self.flags.user_datas.iter().find(|x| &x.name == username) { + Some(some) => some, + None => return, + }; + + let wallpapers = match &user_data.wallpapers_opt { + Some(some) => some, + None => return, + }; + + for (output, surface_id) in self.surface_ids.iter() { + if self.surface_images.contains_key(surface_id) { + continue; + } + + let output_name = match self.surface_names.get(surface_id) { + Some(some) => some, + None => continue, + }; + + log::info!("updating wallpaper for {:?}", output_name); + + for (wallpaper_output_name, wallpaper_data) in wallpapers.iter() { + if wallpaper_output_name == output_name { + match wallpaper_data { + WallpaperData::Bytes(bytes) => { + let image = widget::image::Handle::from_memory(bytes.clone()); + self.surface_images.insert(*surface_id, image); + //TODO: what to do about duplicates? + break; + } + WallpaperData::Color(color) => { + //TODO: support color sources + log::warn!("output {}: unsupported source {:?}", output.id(), color); + } + } + } + } + } + } +} + /// Implement [`cosmic::Application`] to integrate with COSMIC. impl cosmic::Application for App { /// Default async executor to use with the app. @@ -384,12 +479,12 @@ impl cosmic::Application for App { flags, surface_ids: HashMap::new(), active_surface_id_opt: None, + surface_images: HashMap::new(), surface_names: HashMap::new(), text_input_ids: HashMap::new(), network_icon_opt: None, power_info_opt: None, socket_state: SocketState::Pending, - //TODO: choose last used user username_opt: None, prompt_opt: None, session_names, @@ -437,6 +532,8 @@ impl cosmic::Application for App { Some(output_info) => match output_info.name { Some(output_name) => { self.surface_names.insert(surface_id, output_name.clone()); + self.surface_images.remove(&surface_id); + self.update_wallpapers(); } None => { log::warn!("output {}: no output name", output.id()); @@ -477,6 +574,7 @@ impl cosmic::Application for App { log::info!("output {}: removed", output.id()); match self.surface_ids.remove(&output) { Some(surface_id) => { + self.surface_images.remove(&surface_id); self.surface_names.remove(&surface_id); self.text_input_ids.remove(&surface_id); return destroy_layer_surface(surface_id); @@ -503,6 +601,23 @@ impl cosmic::Application for App { }, Message::Socket(socket_state) => { self.socket_state = socket_state; + match &self.socket_state { + SocketState::Open(socket) => { + // When socket is opened, request default user + //TODO: choose last used user + match self.flags.user_datas.first().map(|x| x.name.clone()) { + Some(username) => { + let socket = socket.clone(); + return Command::perform( + async { message::app(Message::Username(socket, username)) }, + |x| x, + ); + } + None => {} + } + } + _ => {} + } } Message::NetworkIcon(network_icon_opt) => { self.network_icon_opt = network_icon_opt; @@ -526,6 +641,8 @@ impl cosmic::Application for App { } Message::Username(socket, username) => { self.username_opt = Some(username.clone()); + self.surface_images.clear(); + self.update_wallpapers(); return request_command(socket, Request::CreateSession { username }); } Message::Auth(socket, response) => { @@ -566,6 +683,7 @@ impl cosmic::Application for App { Message::Exit => { let mut commands = Vec::new(); for (_output, surface_id) in self.surface_ids.drain() { + self.surface_images.remove(&surface_id); self.surface_names.remove(&surface_id); self.text_input_ids.remove(&surface_id); commands.push(destroy_layer_surface(surface_id)); @@ -671,15 +789,20 @@ impl cosmic::Application for App { SocketState::Open(socket) => { match &self.username_opt { Some(username) => { - for (user, icon_opt) in &self.flags.users { - if &user.name == username { - match icon_opt { + for user_data in &self.flags.user_datas { + if &user_data.name == username { + match &user_data.icon_opt { Some(icon) => { column = column.push( widget::container( - widget::Image::new(icon.clone()) - .width(Length::Fixed(78.0)) - .height(Length::Fixed(78.0)), + widget::Image::new( + //TODO: cache handle + widget::image::Handle::from_memory( + icon.clone(), + ), + ) + .width(Length::Fixed(78.0)) + .height(Length::Fixed(78.0)), ) .width(Length::Fill) .align_x(alignment::Horizontal::Center), @@ -687,9 +810,8 @@ impl cosmic::Application for App { } None => {} } - match &user.gecos { - Some(gecos) => { - let full_name = gecos.split(",").next().unwrap_or(""); + match &user_data.full_name_opt { + Some(full_name) => { column = column.push( widget::container(widget::text::title4(full_name)) .width(Length::Fill) @@ -702,18 +824,23 @@ impl cosmic::Application for App { } } None => { - let mut row = - widget::row::with_capacity(self.flags.users.len()).spacing(12.0); - for (user, icon_opt) in &self.flags.users { + let mut row = widget::row::with_capacity(self.flags.user_datas.len()) + .spacing(12.0); + for user_data in &self.flags.user_datas { let mut column = widget::column::with_capacity(2).spacing(12.0); - match icon_opt { + match &user_data.icon_opt { Some(icon) => { column = column.push( widget::container( - widget::Image::new(icon.clone()) - .width(Length::Fixed(78.0)) - .height(Length::Fixed(78.0)), + widget::Image::new( + //TODO: cache handle + widget::image::Handle::from_memory( + icon.clone(), + ), + ) + .width(Length::Fixed(78.0)) + .height(Length::Fixed(78.0)), ) .width(Length::Fill) .align_x(alignment::Horizontal::Center), @@ -721,9 +848,8 @@ impl cosmic::Application for App { } None => {} } - match &user.gecos { - Some(gecos) => { - let full_name = gecos.split(",").next().unwrap_or(""); + match &user_data.full_name_opt { + Some(full_name) => { column = column.push( widget::container(widget::text::title4(full_name)) .width(Length::Fill) @@ -740,7 +866,9 @@ impl cosmic::Application for App { .padding(16) .style(cosmic::theme::Container::Card), ) - .on_press(Message::Username(socket.clone(), user.name.clone())), + .on_press( + Message::Username(socket.clone(), user_data.name.clone()), + ), ); } column = column.push(row); @@ -844,7 +972,10 @@ impl cosmic::Application for App { .align_y(alignment::Vertical::Top) .style(cosmic::theme::Container::Transparent), ) - .image(self.flags.background.clone()) + .image(match self.surface_images.get(&surface_id) { + Some(some) => some.clone(), + None => self.flags.fallback_background.clone(), + }) .content_fit(iced::ContentFit::Cover) .into() }