refactor: optional config subscriptions using dbus
This commit is contained in:
parent
a4d1b1b651
commit
06c33dcf06
14 changed files with 381 additions and 139 deletions
|
|
@ -46,6 +46,7 @@ xdg-portal = ["ashpd"]
|
||||||
applet = ["wayland", "tokio", "cosmic-panel-config", "ron"]
|
applet = ["wayland", "tokio", "cosmic-panel-config", "ron"]
|
||||||
applet-token = []
|
applet-token = []
|
||||||
single-instance = ["dep:zbus", "serde", "ron"]
|
single-instance = ["dep:zbus", "serde", "ron"]
|
||||||
|
dbus-config = ["cosmic-config/dbus", "dep:zbus", "cosmic-settings-daemon"]
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
apply = "0.3.0"
|
apply = "0.3.0"
|
||||||
|
|
@ -68,6 +69,8 @@ css-color = "0.2.5"
|
||||||
nix = { version = "0.27", features = ["process"], optional = true }
|
nix = { version = "0.27", features = ["process"], optional = true }
|
||||||
zbus = {version = "3.14.1", default-features = false, optional = true}
|
zbus = {version = "3.14.1", default-features = false, optional = true}
|
||||||
serde = { version = "1.0.180", optional = true }
|
serde = { version = "1.0.180", optional = true }
|
||||||
|
cosmic-settings-daemon = { git = "https://github.com/pop-os/dbus-settings-bindings", branch = "cosmic-settings-daemon", optional = true }
|
||||||
|
|
||||||
|
|
||||||
[target.'cfg(unix)'.dependencies]
|
[target.'cfg(unix)'.dependencies]
|
||||||
freedesktop-icons = "0.2.4"
|
freedesktop-icons = "0.2.4"
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,7 @@ use proc_macro::TokenStream;
|
||||||
use quote::quote;
|
use quote::quote;
|
||||||
use syn::{self};
|
use syn::{self};
|
||||||
|
|
||||||
#[proc_macro_derive(CosmicConfigEntry)]
|
#[proc_macro_derive(CosmicConfigEntry, attributes(version, id))]
|
||||||
pub fn cosmic_config_entry_derive(input: TokenStream) -> TokenStream {
|
pub fn cosmic_config_entry_derive(input: TokenStream) -> TokenStream {
|
||||||
// Construct a representation of Rust code as a syntax tree
|
// Construct a representation of Rust code as a syntax tree
|
||||||
// that we can manipulate
|
// that we can manipulate
|
||||||
|
|
@ -13,6 +13,24 @@ pub fn cosmic_config_entry_derive(input: TokenStream) -> TokenStream {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn impl_cosmic_config_entry_macro(ast: &syn::DeriveInput) -> TokenStream {
|
fn impl_cosmic_config_entry_macro(ast: &syn::DeriveInput) -> TokenStream {
|
||||||
|
let attributes = &ast.attrs;
|
||||||
|
let version = attributes
|
||||||
|
.iter()
|
||||||
|
.find_map(|attr| {
|
||||||
|
if attr.path.is_ident("version") {
|
||||||
|
match attr.parse_meta() {
|
||||||
|
Ok(syn::Meta::NameValue(syn::MetaNameValue {
|
||||||
|
lit: syn::Lit::Int(lit_int),
|
||||||
|
..
|
||||||
|
})) => Some(lit_int.base10_parse::<u64>().unwrap()),
|
||||||
|
_ => None,
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.unwrap_or(0);
|
||||||
|
|
||||||
let name = &ast.ident;
|
let name = &ast.ident;
|
||||||
|
|
||||||
// Get the fields of the struct
|
// Get the fields of the struct
|
||||||
|
|
@ -64,6 +82,8 @@ fn impl_cosmic_config_entry_macro(ast: &syn::DeriveInput) -> TokenStream {
|
||||||
|
|
||||||
let gen = quote! {
|
let gen = quote! {
|
||||||
impl CosmicConfigEntry for #name {
|
impl CosmicConfigEntry for #name {
|
||||||
|
const VERSION: u64 = #version;
|
||||||
|
|
||||||
fn write_entry(&self, config: &cosmic_config::Config) -> Result<(), cosmic_config::Error> {
|
fn write_entry(&self, config: &cosmic_config::Config) -> Result<(), cosmic_config::Error> {
|
||||||
let tx = config.transaction();
|
let tx = config.transaction();
|
||||||
#(#write_each_config_field)*
|
#(#write_each_config_field)*
|
||||||
|
|
@ -83,7 +103,7 @@ fn impl_cosmic_config_entry_macro(ast: &syn::DeriveInput) -> TokenStream {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn update_keys<T: AsRef<str>>(&mut self, config: &cosmic_config::Config, changed_keys: &[T]) -> (Vec<cosmic_config::Error>, Vec<&str>){
|
fn update_keys<T: AsRef<str>>(&mut self, config: &cosmic_config::Config, changed_keys: &[T]) -> (Vec<cosmic_config::Error>, Vec<&'static str>){
|
||||||
let mut keys = Vec::with_capacity(changed_keys.len());
|
let mut keys = Vec::with_capacity(changed_keys.len());
|
||||||
let mut errors = Vec::new();
|
let mut errors = Vec::new();
|
||||||
for key in changed_keys.iter() {
|
for key in changed_keys.iter() {
|
||||||
|
|
|
||||||
|
|
@ -5,11 +5,13 @@ edition = "2021"
|
||||||
|
|
||||||
[features]
|
[features]
|
||||||
default = ["macro", "subscription"]
|
default = ["macro", "subscription"]
|
||||||
|
dbus = ["dep:zbus", "cosmic-settings-daemon", "futures-util", "subscription"]
|
||||||
macro = ["cosmic-config-derive"]
|
macro = ["cosmic-config-derive"]
|
||||||
subscription = ["iced_futures"]
|
subscription = ["iced_futures"]
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
# For redox support
|
# For redox support
|
||||||
|
zbus = { version = "3.14.1", default-features = false, optional = true }
|
||||||
atomicwrites = { git = "https://github.com/jackpot51/rust-atomicwrites" }
|
atomicwrites = { git = "https://github.com/jackpot51/rust-atomicwrites" }
|
||||||
calloop = { version = "0.12.2", optional = true }
|
calloop = { version = "0.12.2", optional = true }
|
||||||
dirs = "5.0.1"
|
dirs = "5.0.1"
|
||||||
|
|
@ -19,4 +21,6 @@ serde = "1.0.152"
|
||||||
cosmic-config-derive = { path = "../cosmic-config-derive/", optional = true }
|
cosmic-config-derive = { path = "../cosmic-config-derive/", optional = true }
|
||||||
iced = { path = "../iced/", default-features = false, optional = true }
|
iced = { path = "../iced/", default-features = false, optional = true }
|
||||||
iced_futures = { path = "../iced/futures/", default-features = false, optional = true }
|
iced_futures = { path = "../iced/futures/", default-features = false, optional = true }
|
||||||
|
once_cell = "1.19.0"
|
||||||
|
cosmic-settings-daemon = { git = "https://github.com/pop-os/dbus-settings-bindings", branch = "cosmic-settings-daemon", optional = true }
|
||||||
|
futures-util = { version = "0.3", optional = true }
|
||||||
|
|
|
||||||
137
cosmic-config/src/dbus.rs
Normal file
137
cosmic-config/src/dbus.rs
Normal file
|
|
@ -0,0 +1,137 @@
|
||||||
|
use std::ops::Deref;
|
||||||
|
|
||||||
|
use crate::CosmicConfigEntry;
|
||||||
|
use cosmic_settings_daemon::{Changed, ConfigProxy, CosmicSettingsDaemonProxy, Ping};
|
||||||
|
use futures_util::SinkExt;
|
||||||
|
use iced_futures::futures::{future::pending, StreamExt};
|
||||||
|
pub async fn settings_daemon_proxy() -> zbus::Result<CosmicSettingsDaemonProxy<'static>> {
|
||||||
|
let conn = zbus::Connection::session().await?;
|
||||||
|
CosmicSettingsDaemonProxy::new(&conn).await
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct Watcher {
|
||||||
|
proxy: ConfigProxy<'static>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Deref for Watcher {
|
||||||
|
type Target = ConfigProxy<'static>;
|
||||||
|
fn deref(&self) -> &Self::Target {
|
||||||
|
&self.proxy
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Watcher {
|
||||||
|
pub async fn new_config(
|
||||||
|
settings_daemon_proxy: &CosmicSettingsDaemonProxy<'static>,
|
||||||
|
id: &str,
|
||||||
|
version: u64,
|
||||||
|
) -> zbus::Result<Self> {
|
||||||
|
let (path, name) = settings_daemon_proxy.watch_config(id, version).await?;
|
||||||
|
ConfigProxy::builder(settings_daemon_proxy.connection())
|
||||||
|
.path(path)?
|
||||||
|
.destination(name)?
|
||||||
|
.build()
|
||||||
|
.await
|
||||||
|
.map(|proxy| Self { proxy })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct ConfigUpdate<T> {
|
||||||
|
pub errors: Vec<crate::Error>,
|
||||||
|
pub keys: Vec<&'static str>,
|
||||||
|
pub config: T,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn watcher_subscription<T: CosmicConfigEntry + Send + Sync + Default + 'static + Clone>(
|
||||||
|
settings_daemon: CosmicSettingsDaemonProxy<'static>,
|
||||||
|
config_id: &'static str,
|
||||||
|
) -> iced_futures::Subscription<ConfigUpdate<T>> {
|
||||||
|
let id = std::any::TypeId::of::<T>();
|
||||||
|
iced_futures::subscription::channel((config_id, id), 5, move |mut tx| async move {
|
||||||
|
let version = T::VERSION;
|
||||||
|
let Ok(cosmic_config) = crate::Config::new(config_id, version) else {
|
||||||
|
pending::<()>().await;
|
||||||
|
unreachable!();
|
||||||
|
};
|
||||||
|
dbg!(config_id, version, &cosmic_config);
|
||||||
|
let mut config = match T::get_entry(&cosmic_config) {
|
||||||
|
Ok(config) => config,
|
||||||
|
Err((errors, default)) => {
|
||||||
|
if !errors.is_empty() {
|
||||||
|
eprintln!("Failed to get config: {errors:?}");
|
||||||
|
}
|
||||||
|
default
|
||||||
|
}
|
||||||
|
};
|
||||||
|
if let Err(err) = tx
|
||||||
|
.send(ConfigUpdate {
|
||||||
|
errors: Vec::new(),
|
||||||
|
keys: Vec::new(),
|
||||||
|
config: config.clone(),
|
||||||
|
})
|
||||||
|
.await
|
||||||
|
{
|
||||||
|
eprintln!("Failed to send config: {err}");
|
||||||
|
}
|
||||||
|
|
||||||
|
dbg!("sent init");
|
||||||
|
|
||||||
|
let Ok(watcher) = Watcher::new_config(&settings_daemon, config_id, version).await else {
|
||||||
|
dbg!("failed to create watcher");
|
||||||
|
pending::<()>().await;
|
||||||
|
unreachable!();
|
||||||
|
};
|
||||||
|
|
||||||
|
dbg!("watcher created");
|
||||||
|
|
||||||
|
loop {
|
||||||
|
let Ok(changes) = watcher.receive_changed().await else {
|
||||||
|
pending::<()>().await;
|
||||||
|
unreachable!();
|
||||||
|
};
|
||||||
|
let Ok(pings) = watcher.receive_ping().await else {
|
||||||
|
pending::<()>().await;
|
||||||
|
unreachable!();
|
||||||
|
};
|
||||||
|
let mut streams = futures_util::stream_select!(
|
||||||
|
changes.map(Message::ConfigChanged),
|
||||||
|
pings.map(Message::ConfigPing)
|
||||||
|
);
|
||||||
|
while let Some(v) = streams.next().await {
|
||||||
|
match v {
|
||||||
|
Message::ConfigChanged(change) => {
|
||||||
|
let Ok(args) = change.args() else {
|
||||||
|
continue;
|
||||||
|
};
|
||||||
|
let (errors, keys) = config.update_keys(&cosmic_config, &[args.key]);
|
||||||
|
if !keys.is_empty() {
|
||||||
|
if let Err(err) = tx
|
||||||
|
.send(ConfigUpdate {
|
||||||
|
errors,
|
||||||
|
keys,
|
||||||
|
config: config.clone(),
|
||||||
|
})
|
||||||
|
.await
|
||||||
|
{
|
||||||
|
eprintln!("Failed to send config update: {err}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Message::ConfigPing(_) => {
|
||||||
|
// send pong
|
||||||
|
if let Err(err) = watcher.pong().await {
|
||||||
|
eprintln!("Failed to send pong: {err}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
pub enum Message {
|
||||||
|
ConfigChanged(Changed),
|
||||||
|
ConfigPing(Ping),
|
||||||
|
}
|
||||||
|
|
@ -1,20 +1,22 @@
|
||||||
use iced_futures::futures::SinkExt;
|
|
||||||
#[cfg(feature = "subscription")]
|
|
||||||
use iced_futures::{futures::channel::mpsc, subscription};
|
|
||||||
use notify::{
|
use notify::{
|
||||||
event::{EventKind, ModifyKind},
|
event::{EventKind, ModifyKind},
|
||||||
RecommendedWatcher, Watcher,
|
Watcher,
|
||||||
};
|
};
|
||||||
use serde::{de::DeserializeOwned, Serialize};
|
use serde::{de::DeserializeOwned, Serialize};
|
||||||
use std::{
|
use std::{
|
||||||
borrow::Cow,
|
|
||||||
fmt, fs,
|
fmt, fs,
|
||||||
hash::Hash,
|
|
||||||
io::Write,
|
io::Write,
|
||||||
path::{Path, PathBuf},
|
path::{Path, PathBuf},
|
||||||
sync::Mutex,
|
sync::Mutex,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
#[cfg(feature = "subscription")]
|
||||||
|
mod subscription;
|
||||||
|
pub use subscription::*;
|
||||||
|
|
||||||
|
#[cfg(all(feature = "dbus", feature = "subscription"))]
|
||||||
|
pub mod dbus;
|
||||||
|
|
||||||
#[cfg(feature = "macro")]
|
#[cfg(feature = "macro")]
|
||||||
pub use cosmic_config_derive;
|
pub use cosmic_config_derive;
|
||||||
|
|
||||||
|
|
@ -322,24 +324,12 @@ impl<'a> ConfigSet for ConfigTransaction<'a> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(feature = "subscription")]
|
|
||||||
pub enum ConfigState<T> {
|
|
||||||
Init(Cow<'static, str>, u64, bool),
|
|
||||||
Waiting(T, RecommendedWatcher, mpsc::Receiver<Vec<String>>, Config),
|
|
||||||
Failed,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(feature = "subscription")]
|
|
||||||
pub enum ConfigUpdate<T> {
|
|
||||||
Update(T),
|
|
||||||
UpdateError(T, Vec<crate::Error>),
|
|
||||||
Failed,
|
|
||||||
}
|
|
||||||
|
|
||||||
pub trait CosmicConfigEntry
|
pub trait CosmicConfigEntry
|
||||||
where
|
where
|
||||||
Self: Sized,
|
Self: Sized,
|
||||||
{
|
{
|
||||||
|
const VERSION: u64;
|
||||||
|
|
||||||
fn write_entry(&self, config: &Config) -> Result<(), crate::Error>;
|
fn write_entry(&self, config: &Config) -> Result<(), crate::Error>;
|
||||||
fn get_entry(config: &Config) -> Result<Self, (Vec<crate::Error>, Self)>;
|
fn get_entry(config: &Config) -> Result<Self, (Vec<crate::Error>, Self)>;
|
||||||
/// Returns the keys that were updated
|
/// Returns the keys that were updated
|
||||||
|
|
@ -347,108 +337,5 @@ where
|
||||||
&mut self,
|
&mut self,
|
||||||
config: &Config,
|
config: &Config,
|
||||||
changed_keys: &[T],
|
changed_keys: &[T],
|
||||||
) -> (Vec<crate::Error>, Vec<&str>);
|
) -> (Vec<crate::Error>, Vec<&'static str>);
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(feature = "subscription")]
|
|
||||||
pub fn config_subscription<
|
|
||||||
I: 'static + Copy + Send + Sync + Hash,
|
|
||||||
T: 'static + Send + Sync + PartialEq + Clone + CosmicConfigEntry,
|
|
||||||
>(
|
|
||||||
id: I,
|
|
||||||
config_id: Cow<'static, str>,
|
|
||||||
config_version: u64,
|
|
||||||
) -> iced_futures::Subscription<(I, Result<T, (Vec<crate::Error>, T)>)> {
|
|
||||||
subscription::channel(id, 100, move |mut output| {
|
|
||||||
let config_id = config_id.clone();
|
|
||||||
async move {
|
|
||||||
let config_id = config_id.clone();
|
|
||||||
let mut state = ConfigState::Init(config_id, config_version, false);
|
|
||||||
|
|
||||||
loop {
|
|
||||||
state = start_listening(state, &mut output, id).await;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(feature = "subscription")]
|
|
||||||
pub fn config_state_subscription<
|
|
||||||
I: 'static + Copy + Send + Sync + Hash,
|
|
||||||
T: 'static + Send + Sync + PartialEq + Clone + CosmicConfigEntry,
|
|
||||||
>(
|
|
||||||
id: I,
|
|
||||||
config_id: Cow<'static, str>,
|
|
||||||
config_version: u64,
|
|
||||||
) -> iced_futures::Subscription<(I, Result<T, (Vec<crate::Error>, T)>)> {
|
|
||||||
subscription::channel(id, 100, move |mut output| {
|
|
||||||
let config_id = config_id.clone();
|
|
||||||
async move {
|
|
||||||
let config_id = config_id.clone();
|
|
||||||
let mut state = ConfigState::Init(config_id, config_version, true);
|
|
||||||
|
|
||||||
loop {
|
|
||||||
state = start_listening(state, &mut output, id).await;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
async fn start_listening<
|
|
||||||
I: Copy,
|
|
||||||
T: 'static + Send + Sync + PartialEq + Clone + CosmicConfigEntry,
|
|
||||||
>(
|
|
||||||
state: ConfigState<T>,
|
|
||||||
output: &mut mpsc::Sender<(I, Result<T, (Vec<crate::Error>, T)>)>,
|
|
||||||
id: I,
|
|
||||||
) -> ConfigState<T> {
|
|
||||||
use iced_futures::futures::{future::pending, StreamExt};
|
|
||||||
|
|
||||||
match state {
|
|
||||||
ConfigState::Init(config_id, version, is_state) => {
|
|
||||||
let (tx, rx) = mpsc::channel(100);
|
|
||||||
let config = match if is_state {
|
|
||||||
Config::new_state(&config_id, version)
|
|
||||||
} else {
|
|
||||||
Config::new(&config_id, version)
|
|
||||||
} {
|
|
||||||
Ok(c) => c,
|
|
||||||
Err(_) => return ConfigState::Failed,
|
|
||||||
};
|
|
||||||
let watcher = match config.watch(move |_helper, keys| {
|
|
||||||
let mut tx = tx.clone();
|
|
||||||
let _ = tx.try_send(keys.to_vec());
|
|
||||||
}) {
|
|
||||||
Ok(w) => w,
|
|
||||||
Err(_) => return ConfigState::Failed,
|
|
||||||
};
|
|
||||||
|
|
||||||
match T::get_entry(&config) {
|
|
||||||
Ok(t) => {
|
|
||||||
_ = output.send((id, Ok(t.clone()))).await;
|
|
||||||
ConfigState::Waiting(t, watcher, rx, config)
|
|
||||||
}
|
|
||||||
Err((errors, t)) => {
|
|
||||||
_ = output.send((id, Err((errors, t.clone())))).await;
|
|
||||||
ConfigState::Waiting(t, watcher, rx, config)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
ConfigState::Waiting(mut conf_data, watcher, mut rx, config) => match rx.next().await {
|
|
||||||
Some(keys) => {
|
|
||||||
let (errors, changed) = conf_data.update_keys(&config, &keys);
|
|
||||||
|
|
||||||
if !changed.is_empty() {
|
|
||||||
if errors.is_empty() {
|
|
||||||
_ = output.send((id, Ok(conf_data.clone()))).await;
|
|
||||||
} else {
|
|
||||||
_ = output.send((id, Err((errors, conf_data.clone())))).await;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
ConfigState::Waiting(conf_data, watcher, rx, config)
|
|
||||||
}
|
|
||||||
None => ConfigState::Failed,
|
|
||||||
},
|
|
||||||
ConfigState::Failed => pending().await,
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
119
cosmic-config/src/subscription.rs
Normal file
119
cosmic-config/src/subscription.rs
Normal file
|
|
@ -0,0 +1,119 @@
|
||||||
|
use iced_futures::futures::SinkExt;
|
||||||
|
use iced_futures::{futures::channel::mpsc, subscription};
|
||||||
|
use notify::RecommendedWatcher;
|
||||||
|
use std::{borrow::Cow, hash::Hash};
|
||||||
|
|
||||||
|
use crate::{Config, CosmicConfigEntry};
|
||||||
|
|
||||||
|
pub enum ConfigState<T> {
|
||||||
|
Init(Cow<'static, str>, u64, bool),
|
||||||
|
Waiting(T, RecommendedWatcher, mpsc::Receiver<Vec<String>>, Config),
|
||||||
|
Failed,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub enum ConfigUpdate<T> {
|
||||||
|
Update(T),
|
||||||
|
UpdateError(T, Vec<crate::Error>),
|
||||||
|
Failed,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn config_subscription<
|
||||||
|
I: 'static + Copy + Send + Sync + Hash,
|
||||||
|
T: 'static + Send + Sync + PartialEq + Clone + CosmicConfigEntry,
|
||||||
|
>(
|
||||||
|
id: I,
|
||||||
|
config_id: Cow<'static, str>,
|
||||||
|
config_version: u64,
|
||||||
|
) -> iced_futures::Subscription<(I, Result<T, (Vec<crate::Error>, T)>)> {
|
||||||
|
subscription::channel(id, 100, move |mut output| {
|
||||||
|
let config_id = config_id.clone();
|
||||||
|
async move {
|
||||||
|
let config_id = config_id.clone();
|
||||||
|
let mut state = ConfigState::Init(config_id, config_version, false);
|
||||||
|
|
||||||
|
loop {
|
||||||
|
state = start_listening(state, &mut output, id).await;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn config_state_subscription<
|
||||||
|
I: 'static + Copy + Send + Sync + Hash,
|
||||||
|
T: 'static + Send + Sync + PartialEq + Clone + CosmicConfigEntry,
|
||||||
|
>(
|
||||||
|
id: I,
|
||||||
|
config_id: Cow<'static, str>,
|
||||||
|
config_version: u64,
|
||||||
|
) -> iced_futures::Subscription<(I, Result<T, (Vec<crate::Error>, T)>)> {
|
||||||
|
subscription::channel(id, 100, move |mut output| {
|
||||||
|
let config_id = config_id.clone();
|
||||||
|
async move {
|
||||||
|
let config_id = config_id.clone();
|
||||||
|
let mut state = ConfigState::Init(config_id, config_version, true);
|
||||||
|
|
||||||
|
loop {
|
||||||
|
state = start_listening(state, &mut output, id).await;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn start_listening<
|
||||||
|
I: Copy,
|
||||||
|
T: 'static + Send + Sync + PartialEq + Clone + CosmicConfigEntry,
|
||||||
|
>(
|
||||||
|
state: ConfigState<T>,
|
||||||
|
output: &mut mpsc::Sender<(I, Result<T, (Vec<crate::Error>, T)>)>,
|
||||||
|
id: I,
|
||||||
|
) -> ConfigState<T> {
|
||||||
|
use iced_futures::futures::{future::pending, StreamExt};
|
||||||
|
|
||||||
|
match state {
|
||||||
|
ConfigState::Init(config_id, version, is_state) => {
|
||||||
|
let (tx, rx) = mpsc::channel(100);
|
||||||
|
let config = match if is_state {
|
||||||
|
Config::new_state(&config_id, version)
|
||||||
|
} else {
|
||||||
|
Config::new(&config_id, version)
|
||||||
|
} {
|
||||||
|
Ok(c) => c,
|
||||||
|
Err(_) => return ConfigState::Failed,
|
||||||
|
};
|
||||||
|
let watcher = match config.watch(move |_helper, keys| {
|
||||||
|
let mut tx = tx.clone();
|
||||||
|
let _ = tx.try_send(keys.to_vec());
|
||||||
|
}) {
|
||||||
|
Ok(w) => w,
|
||||||
|
Err(_) => return ConfigState::Failed,
|
||||||
|
};
|
||||||
|
|
||||||
|
match T::get_entry(&config) {
|
||||||
|
Ok(t) => {
|
||||||
|
_ = output.send((id, Ok(t.clone()))).await;
|
||||||
|
ConfigState::Waiting(t, watcher, rx, config)
|
||||||
|
}
|
||||||
|
Err((errors, t)) => {
|
||||||
|
_ = output.send((id, Err((errors, t.clone())))).await;
|
||||||
|
ConfigState::Waiting(t, watcher, rx, config)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ConfigState::Waiting(mut conf_data, watcher, mut rx, config) => match rx.next().await {
|
||||||
|
Some(keys) => {
|
||||||
|
let (errors, changed) = conf_data.update_keys(&config, &keys);
|
||||||
|
|
||||||
|
if !changed.is_empty() {
|
||||||
|
if errors.is_empty() {
|
||||||
|
_ = output.send((id, Ok(conf_data.clone()))).await;
|
||||||
|
} else {
|
||||||
|
_ = output.send((id, Err((errors, conf_data.clone())))).await;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ConfigState::Waiting(conf_data, watcher, rx, config)
|
||||||
|
}
|
||||||
|
None => ConfigState::Failed,
|
||||||
|
},
|
||||||
|
ConfigState::Failed => pending().await,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -7,6 +7,7 @@ pub const THEME_MODE_ID: &str = "com.system76.CosmicTheme.Mode";
|
||||||
#[derive(
|
#[derive(
|
||||||
Debug, Clone, Copy, PartialEq, Eq, cosmic_config::cosmic_config_derive::CosmicConfigEntry,
|
Debug, Clone, Copy, PartialEq, Eq, cosmic_config::cosmic_config_derive::CosmicConfigEntry,
|
||||||
)]
|
)]
|
||||||
|
#[version = 1]
|
||||||
pub struct ThemeMode {
|
pub struct ThemeMode {
|
||||||
/// The theme dark mode setting.
|
/// The theme dark mode setting.
|
||||||
pub is_dark: bool,
|
pub is_dark: bool,
|
||||||
|
|
|
||||||
|
|
@ -40,6 +40,7 @@ pub enum Layer {
|
||||||
PartialEq,
|
PartialEq,
|
||||||
cosmic_config::cosmic_config_derive::CosmicConfigEntry,
|
cosmic_config::cosmic_config_derive::CosmicConfigEntry,
|
||||||
)]
|
)]
|
||||||
|
#[version = 1]
|
||||||
pub struct Theme {
|
pub struct Theme {
|
||||||
/// name of the theme
|
/// name of the theme
|
||||||
pub name: String,
|
pub name: String,
|
||||||
|
|
@ -388,6 +389,7 @@ impl From<CosmicPalette> for Theme {
|
||||||
cosmic_config::cosmic_config_derive::CosmicConfigEntry,
|
cosmic_config::cosmic_config_derive::CosmicConfigEntry,
|
||||||
PartialEq,
|
PartialEq,
|
||||||
)]
|
)]
|
||||||
|
#[version = 1]
|
||||||
pub struct ThemeBuilder {
|
pub struct ThemeBuilder {
|
||||||
/// override the palette for the builder
|
/// override the palette for the builder
|
||||||
pub palette: CosmicPalette,
|
pub palette: CosmicPalette,
|
||||||
|
|
|
||||||
|
|
@ -8,7 +8,7 @@ publish = false
|
||||||
[dependencies]
|
[dependencies]
|
||||||
apply = "0.3.0"
|
apply = "0.3.0"
|
||||||
fraction = "0.14.0"
|
fraction = "0.14.0"
|
||||||
libcosmic = { path = "../..", features = ["debug", "winit", "tokio", "single-instance"] }
|
libcosmic = { path = "../..", features = ["debug", "winit", "tokio", "single-instance", "dbus-config"] }
|
||||||
once_cell = "1.18"
|
once_cell = "1.18"
|
||||||
slotmap = "1.0.6"
|
slotmap = "1.0.6"
|
||||||
env_logger = "0.10"
|
env_logger = "0.10"
|
||||||
|
|
|
||||||
|
|
@ -69,6 +69,9 @@ pub struct Core {
|
||||||
|
|
||||||
#[cfg(feature = "single-instance")]
|
#[cfg(feature = "single-instance")]
|
||||||
pub(crate) single_instance: bool,
|
pub(crate) single_instance: bool,
|
||||||
|
|
||||||
|
#[cfg(feature = "dbus-config")]
|
||||||
|
pub(crate) settings_daemon: Option<cosmic_settings_daemon::CosmicSettingsDaemonProxy<'static>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Default for Core {
|
impl Default for Core {
|
||||||
|
|
@ -114,6 +117,8 @@ impl Default for Core {
|
||||||
applet: crate::applet::Context::default(),
|
applet: crate::applet::Context::default(),
|
||||||
#[cfg(feature = "single-instance")]
|
#[cfg(feature = "single-instance")]
|
||||||
single_instance: false,
|
single_instance: false,
|
||||||
|
#[cfg(feature = "dbus-config")]
|
||||||
|
settings_daemon: None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -208,4 +213,16 @@ impl Core {
|
||||||
pub fn system_theme_mode(&self) -> ThemeMode {
|
pub fn system_theme_mode(&self) -> ThemeMode {
|
||||||
self.system_theme_mode
|
self.system_theme_mode
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "dbus-config")]
|
||||||
|
pub fn watch_config<T: CosmicConfigEntry + Send + Sync + Default + 'static + Clone>(
|
||||||
|
&self,
|
||||||
|
config_id: &'static str,
|
||||||
|
) -> iced::Subscription<cosmic_config::dbus::ConfigUpdate<T>> {
|
||||||
|
if let Some(settings_daemon) = self.settings_daemon.clone() {
|
||||||
|
cosmic_config::dbus::watcher_subscription(settings_daemon, config_id)
|
||||||
|
} else {
|
||||||
|
iced::Subscription::none()
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,8 @@
|
||||||
// Copyright 2023 System76 <info@system76.com>
|
// Copyright 2023 System76 <info@system76.com>
|
||||||
// SPDX-License-Identifier: MPL-2.0
|
// SPDX-License-Identifier: MPL-2.0
|
||||||
|
|
||||||
|
use std::sync::Arc;
|
||||||
|
|
||||||
use super::{command, Application, ApplicationExt, Core, Subscription};
|
use super::{command, Application, ApplicationExt, Core, Subscription};
|
||||||
use crate::theme::{self, Theme, ThemeType, THEME};
|
use crate::theme::{self, Theme, ThemeType, THEME};
|
||||||
use crate::widget::nav_bar;
|
use crate::widget::nav_bar;
|
||||||
|
|
@ -64,6 +66,9 @@ pub enum Message {
|
||||||
WmCapabilities(window::Id, WindowManagerCapabilities),
|
WmCapabilities(window::Id, WindowManagerCapabilities),
|
||||||
/// Activate the application
|
/// Activate the application
|
||||||
Activate(String),
|
Activate(String),
|
||||||
|
#[cfg(feature = "dbus-config")]
|
||||||
|
/// dbus settings daemon setup
|
||||||
|
SettingsDaemon(zbus::Result<cosmic_settings_daemon::CosmicSettingsDaemonProxy<'static>>),
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Default)]
|
#[derive(Default)]
|
||||||
|
|
@ -83,6 +88,13 @@ where
|
||||||
fn new((core, flags): Self::Flags) -> (Self, iced::Command<Self::Message>) {
|
fn new((core, flags): Self::Flags) -> (Self, iced::Command<Self::Message>) {
|
||||||
let (model, command) = T::init(core, flags);
|
let (model, command) = T::init(core, flags);
|
||||||
|
|
||||||
|
#[cfg(feature = "dbus-config")]
|
||||||
|
let command = iced::Command::batch(vec![
|
||||||
|
command,
|
||||||
|
iced::Command::perform(cosmic_config::dbus::settings_daemon_proxy(), |p| {
|
||||||
|
super::Message::Cosmic(super::cosmic::Message::SettingsDaemon(p))
|
||||||
|
}),
|
||||||
|
]);
|
||||||
(Self::new(model), command)
|
(Self::new(model), command)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -164,12 +176,26 @@ where
|
||||||
keyboard_nav::subscription()
|
keyboard_nav::subscription()
|
||||||
.map(Message::KeyboardNav)
|
.map(Message::KeyboardNav)
|
||||||
.map(super::Message::Cosmic),
|
.map(super::Message::Cosmic),
|
||||||
theme::subscription(
|
#[cfg(feature = "dbus-config")]
|
||||||
self.app.core().theme_sub_counter,
|
self.app
|
||||||
self.app.core().system_theme_mode.is_dark,
|
.core()
|
||||||
)
|
.watch_config::<cosmic_theme::Theme>(if self.app.core().system_theme_mode.is_dark {
|
||||||
.map(Message::SystemThemeChange)
|
cosmic_theme::DARK_THEME_ID
|
||||||
.map(super::Message::Cosmic),
|
} else {
|
||||||
|
cosmic_theme::LIGHT_THEME_ID
|
||||||
|
})
|
||||||
|
.map(|update| {
|
||||||
|
for e in update.errors {
|
||||||
|
tracing::error!("{e}");
|
||||||
|
}
|
||||||
|
Message::SystemThemeChange(crate::theme::Theme::system(Arc::new(update.config)))
|
||||||
|
})
|
||||||
|
.map(super::Message::Cosmic),
|
||||||
|
#[cfg(not(feature = "dbus-config"))]
|
||||||
|
theme::subscription(self.app.core().system_theme_mode.is_dark)
|
||||||
|
.map(Message::SystemThemeChange)
|
||||||
|
.map(super::Message::Cosmic),
|
||||||
|
#[cfg(not(feature = "dbus-config"))]
|
||||||
cosmic_config::config_subscription::<_, cosmic_theme::ThemeMode>(
|
cosmic_config::config_subscription::<_, cosmic_theme::ThemeMode>(
|
||||||
0,
|
0,
|
||||||
cosmic_theme::THEME_MODE_ID.into(),
|
cosmic_theme::THEME_MODE_ID.into(),
|
||||||
|
|
@ -185,6 +211,17 @@ where
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.map(super::Message::Cosmic),
|
.map(super::Message::Cosmic),
|
||||||
|
#[cfg(feature = "dbus-config")]
|
||||||
|
self.app
|
||||||
|
.core()
|
||||||
|
.watch_config::<ThemeMode>(cosmic_theme::THEME_MODE_ID)
|
||||||
|
.map(|update| {
|
||||||
|
for e in update.errors {
|
||||||
|
tracing::error!("{e}");
|
||||||
|
}
|
||||||
|
Message::SystemThemeModeChange(update.config)
|
||||||
|
})
|
||||||
|
.map(super::Message::Cosmic),
|
||||||
window_events.map(super::Message::Cosmic),
|
window_events.map(super::Message::Cosmic),
|
||||||
#[cfg(feature = "single-instance")]
|
#[cfg(feature = "single-instance")]
|
||||||
self.app
|
self.app
|
||||||
|
|
@ -384,6 +421,15 @@ impl<T: Application> Cosmic<T> {
|
||||||
_token,
|
_token,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
#[cfg(feature = "dbus-config")]
|
||||||
|
Message::SettingsDaemon(p) => match p {
|
||||||
|
Ok(p) => {
|
||||||
|
self.app.core_mut().settings_daemon = Some(p);
|
||||||
|
}
|
||||||
|
Err(e) => {
|
||||||
|
tracing::error!("Failed to connect to settings daemon: {e}");
|
||||||
|
}
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
iced::Command::none()
|
iced::Command::none()
|
||||||
|
|
|
||||||
|
|
@ -551,7 +551,7 @@ impl<App: Application> ApplicationExt for App {
|
||||||
|
|
||||||
#[cfg(any(feature = "multi-window", feature = "wayland"))]
|
#[cfg(any(feature = "multi-window", feature = "wayland"))]
|
||||||
fn title(&self, id: window::Id) -> &str {
|
fn title(&self, id: window::Id) -> &str {
|
||||||
self.core().title.get(&id).map(|s| s.as_str()).unwrap_or("")
|
self.core().title.get(&id).map_or("", |s| s.as_str())
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(not(any(feature = "multi-window", feature = "wayland")))]
|
#[cfg(not(any(feature = "multi-window", feature = "wayland")))]
|
||||||
|
|
|
||||||
|
|
@ -153,11 +153,11 @@ impl Context {
|
||||||
Container::<Message, Renderer>::new(Container::<Message, Renderer>::new(content).style(
|
Container::<Message, Renderer>::new(Container::<Message, Renderer>::new(content).style(
|
||||||
theme::Container::custom(|theme| {
|
theme::Container::custom(|theme| {
|
||||||
let cosmic = theme.cosmic();
|
let cosmic = theme.cosmic();
|
||||||
|
let corners = cosmic.corner_radii.clone();
|
||||||
Appearance {
|
Appearance {
|
||||||
text_color: Some(cosmic.background.on.into()),
|
text_color: Some(cosmic.background.on.into()),
|
||||||
background: Some(Color::from(cosmic.background.base).into()),
|
background: Some(Color::from(cosmic.background.base).into()),
|
||||||
border_radius: cosmic.corner_radii.radius_m.into(),
|
border_radius: corners.radius_m.into(),
|
||||||
border_width: 1.0,
|
border_width: 1.0,
|
||||||
border_color: cosmic.background.divider.into(),
|
border_color: cosmic.background.divider.into(),
|
||||||
icon_color: Some(cosmic.background.on.into()),
|
icon_color: Some(cosmic.background.on.into()),
|
||||||
|
|
|
||||||
|
|
@ -16,6 +16,9 @@ use iced_futures::Subscription;
|
||||||
use std::cell::RefCell;
|
use std::cell::RefCell;
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
|
|
||||||
|
#[cfg(feature = "dbus-config")]
|
||||||
|
use cosmic_config::dbus;
|
||||||
|
|
||||||
pub type CosmicColor = ::palette::rgb::Srgba;
|
pub type CosmicColor = ::palette::rgb::Srgba;
|
||||||
pub type CosmicComponent = cosmic_theme::Component;
|
pub type CosmicComponent = cosmic_theme::Component;
|
||||||
pub type CosmicTheme = cosmic_theme::Theme;
|
pub type CosmicTheme = cosmic_theme::Theme;
|
||||||
|
|
@ -68,9 +71,12 @@ pub fn is_high_contrast() -> bool {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Watches for changes to the system's theme preference.
|
/// Watches for changes to the system's theme preference.
|
||||||
pub fn subscription(id: u64, is_dark: bool) -> Subscription<crate::theme::Theme> {
|
pub fn subscription(is_dark: bool) -> Subscription<crate::theme::Theme> {
|
||||||
config_subscription::<_, crate::cosmic_theme::Theme>(
|
config_subscription::<_, crate::cosmic_theme::Theme>(
|
||||||
(id, is_dark),
|
(
|
||||||
|
std::any::TypeId::of::<crate::cosmic_theme::Theme>(),
|
||||||
|
is_dark,
|
||||||
|
),
|
||||||
if is_dark {
|
if is_dark {
|
||||||
cosmic_theme::DARK_THEME_ID
|
cosmic_theme::DARK_THEME_ID
|
||||||
} else {
|
} else {
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue