cosmic/
dbus_activation.rs1use crate::ApplicationExt;
5use iced::Subscription;
6use iced_futures::futures::SinkExt;
7use iced_futures::futures::channel::mpsc::{Receiver, Sender};
8use std::any::TypeId;
9use std::collections::HashMap;
10use url::Url;
11use zbus::zvariant::Value;
12use zbus::{interface, proxy};
13
14#[cold]
15pub fn subscription<App: ApplicationExt>() -> Subscription<crate::Action<App::Message>> {
16 use iced_futures::futures::StreamExt;
17 iced_futures::Subscription::run_with(TypeId::of::<DbusActivation>(), |_| {
18 iced::stream::channel(
19 10,
20 move |mut output: Sender<crate::Action<App::Message>>| async move {
21 let mut single_instance: DbusActivation = DbusActivation::new();
22 let mut rx = single_instance.rx();
23 if let Ok(builder) = zbus::connection::Builder::session() {
24 let path: String = format!("/{}", App::APP_ID.replace('.', "/"));
25 if let Ok(conn) = builder.build().await {
26 if conn.object_server().at(path, single_instance).await != Ok(true) {
38 tracing::error!("Failed to serve dbus");
39 std::process::exit(1);
40 }
41 if conn.request_name(App::APP_ID).await.is_err() {
42 tracing::error!("Failed to serve dbus");
43 std::process::exit(1);
44 }
45
46 output
47 .send(crate::Action::Cosmic(crate::app::Action::DbusConnection(
48 conn.clone(),
49 )))
50 .await;
51
52 #[cfg(feature = "smol")]
53 let handle = {
54 std::thread::spawn(move || {
55 let conn_clone = _conn.clone();
56
57 zbus::block_on(async move {
58 loop {
59 conn_clone.executor().tick().await;
60 }
61 })
62 })
63 };
64 while let Some(mut msg) = rx.next().await {
65 if let Some(token) = msg.activation_token.take() {
66 if let Err(err) = output
67 .send(crate::Action::Cosmic(crate::app::Action::Activate(
68 token,
69 )))
70 .await
71 {
72 tracing::error!(?err, "Failed to send message");
73 }
74 }
75 if let Err(err) = output.send(crate::Action::DbusActivation(msg)).await
76 {
77 tracing::error!(?err, "Failed to send message");
78 }
79 }
80 }
81 } else {
82 tracing::warn!("Failed to connect to dbus for single instance");
83 }
84
85 loop {
86 iced::futures::pending!();
87 }
88 },
89 )
90 })
91}
92
93#[derive(Debug, Clone)]
94pub struct Message<Action = String, Args = Vec<String>> {
95 pub activation_token: Option<String>,
96 pub desktop_startup_id: Option<String>,
97 pub msg: Details<Action, Args>,
98}
99
100#[derive(Debug, Clone)]
101pub enum Details<Action = String, Args = Vec<String>> {
102 Activate,
103 Open {
104 url: Vec<Url>,
105 },
106 ActivateAction {
108 action: Action,
109 args: Args,
110 },
111}
112
113#[derive(Debug, Default)]
114pub struct DbusActivation(Option<Sender<Message>>);
115
116impl DbusActivation {
117 #[must_use]
118 #[inline]
119 pub fn new() -> Self {
120 Self(None)
121 }
122
123 #[inline]
124 pub fn rx(&mut self) -> Receiver<Message> {
125 let (tx, rx) = iced_futures::futures::channel::mpsc::channel(10);
126 self.0 = Some(tx);
127 rx
128 }
129}
130
131#[proxy(interface = "org.freedesktop.DbusActivation", assume_defaults = true)]
132pub trait DbusActivationInterface {
133 fn activate(&mut self, platform_data: HashMap<&str, Value<'_>>) -> zbus::Result<()>;
135
136 fn open(
138 &mut self,
139 uris: Vec<&str>,
140 platform_data: HashMap<&str, Value<'_>>,
141 ) -> zbus::Result<()>;
142
143 fn activate_action(
145 &mut self,
146 action_name: &str,
147 parameter: Vec<&str>,
148 platform_data: HashMap<&str, Value<'_>>,
149 ) -> zbus::Result<()>;
150}
151
152#[interface(name = "org.freedesktop.DbusActivation")]
153impl DbusActivation {
154 #[cold]
155 async fn activate(&mut self, platform_data: HashMap<&str, Value<'_>>) {
156 if let Some(tx) = &mut self.0 {
157 let _ = tx
158 .send(Message {
159 activation_token: platform_data.get("activation-token").and_then(|t| match t {
160 Value::Str(t) => Some(t.to_string()),
161 _ => None,
162 }),
163 desktop_startup_id: platform_data.get("desktop-startup-id").and_then(
164 |t| match t {
165 Value::Str(t) => Some(t.to_string()),
166 _ => None,
167 },
168 ),
169 msg: Details::Activate,
170 })
171 .await;
172 }
173 }
174
175 #[cold]
176 async fn open(&mut self, uris: Vec<&str>, platform_data: HashMap<&str, Value<'_>>) {
177 if let Some(tx) = &mut self.0 {
178 let _ = tx
179 .send(Message {
180 activation_token: platform_data.get("activation-token").and_then(|t| match t {
181 Value::Str(t) => Some(t.to_string()),
182 _ => None,
183 }),
184 desktop_startup_id: platform_data.get("desktop-startup-id").and_then(
185 |t| match t {
186 Value::Str(t) => Some(t.to_string()),
187 _ => None,
188 },
189 ),
190 msg: Details::Open {
191 url: uris.iter().filter_map(|u| Url::parse(u).ok()).collect(),
192 },
193 })
194 .await;
195 }
196 }
197
198 #[cold]
199 async fn activate_action(
200 &mut self,
201 action_name: &str,
202 parameter: Vec<&str>,
203 platform_data: HashMap<&str, Value<'_>>,
204 ) {
205 if let Some(tx) = &mut self.0 {
206 let _ = tx
207 .send(Message {
208 activation_token: platform_data.get("activation-token").and_then(|t| match t {
209 Value::Str(t) => Some(t.to_string()),
210 _ => None,
211 }),
212 desktop_startup_id: platform_data.get("desktop-startup-id").and_then(
213 |t| match t {
214 Value::Str(t) => Some(t.to_string()),
215 _ => None,
216 },
217 ),
218 msg: Details::ActivateAction {
219 action: action_name.to_string(),
220 args: parameter
221 .iter()
222 .map(std::string::ToString::to_string)
223 .collect(),
224 },
225 })
226 .await;
227 }
228 }
229}