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