diff --git a/Cargo.lock b/Cargo.lock index 0e4e2ef..5167d12 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -834,9 +834,9 @@ version = "0.1.0" dependencies = [ "cosmic-bg-config", "cosmic-config", + "cosmic-theme", "env_logger", "libc", - "libcosmic", "log", "pwd", "ron", diff --git a/Cargo.toml b/Cargo.toml index b9597b3..bc69503 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -58,6 +58,10 @@ default-features = false git = "https://github.com/pop-os/libcosmic" default-features = false +[workspace.dependencies.cosmic-theme] +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 index 526c572..8cbef6b 100644 --- a/daemon/Cargo.toml +++ b/daemon/Cargo.toml @@ -8,9 +8,9 @@ edition = "2021" [dependencies] cosmic-bg-config.workspace = true cosmic-config.workspace = true +cosmic-theme.workspace = true env_logger.workspace = true libc = "0.2" -libcosmic.workspace = true log.workspace = true pwd.workspace = true ron.workspace = true diff --git a/daemon/src/lib.rs b/daemon/src/lib.rs index e00c202..ce0c4b3 100644 --- a/daemon/src/lib.rs +++ b/daemon/src/lib.rs @@ -1,4 +1,5 @@ pub use cosmic_bg_config::Color; +pub use cosmic_theme::Theme; #[derive(Clone, Debug, serde::Deserialize, serde::Serialize)] pub struct UserData { @@ -6,6 +7,7 @@ pub struct UserData { pub name: String, pub full_name_opt: Option, pub icon_opt: Option>, + pub theme_opt: Option, pub wallpapers_opt: Option>, } diff --git a/daemon/src/main.rs b/daemon/src/main.rs index ea7f600..f321d24 100644 --- a/daemon/src/main.rs +++ b/daemon/src/main.rs @@ -1,22 +1,37 @@ use cosmic_bg_config::Source; +use cosmic_config::CosmicConfigEntry; use cosmic_greeter_daemon::{UserData, WallpaperData}; -use std::{error::Error, fs, future::pending, io, path::Path}; +use std::{env, 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 { + // Save root HOME + let root_home_opt = env::var_os("HOME"); + + // Switch to user HOME + env::set_var("HOME", &user.dir); + + // Switch to user UID if unsafe { libc::seteuid(user.uid) } != 0 { return Err(io::Error::last_os_error()); } let t = f(); + // Restore root UID if unsafe { libc::seteuid(0) } != 0 { panic!("failed to restore root user id") } + // Restore root HOME + match root_home_opt { + Some(root_home) => env::set_var("HOME", root_home), + None => env::remove_var("HOME"), + } + Ok(t) } @@ -87,77 +102,96 @@ impl GreeterProxy { None }; + let mut user_data = UserData { + uid: user.uid, + name: user.name.clone(), + full_name_opt: user + .gecos + .as_ref() + .map(|gecos| gecos.split(',').next().unwrap_or_default().to_string()), + icon_opt, + theme_opt: None, + //TODO: should wallpapers come from a per-user call? + wallpapers_opt: 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) + run_as_user(&user, || { + let mut is_dark = true; + match cosmic_theme::ThemeMode::config() { + Ok(helper) => match cosmic_theme::ThemeMode::get_entry(&helper) { + Ok(theme_mode) => { + is_dark = theme_mode.is_dark; + } + Err((errs, theme_mode)) => { + log::error!("failed to load cosmic-theme config: {:?}", errs); + is_dark = theme_mode.is_dark; + } + }, + Err(err) => { + log::error!("failed to create cosmic-theme mode helper: {:?}", err); + } + } + + match if is_dark { + cosmic_theme::Theme::dark_config() + } else { + cosmic_theme::Theme::light_config() + } { + Ok(helper) => match cosmic_theme::Theme::get_entry(&helper) { + Ok(theme) => { + user_data.theme_opt = Some(theme); + } + Err((errs, theme)) => { + log::error!("failed to load cosmic-theme config: {:?}", errs); + user_data.theme_opt = Some(theme); + } + }, + Err(err) => { + log::error!("failed to create cosmic-theme config helper: {:?}", err); + } + } + + //TODO: fallback to background config if background state is not set? + let mut wallpaper_state_opt = None; + match cosmic_bg_config::state::State::state() { + Ok(helper) => match cosmic_bg_config::state::State::get_entry(&helper) { + Ok(state) => { + wallpaper_state_opt = Some(state); + } + Err((errs, state)) => { + log::error!("failed to load cosmic-bg state: {:?}", errs); + wallpaper_state_opt = Some(state); + } + }, + Err(err) => { + log::error!("failed to create cosmic-bg state helper: {:?}", err); + } + } + + if let Some(wallpaper_state) = wallpaper_state_opt { + let mut wallpaper_datas = Vec::new(); + for (output, source) in wallpaper_state.wallpapers { + match source { + Source::Path(path) => match fs::read(&path) { + Ok(bytes) => { + wallpaper_datas.push((output, WallpaperData::Bytes(bytes))); } Err(err) => { - log::error!( - "failed to parse wallpapers {:?}: {:?}", - wallpapers_path, - err - ); - None + log::error!("failed to read wallpaper {:?}: {:?}", path, err); } + }, + Source::Color(color) => { + wallpaper_datas.push((output, WallpaperData::Color(color))); } } - Err(err) => { - log::error!( - "failed to read wallpapers {:?}: {:?}", - wallpapers_path, - err - ); - None - } } - } else { - None + user_data.wallpapers_opt = Some(wallpaper_datas); } }) .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, - }); + user_datas.push(user_data); } //TODO: is ron the best choice for passing around background data? diff --git a/debian/rules b/debian/rules index 18fcfc4..f7de117 100755 --- a/debian/rules +++ b/debian/rules @@ -23,4 +23,4 @@ override_dh_auto_install: override_dh_installsystemd: dh_installsystemd -pcosmic-greeter --no-start -r cosmic-greeter.service - dh_installsystemd -pcosmic-greeter-daemon -r cosmic-greeter-daemon.service + dh_installsystemd -pcosmic-greeter-daemon cosmic-greeter-daemon.service diff --git a/src/greeter.rs b/src/greeter.rs index 7ca301e..9a02a0c 100644 --- a/src/greeter.rs +++ b/src/greeter.rs @@ -91,6 +91,7 @@ fn user_data_fallback() -> Vec { .gecos .map(|gecos| gecos.split(',').next().unwrap_or_default().to_string()), icon_opt, + theme_opt: None, wallpapers_opt: None, } }) @@ -388,51 +389,59 @@ pub struct App { } impl App { - fn update_wallpapers(&mut self) { + fn update_user_config(&mut self) -> Command { let username = match &self.username_opt { Some(some) => some, - None => return, + None => return Command::none(), }; let user_data = match self.flags.user_datas.iter().find(|x| &x.name == username) { Some(some) => some, - None => return, + None => return Command::none(), }; - let wallpapers = match &user_data.wallpapers_opt { - Some(some) => some, - None => return, - }; + if let Some(wallpapers) = &user_data.wallpapers_opt { + for (output, surface_id) in self.surface_ids.iter() { + if self.surface_images.contains_key(surface_id) { + continue; + } - 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, + }; - let output_name = match self.surface_names.get(surface_id) { - Some(some) => some, - None => continue, - }; + log::info!("updating wallpaper for {:?}", output_name); - 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); + 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 + ); + } } } } } } + + match &user_data.theme_opt { + Some(theme) => { + cosmic::app::command::set_theme(cosmic::Theme::custom(Arc::new(theme.clone()))) + } + None => Command::none(), + } } } @@ -533,7 +542,6 @@ impl cosmic::Application for App { 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()); @@ -549,6 +557,7 @@ impl cosmic::Application for App { .insert(surface_id, text_input_id.clone()); return Command::batch([ + self.update_user_config(), get_layer_surface(SctkLayerSurfaceSettings { id: surface_id, layer: Layer::Overlay, @@ -642,8 +651,10 @@ 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 }); + return Command::batch([ + self.update_user_config(), + request_command(socket, Request::CreateSession { username }), + ]); } Message::Auth(socket, response) => { return request_command(socket, Request::PostAuthMessageResponse { response });