diff --git a/cosmic-portal-config/src/background.rs b/cosmic-portal-config/src/background.rs index a096053..38d07d1 100644 --- a/cosmic-portal-config/src/background.rs +++ b/cosmic-portal-config/src/background.rs @@ -1,19 +1,15 @@ // SPDX-License-Identifier: GPL-3.0-only -use std::collections::HashMap; - use serde::{Deserialize, Serialize}; -#[derive(Debug, Clone, Default, PartialEq, Deserialize, Serialize)] +#[derive(Debug, Clone, Copy, Default, PartialEq, Deserialize, Serialize)] #[serde(deny_unknown_fields)] pub struct Background { - /// App ID and allowed status - pub apps: HashMap, /// Default preference for NotifyBackground's dialog pub default_perm: PermissionDialog, } -#[derive(Debug, Clone, Default, PartialEq, Eq, Deserialize, Serialize)] +#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Deserialize, Serialize)] pub enum PermissionDialog { /// Grant apps permission to run in the background Allow, diff --git a/src/app.rs b/src/app.rs index f23532f..fd5e08a 100644 --- a/src/app.rs +++ b/src/app.rs @@ -31,6 +31,7 @@ pub struct CosmicPortal { pub config_handler: Option, pub config: config::Config, + pub tx_conf: Option>, pub access_args: Option, pub access_choices: Vec<(Option, Vec)>, @@ -71,10 +72,7 @@ pub enum Msg { Background(background::Msg), Portal(subscription::Event), Output(OutputEvent, WlOutput), - ConfigUpdateBackground { - app_id: String, - choice: Option, - }, + ConfigNotifyWatcher, ConfigSetScreenshot(config::screenshot::Screenshot), /// Update config from external changes ConfigSubUpdate(config::Config), @@ -136,6 +134,7 @@ impl cosmic::Application for CosmicPortal { core, config_handler, config, + tx_conf: None, access_args: Default::default(), access_choices: Default::default(), file_choosers: Default::default(), @@ -196,36 +195,15 @@ impl cosmic::Application for CosmicPortal { subscription::Event::Background(args) => { background::update_args(self, args).map(cosmic::app::Message::App) } - subscription::Event::BackgroundGetAppPerm(app_id, tx) => { - let perm = match self.config.background.default_perm { - config::background::PermissionDialog::Allow => { - background::ConfigAppPerm::DefaultAllow - } - config::background::PermissionDialog::Deny => { - background::ConfigAppPerm::DefaultDeny - } - _ => match self.config.background.apps.get(&app_id) { - Some(true) => background::ConfigAppPerm::UserAllow, - Some(false) => background::ConfigAppPerm::UserDeny, - None => background::ConfigAppPerm::Unset, - }, - }; - cosmic::Command::perform( - async move { - let _ = tx.send(perm).await; - cosmic::app::message::none() - }, - |x| x, - ) - } subscription::Event::Config(config) => self.update(Msg::ConfigSubUpdate(config)), subscription::Event::Accent(_) | subscription::Event::IsDark(_) | subscription::Event::HighContrast(_) | subscription::Event::BackgroundToplevels => cosmic::iced::Command::none(), - subscription::Event::Init(tx) => { + subscription::Event::Init { tx, tx_conf } => { self.tx = Some(tx); - Command::none() + self.tx_conf = Some(tx_conf); + self.update(Msg::ConfigNotifyWatcher) } }, Msg::Screenshot(m) => screenshot::update_msg(self, m).map(cosmic::app::Message::App), @@ -305,20 +283,10 @@ impl cosmic::Application for CosmicPortal { cosmic::iced::Command::none() } - Msg::ConfigUpdateBackground { app_id, choice } => { - if let (Some(choice), Some(handler)) = (choice, &mut self.config_handler) { - self.config - .background - .apps - .insert(app_id, choice == background::PermissionResponse::Allow); - if let Err(e) = self - .config - .set_background(handler, self.config.background.clone()) - { - log::error!("Failed to save background config: {e}"); - } + Msg::ConfigNotifyWatcher => { + if let Some(tx) = self.tx_conf.as_mut() { + tx.send_replace(self.config.clone()); } - cosmic::iced::Command::none() } Msg::ConfigSetScreenshot(screenshot) => { @@ -330,12 +298,11 @@ impl cosmic::Application for CosmicPortal { } None => log::error!("Failed to save config: No config handler"), } - - cosmic::iced::Command::none() + self.update(Msg::ConfigNotifyWatcher) } Msg::ConfigSubUpdate(config) => { self.config = config; - cosmic::iced::Command::none() + self.update(Msg::ConfigNotifyWatcher) } } } diff --git a/src/background.rs b/src/background.rs index 3e21935..0db32a2 100644 --- a/src/background.rs +++ b/src/background.rs @@ -1,29 +1,37 @@ // SPDX-License-Identifier: GPL-3.0-only -use std::{ - collections::HashMap, - sync::{Arc, Condvar, Mutex}, -}; +use std::sync::{Arc, Condvar, Mutex}; -use ashpd::enumflags2::BitFlags; +// use ashpd::enumflags2::{bitflags, BitFlag, BitFlags}; use cosmic::{iced::window, widget}; use cosmic_protocols::toplevel_info::v1::client::zcosmic_toplevel_handle_v1; use futures::{FutureExt, TryFutureExt}; -use tokio::sync::mpsc::Sender; +use tokio::sync::{mpsc, watch}; use zbus::{fdo, object_server::SignalContext, zvariant}; -use crate::{app::CosmicPortal, fl, subscription, wayland::WaylandHelper, PortalResponse}; +use crate::{ + app::CosmicPortal, + config::{self, background::PermissionDialog}, + fl, subscription, + wayland::WaylandHelper, + PortalResponse, +}; /// Background portal backend /// /// https://flatpak.github.io/xdg-desktop-portal/docs/doc-org.freedesktop.impl.portal.Background.html pub struct Background { wayland_helper: WaylandHelper, - tx: Sender, + tx: mpsc::Sender, + rx_conf: watch::Receiver, } impl Background { - pub fn new(wayland_helper: WaylandHelper, tx: Sender) -> Self { + pub fn new( + wayland_helper: WaylandHelper, + tx: mpsc::Sender, + rx_conf: watch::Receiver, + ) -> Self { let toplevel_signal = wayland_helper.toplevel_signal(); let toplevel_tx = tx.clone(); std::thread::Builder::new() @@ -31,27 +39,29 @@ impl Background { .spawn(move || Background::toplevel_signal(toplevel_signal, toplevel_tx)) .expect("Spawning toplevels update thread should succeed"); - Self { wayland_helper, tx } + Self { + wayland_helper, + tx, + rx_conf, + } } /// Trigger [`Background::running_applications_changed`] on toplevel updates - fn toplevel_signal(signal: Arc<(Mutex, Condvar)>, tx: Sender) { + fn toplevel_signal(signal: Arc<(Mutex, Condvar)>, tx: mpsc::Sender) { loop { let (lock, cvar) = &*signal; let mut updated = lock.lock().unwrap(); - log::debug!("[background] Waiting for toplevel updates"); + log::debug!("Waiting for toplevel updates"); while !*updated { updated = cvar.wait(updated).unwrap(); } - log::debug!( - "[background] Emitting RunningApplicationsChanged in response to toplevel updates" - ); + log::debug!("Emitting RunningApplicationsChanged in response to toplevel updates"); debug_assert!(*updated); *updated = false; if let Err(e) = tx.blocking_send(subscription::Event::BackgroundToplevels) { - log::warn!("[background] Failed sending event to trigger RunningApplicationsChanged: {e:?}"); + log::warn!("Failed sending event to trigger RunningApplicationsChanged: {e:?}"); } } } @@ -60,8 +70,8 @@ impl Background { #[zbus::interface(name = "org.freedesktop.impl.portal.Background")] impl Background { /// Status on running apps (active, running, or background) - async fn get_app_state(&self) -> fdo::Result> { - let toplevels: HashMap<_, _> = self + async fn get_app_state(&self) -> fdo::Result> { + let toplevels: Vec<_> = self .wayland_helper .toplevels() .into_iter() @@ -80,16 +90,16 @@ impl Background { AppStatus::Background }; - (info.app_id, status) + AppState { + app_id: info.app_id, + status, + } }) .collect(); - log::debug!( - "[background] GetAppState returning {} toplevels", - toplevels.len() - ); + log::debug!("GetAppState returning {} toplevels", toplevels.len()); #[cfg(debug_assertions)] - log::trace!("[background] App status: {toplevels:#?}"); + log::trace!("App status: {toplevels:#?}"); Ok(toplevels) } @@ -101,44 +111,30 @@ impl Background { app_id: String, name: String, ) -> PortalResponse { - log::debug!("[background] Request handle: {handle:?}"); + log::debug!("Request handle: {handle:?}"); - // Request only what's needed to avoid cloning and receiving the entire config + // Request a copy of the config from the main app instance // This is also cleaner than storing the config because it's difficult to keep it // updated without synch primitives and we also avoid &mut self - let (tx, mut rx) = tokio::sync::mpsc::channel(1); - let config = self - .tx - .send(subscription::Event::BackgroundGetAppPerm( - app_id.clone(), - tx, - )) - .inspect_err(|e| { - log::error!("[background] Failed receiving background config from main app {e:?}") - }) - .map_ok(|_| ConfigAppPerm::default()) - .map_err(|_| ()) - .and_then(|_| rx.recv().map(|out| out.ok_or(()))) - .await - .unwrap_or_default(); + let config = self.rx_conf.borrow().background; - match config { + match config.default_perm { // Skip dialog based on default response set in configs - ConfigAppPerm::DefaultAllow => { - log::debug!("[background] AUTO ALLOW {name} based on default permission"); + PermissionDialog::Allow => { + log::debug!("AUTO ALLOW {name} based on default permission"); PortalResponse::Success(NotifyBackgroundResult { result: PermissionResponse::Allow, }) } - ConfigAppPerm::DefaultDeny => { - log::debug!("[background] AUTO DENY {name} based on default permission"); + PermissionDialog::Deny => { + log::debug!("AUTO DENY {name} based on default permission"); PortalResponse::Success(NotifyBackgroundResult { result: PermissionResponse::Deny, }) } // Dialog - ConfigAppPerm::Unset => { - log::debug!("[background] Requesting permission for {app_id} ({name})",); + PermissionDialog::Ask => { + log::debug!("Requesting user permission for {app_id} ({name})",); let handle = handle.to_owned(); let id = window::Id::unique(); @@ -151,7 +147,7 @@ impl Background { tx, })) .inspect_err(|e| { - log::error!("[background] Failed to send message to register permissions dialog: {e:?}") + log::error!("Failed to send message to register permissions dialog: {e:?}") }) .map_ok(|_| PortalResponse::::Other) .map_err(|_| ()) @@ -159,19 +155,6 @@ impl Background { .unwrap_or_else(|_| PortalResponse::Other) .await } - // We asked the user about this app already - ConfigAppPerm::UserAllow => { - log::debug!("[background] AUTO ALLOW {app_id} ({name}) based on cached response"); - PortalResponse::Success(NotifyBackgroundResult { - result: PermissionResponse::Allow, - }) - } - ConfigAppPerm::UserDeny => { - log::debug!("[background] AUTO DENY {app_id} ({name}) based on cached response"); - PortalResponse::Success(NotifyBackgroundResult { - result: PermissionResponse::Deny, - }) - } } } @@ -185,7 +168,7 @@ impl Background { commandline: Vec, flags: u32, ) -> fdo::Result { - log::warn!("[background] Autostart not implemented"); + log::warn!("Autostart not implemented"); Ok(enable) } @@ -195,7 +178,7 @@ impl Background { } #[derive(Clone, Copy, Debug, PartialEq, Eq, serde::Serialize, zvariant::Type)] -#[zvariant(signature = "v")] +#[zvariant(signature = "u")] pub enum AppStatus { /// No open windows Background = 0, @@ -205,6 +188,13 @@ pub enum AppStatus { Active, } +#[derive(Clone, Debug, serde::Serialize, zvariant::Type)] +#[zvariant(signature = "{sv}")] +struct AppState { + app_id: String, + status: AppStatus, +} + /// Result vardict for [`Background::notify_background`] #[derive(Clone, Debug, zvariant::SerializeDict, zvariant::Type)] #[zvariant(signature = "a{sv}")] @@ -224,24 +214,13 @@ pub enum PermissionResponse { AllowOnce, } -/// Evaluated permissions from background config -#[derive(Clone, Copy, Default, PartialEq, Eq)] -pub enum ConfigAppPerm { - DefaultAllow, - DefaultDeny, - #[default] - Unset, - UserAllow, - UserDeny, -} - /// Background permissions dialog state #[derive(Clone, Debug)] pub struct Args { pub handle: zvariant::ObjectPath<'static>, pub id: window::Id, pub app_id: String, - tx: Sender>, + tx: mpsc::Sender>, } /// Background permissions dialog response @@ -254,6 +233,13 @@ pub enum Msg { Cancel(window::Id), } +// #[bitflags] +// #[repr(u32)] +// #[derive(Clone, Copy, Debug, PartialEq)] +// enum AutostartFlags { +// DBus = 0x01, +// } + /// Permissions dialog pub(crate) fn view(portal: &CosmicPortal, id: window::Id) -> cosmic::Element { let name = portal @@ -293,7 +279,7 @@ pub fn update_args(portal: &mut CosmicPortal, args: Args) -> cosmic::Command cosmic::Command { let Some(Args { @@ -345,28 +328,23 @@ pub fn update_msg(portal: &mut CosmicPortal, msg: Msg) -> cosmic::Command), Background(crate::background::Args), - BackgroundGetAppPerm(String, Sender), BackgroundToplevels, Accent(Srgba), IsDark(bool), HighContrast(bool), Config(config::Config), - Init(tokio::sync::mpsc::Sender), + Init { + tx: tokio::sync::mpsc::Sender, + tx_conf: tokio::sync::watch::Sender, + }, } impl Debug for Event { @@ -80,17 +82,16 @@ impl Debug for Event { Event::Screencast(s) => s.fmt(f), Event::CancelScreencast(h) => f.debug_tuple("CancelScreencast").field(h).finish(), Event::Background(b) => b.fmt(f), - Event::BackgroundGetAppPerm(app_id, tx) => f - .debug_tuple("BackgroundGetAppPerm") - .field(app_id) - .field(tx) - .finish(), Event::BackgroundToplevels => f.debug_tuple("BackgroundToplevels").finish(), Event::Accent(a) => a.fmt(f), Event::IsDark(t) => t.fmt(f), Event::HighContrast(c) => c.fmt(f), Event::Config(c) => c.fmt(f), - Event::Init(tx) => tx.fmt(f), + Event::Init { tx, tx_conf } => f + .debug_struct("Init") + .field("tx", tx) + .field("tx_conf", tx_conf) + .finish(), } } } @@ -142,13 +143,14 @@ pub(crate) async fn process_changes( match state { State::Init => { let (tx, rx) = tokio::sync::mpsc::channel(10); + let (tx_conf, rx_conf) = tokio::sync::watch::channel(config::Config::default()); let connection = zbus::ConnectionBuilder::session()? .name(DBUS_NAME)? .serve_at(DBUS_PATH, Access::new(wayland_helper.clone(), tx.clone()))? .serve_at( DBUS_PATH, - Background::new(wayland_helper.clone(), tx.clone()), + Background::new(wayland_helper.clone(), tx.clone(), rx_conf.clone()), )? .serve_at(DBUS_PATH, FileChooser::new(tx.clone()))? .serve_at( @@ -162,7 +164,7 @@ pub(crate) async fn process_changes( .serve_at(DBUS_PATH, Settings::new())? .build() .await?; - _ = output.send(Event::Init(tx)).await; + _ = output.send(Event::Init { tx, tx_conf }).await; *state = State::Waiting(connection, rx); } State::Waiting(conn, rx) => { @@ -198,12 +200,6 @@ pub(crate) async fn process_changes( log::error!("Error sending background event: {:?}", err); } } - Event::BackgroundGetAppPerm(app_id, tx) => { - if let Err(err) = output.send(Event::BackgroundGetAppPerm(app_id, tx)).await - { - log::error!("Error sending background config request event: {:?}", err); - } - } Event::BackgroundToplevels => { let background = conn .object_server() @@ -272,7 +268,7 @@ pub(crate) async fn process_changes( log::error!("Error sending config update: {:?}", err) } } - Event::Init(_) => {} + Event::Init { .. } => {} } } }