Skip to main content

cosmic/
dbus_activation.rs

1// Copyright 2024 System76 <info@system76.com>
2// SPDX-License-Identifier: MPL-2.0
3
4use 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                    // XXX Setup done this way seems to be more reliable.
25                    //
26                    // the docs for serve_at seem to imply it will replace the
27                    // existing interface at the requested path, but it doesn't
28                    // seem to work that way all the time. The docs for
29                    // object_server().at() imply it won't replace the existing
30                    // interface.
31                    //
32                    // request_name is used either way, with the builder or
33                    // with the connection, but it must be done after the
34                    // object server is setup.
35                    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    /// action can be deserialized as Flags
101    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    /// Activate the application.
128    fn activate(&mut self, platform_data: HashMap<&str, Variant<'_>>) -> zbus::Result<()>;
129
130    /// Open the given URIs.
131    fn open(
132        &mut self,
133        uris: Vec<&str>,
134        platform_data: HashMap<&str, Variant<'_>>,
135    ) -> zbus::Result<()>;
136
137    /// Activate the given action.
138    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}