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