tauri_plugin_widgets/desktop/
sources.rs1use std::time::Duration;
5
6use tauri::{Emitter, Manager, Runtime};
7
8use super::Widget;
9
10impl<R: Runtime> Widget<R> {
11 pub fn set_widget_data(
15 &self,
16 widget_id: &str,
17 data: &serde_json::Value,
18 instance: Option<&str>,
19 group: Option<&str>,
20 ) -> crate::Result<bool> {
21 let group = crate::group::resolve_group(group)?;
22 crate::group::validate_widget_id(widget_id)?;
23 let instance = crate::group::validate_instance_id(instance)?;
24 let key = crate::store::data_key(widget_id, instance);
25 let raw = if data.is_null() {
26 String::new()
27 } else {
28 serde_json::to_string(data)?
29 };
30 if raw.len() > crate::resolve::MAX_SOURCE_BYTES {
31 return Err(crate::Error::new(format!(
32 "widget data is {} bytes; the limit is {}",
33 raw.len(),
34 crate::resolve::MAX_SOURCE_BYTES
35 )));
36 }
37 if self.get_items(&key, &group)?.as_deref() == Some(raw.as_str()) {
38 return Ok(false);
39 }
40 self.set_items(&key, &raw, &group)?;
41 self.data_changed(&group, widget_id, instance);
42 let _ = self.app.emit(
43 "widget-data-push",
44 serde_json::json!({
45 "group": group,
46 "widgetId": widget_id,
47 "instance": instance,
48 "data": data,
49 }),
50 );
51 self.reload_all_timelines()?;
52 Ok(true)
53 }
54
55 pub fn register_poll_source<F>(&self, widget_id: &str, interval: Duration, mut poll: F)
67 where
68 F: FnMut() -> serde_json::Value + Send + 'static,
69 {
70 let interval = interval.max(Duration::from_millis(100));
71 self.spawn_source(widget_id, move |alive, push| {
72 while alive() {
73 push(poll());
74 std::thread::sleep(interval);
75 }
76 });
77 }
78
79 pub fn register_listen_source<I>(&self, widget_id: &str, values: I)
82 where
83 I: IntoIterator<Item = serde_json::Value>,
84 I::IntoIter: Send + 'static,
85 {
86 let iter = values.into_iter();
87 self.spawn_source(widget_id, move |alive, push| {
88 for v in iter {
89 if !alive() {
90 break;
91 }
92 push(v);
93 }
94 });
95 }
96
97 pub fn unregister_source(&self, widget_id: &str) -> bool {
99 self.sources.lock().unwrap().remove(widget_id).is_some()
100 }
101
102 fn spawn_source<B>(&self, widget_id: &str, body: B)
103 where
104 B: FnOnce(&dyn Fn() -> bool, &dyn Fn(serde_json::Value)) + Send + 'static,
105 {
106 static NEXT: std::sync::atomic::AtomicU64 = std::sync::atomic::AtomicU64::new(1);
108 let gen = NEXT.fetch_add(1, std::sync::atomic::Ordering::Relaxed);
109 self.sources
110 .lock()
111 .unwrap()
112 .insert(widget_id.to_string(), gen);
113 let app = self.app.clone();
114 let id = widget_id.to_string();
115 std::thread::spawn(move || {
116 let alive = || {
117 app.try_state::<Widget<R>>()
118 .is_some_and(|w| w.sources.lock().unwrap().get(&id) == Some(&gen))
119 };
120 let push = |v: serde_json::Value| {
121 if let Some(w) = app.try_state::<Widget<R>>() {
122 if let Err(e) = w.set_widget_data(&id, &v, None, None) {
123 log::warn!("widgets: data source {id}: {e}");
124 }
125 }
126 };
127 body(&alive, &push);
128 if let Some(w) = app.try_state::<Widget<R>>() {
129 let mut s = w.sources.lock().unwrap();
130 if s.get(&id) == Some(&gen) {
131 s.remove(&id);
132 }
133 }
134 });
135 }
136}