Skip to main content

workflow_nw/
application.rs

1//!
2//! Node Webkit application helper provided by the [`Application`] struct.
3//!
4//!
5//!
6
7use crate::media::MediaStreamTrackKind;
8use crate::result::Result;
9use nw_sys::{prelude::*, utils};
10use std::rc::Rc;
11use wasm_bindgen::JsCast;
12use wasm_bindgen::prelude::*;
13use web_sys::{MediaStream, MediaStreamTrack, MouseEvent};
14use workflow_core::channel::*;
15use workflow_wasm::prelude::*;
16
17static mut APP: Option<Arc<Application>> = None;
18
19/// get saved [Application](Application) instance.
20pub fn app() -> Option<Arc<Application>> {
21    let app_ptr = &raw const APP;
22    unsafe { (*app_ptr).clone() }
23}
24
25/// Application helper. This struct contains a map of callbacks that
26/// can be used to retain different application callbacks as well as
27/// media stream helper functions for controlling media playback.
28///
29/// For usage example please refer to [Examples](crate)
30#[derive(Clone)]
31pub struct Application {
32    /// a storage for [MediaStream](web_sys::MediaStream)
33    pub media_stream: Rc<Mutex<Option<MediaStream>>>,
34
35    /// holds references to [Callback](workflow_wasm::callback::Callback)
36    pub callbacks: CallbackMap,
37}
38
39unsafe impl Send for Application {}
40unsafe impl Sync for Application {}
41
42impl Application {
43    /// Create [Application](Application) object.
44    /// if instance is allready created then it will return saved application.
45    pub fn new() -> Result<Arc<Self>> {
46        if let Some(app) = app() {
47            return Ok(app);
48        }
49        let app = Arc::new(Self {
50            callbacks: CallbackMap::new(),
51            media_stream: Rc::new(Mutex::new(None)),
52        });
53
54        unsafe {
55            APP = Some(app.clone());
56        };
57
58        Ok(app)
59    }
60
61    /// Store or Clear saved [MediaStream](web_sys::MediaStream)
62    pub fn set_media_stream(&self, media_stream: Option<MediaStream>) -> Result<()> {
63        *self.media_stream.lock()? = media_stream;
64        Ok(())
65    }
66
67    /// Get saved [MediaStream](web_sys::MediaStream)
68    pub fn get_media_stream(&self) -> Result<Option<MediaStream>> {
69        let media_stream = self.media_stream.lock()?.clone();
70        Ok(media_stream)
71    }
72
73    /// Stop [MediaStream](web_sys::MediaStream) tracks ([MediaStreamTrack](web_sys::MediaStreamTrack))
74    /// of given kind or [All](MediaStreamTrackKind::All)
75    /// you can provide any [MediaStream](web_sys::MediaStream) or it will get internal saved stream.
76    pub fn stop_media_stream(
77        &self,
78        track_kind: Option<MediaStreamTrackKind>,
79        mut stream: Option<MediaStream>,
80    ) -> Result<()> {
81        if stream.is_none() {
82            stream = self.get_media_stream()?;
83        }
84        if let Some(media_stream) = stream {
85            let tracks = media_stream.get_tracks();
86            let kind = track_kind.unwrap_or(MediaStreamTrackKind::All);
87            let mut all = false;
88            let mut video = false;
89            let mut audio = false;
90            match kind {
91                MediaStreamTrackKind::All => {
92                    all = true;
93                }
94                MediaStreamTrackKind::Video => {
95                    video = true;
96                }
97                MediaStreamTrackKind::Audio => {
98                    audio = true;
99                }
100            }
101
102            for index in 0..tracks.length() {
103                if let Ok(track) = tracks.get(index).dyn_into::<MediaStreamTrack>() {
104                    let k = track.kind();
105                    if all || (k.eq("video") && video) || (k.eq("audio") && audio) {
106                        track.stop();
107                    }
108                }
109            }
110        }
111        Ok(())
112    }
113
114    /// Create window with given [Options](nw_sys::window::Options)
115    /// and callback closure
116    pub fn create_window_with_callback<F>(
117        &self,
118        url: &str,
119        option: &nw_sys::window::Options,
120        callback: F,
121    ) -> Result<()>
122    where
123        F: FnMut(nw_sys::Window) -> std::result::Result<(), JsValue> + 'static,
124    {
125        let callback = Callback::new(callback);
126
127        nw_sys::window::open_with_options_and_callback(url, option, callback.as_ref());
128
129        self.callbacks.retain(callback)?;
130        Ok(())
131    }
132
133    // pub async fn create_window_async(
134    //     &self,
135    //     url: &str,
136    //     option: &nw_sys::window::Options,
137    // ) -> Result<nw_sys::window::Window> {
138    //     let (sender, receiver) = oneshot();
139
140    //     let callback = Callback::new(move |window: nw_sys::Window| {
141    //         sender.try_send(window).unwrap();
142    //     });
143
144    //     nw_sys::window::open_with_options_and_callback(url, option, callback.as_ref());
145    //     Ok(receiver.recv().await?)
146    // }
147
148    /// Opens a new window for the given `url` with the supplied
149    /// [Options](nw_sys::window::Options) and resolves once the window
150    /// has been created, returning its [`Window`](nw_sys::window::Window) handle.
151    pub async fn create_window_async(
152        url: &str,
153        option: &nw_sys::window::Options,
154    ) -> Result<nw_sys::window::Window> {
155        let (sender, receiver) = oneshot();
156
157        let callback = Callback::new(move |window: nw_sys::Window| {
158            sender.try_send(window).unwrap();
159        });
160
161        nw_sys::window::open_with_options_and_callback(url, option, callback.as_ref());
162        Ok(receiver.recv().await?)
163    }
164
165    /// Create window with given [Options](nw_sys::window::Options)
166    /// The resulting window handle is not retained. Please use [`Application::create_window_with_callback`]
167    /// or [`Application::create_window`] to retain the window handle.
168    pub fn create_window(url: &str, option: &nw_sys::window::Options) -> Result<()> {
169        nw_sys::window::open_with_options(url, option);
170
171        Ok(())
172    }
173
174    /// Create context menu
175    pub fn create_context_menu(&self, menus: Vec<nw_sys::MenuItem>) -> Result<()> {
176        let popup_menu = nw_sys::Menu::new();
177        for menu_item in menus {
178            popup_menu.append(&menu_item);
179        }
180
181        self.on_context_menu(move |ev: MouseEvent| -> std::result::Result<(), JsValue> {
182            ev.prevent_default();
183            popup_menu.popup(ev.x(), ev.y());
184            Ok(())
185        })?;
186
187        Ok(())
188    }
189
190    /// A utility for adding callback for `contextmenu` event
191    pub fn on_context_menu<F>(&self, callback: F) -> Result<()>
192    where
193        F: Sized + FnMut(MouseEvent) -> std::result::Result<(), JsValue> + 'static,
194    {
195        let win = nw_sys::window::get();
196        let dom_win = win.window();
197        let body = utils::body(Some(dom_win));
198
199        let callback = callback!(callback);
200        body.add_event_listener_with_callback("contextmenu", callback.as_ref())?;
201        self.callbacks.retain(callback)?;
202
203        Ok(())
204    }
205
206    /// Choose desktop media
207    ///
208    /// Screen sharing by selection; Currently only working in Windows and OSX
209    /// and some linux distribution.
210    ///
211    /// ⧉ [NWJS Documentation](https://docs.nwjs.io/en/latest/References/Screen/#screenchoosedesktopmedia-sources-callback)
212    ///
213    pub fn choose_desktop_media<F>(
214        &self,
215        sources: nw_sys::screen::MediaSources,
216        mut callback: F,
217    ) -> Result<()>
218    where
219        F: 'static + FnMut(Option<String>) -> Result<()>,
220    {
221        let callback_ = Callback::new(move |value: JsValue| -> std::result::Result<(), JsValue> {
222            let mut stream_id = None;
223            if value.is_string()
224                && let Some(id) = value.as_string()
225                && !id.is_empty()
226            {
227                stream_id = Some(id);
228            }
229
230            callback(stream_id)?;
231
232            Ok(())
233        });
234
235        nw_sys::screen::choose_desktop_media(sources, callback_.as_ref())?;
236
237        self.callbacks.retain(callback_)?;
238
239        Ok(())
240    }
241}