nw_sys/
screen.rs

1//!
2//! Access to system display information, including system resolution, display layout and
3//! display layout change notification events.
4//!
5//! # Synopsis
6//! ```
7//! // init must be called once during startup, before any function to nw.Screen can be called
8//! nw_sys::screen::init_once();
9//!
10//! let display_bounds_changed_callback = Callback::new(move |screen:JsValue|{
11//!     let screen: nw_sys::screen::ScreenInfo = screen.try_into()?;
12//!     log_info!("displayBoundsChanged: {:#?}", screen);
13//!     Ok(())
14//! });
15//!
16//! let display_added_callback = Callback::new(move |screen:JsValue|{
17//!     let screen: nw_sys::screen::ScreenInfo = screen.try_into()?;
18//!     log_info!("displayAdded: {:#?}", screen);
19//!     Ok(())
20//! });
21//!
22//! let display_removed_callback = Callback::new(move |screen:JsValue|{
23//!     let screen: nw_sys::screen::ScreenInfo = screen.try_into()?;
24//!     log_info!("displayRemoved: {:#?}", screen);
25//!     Ok(())
26//! });
27//!
28//! // listen to screen events
29//! nw_sys::screen::on("displayBoundsChanged", display_bounds_changed_callback.as_ref());
30//! nw_sys::screen::on("displayAdded", display_added_callback.as_ref());
31//! nw_sys::screen::on("displayRemoved", display_removed_callback.as_ref());
32//!
33//! // save callbacks somewhere
34//! app.push_callback(display_bounds_changed_callback)?;
35//! app.push_callback(display_added_callback)?;
36//! app.push_callback(display_removed_callback)?;
37//!
38//! ```
39//!
40//! Screen
41//! # Synopsis
42//! ```
43//! use workflow_wasm::prelude::*;
44//!
45//! // init must be called once during startup, before any function to nw.Screen can be called
46//! nw_sys::screen::init_once();
47//!
48//! let display_bounds_changed_callback = callback!(move |screen:JsValue|{
49//!     let screen: nw_sys::screen::ScreenInfo = screen.try_into()?;
50//!     log_info!("displayBoundsChanged: {:#?}", screen);
51//!     Ok(())
52//! });
53//!
54//! let display_added_callback = callback!(move |screen:JsValue|{
55//!     let screen: nw_sys::screen::ScreenInfo = screen.try_into()?;
56//!     log_info!("displayAdded: {:#?}", screen);
57//!     Ok(())
58//! });
59//!
60//! let display_removed_callback = callback!(move |screen:JsValue|{
61//!     let screen: nw_sys::screen::ScreenInfo = screen.try_into()?;
62//!     log_info!("displayRemoved: {:#?}", screen);
63//!     Ok(())
64//! });
65//!
66//! // listen to screen events
67//! nw_sys::screen::on("displayBoundsChanged", display_bounds_changed_callback.into_js());
68//! nw_sys::screen::on("displayAdded", display_added_callback.into_js());
69//! nw_sys::screen::on("displayRemoved", display_removed_callback.into_js());
70//!
71//! // save callbacks somewhere
72//! app.push_callback(display_bounds_changed_callback)?;
73//! app.push_callback(display_added_callback)?;
74//! app.push_callback(display_removed_callback)?;
75//!
76//! ```
77
78use js_sys::{Array, Function};
79use wasm_bindgen::prelude::*;
80//use workflow_log::log_info;
81//use crate::options::OptionsExt;
82use crate::result::Result;
83use crate::utils;
84
85#[wasm_bindgen]
86extern "C" {
87
88    #[wasm_bindgen(js_namespace=nw, js_name = Screen)]
89    #[derive(Debug, Clone)]
90    type ScreenLocal;
91
92    #[wasm_bindgen(js_namespace=["nw", "Screen"], js_name = Init)]
93    /// Init the Screen singleton object, you only need to call this once
94    ///
95    /// ⧉ [NWJS Documentation](https://docs.nwjs.io/en/latest/References/Screen/#screeninit)
96    ///
97    pub fn init();
98
99    #[wasm_bindgen(getter, static_method_of=ScreenLocal, js_namespace=["nw"], js_class=Screen, js_name = screens)]
100    fn screens_impl() -> Array;
101
102    #[wasm_bindgen(js_namespace=["nw", "Screen"], js_name = chooseDesktopMedia)]
103    fn choose_desktop_media_impl(sources: Array, callback: &Function);
104
105    #[wasm_bindgen(js_namespace=["nw", "Screen"], js_name = on)]
106    ///
107    ///
108    /// Interface for accessing display & monitor layout information. For usage example please refer to [nw_sys::screen](self)
109    ///
110    /// ### Events:
111    /// - displayBoundsChanged (screen)
112    /// - displayAdded (screen)
113    /// - displayRemoved (screen)
114    ///
115    ///
116    /// ⧉ [NWJS Documentation](https://docs.nwjs.io/en/latest/References/Screen/#event-displayboundschangedscreen)
117    ///
118    pub fn on(event_name: &str, callback: &Function);
119}
120
121pub mod desktop_capture_monitor {
122    use js_sys::Function;
123    use wasm_bindgen::prelude::*;
124
125    #[wasm_bindgen]
126    extern "C" {
127
128        #[wasm_bindgen(js_namespace=["nw", "Screen"], js_name = DesktopCaptureMonitor)]
129        #[derive(Debug, Clone)]
130        type DCM;
131
132        #[wasm_bindgen(getter, static_method_of=DCM, js_namespace=["nw", "Screen"], js_class=DesktopCaptureMonitor, js_name = started)]
133        /// Return Boolean of whether the DesktopCaptureMonitor is started.
134        ///
135        ///
136        /// ⧉ [NWJS Documentation](https://docs.nwjs.io/en/latest/References/Screen/#screendesktopcapturemonitorstarted)
137        ///
138        pub fn started() -> bool;
139
140        #[wasm_bindgen(js_namespace=["nw", "Screen", "DesktopCaptureMonitor"], js_name = start)]
141        /// The DesktopCaptureMonitor will start monitoring the system
142        /// and trigger the the events. The screen may flicker
143        /// if while DesktopCaptureMonitor is running.
144        ///
145        /// Example:
146        /// ```rust
147        /// nw::screen::desktop_capture_monitor::start(true, true);
148        /// ```
149        ///
150        /// ⧉ [NWJS Documentation](https://docs.nwjs.io/en/latest/References/Screen/#screendesktopcapturemonitorstartshould_include_screens-should_include_windows)
151        ///
152        pub fn start(should_include_screens: bool, should_include_windows: bool);
153
154        #[wasm_bindgen(js_namespace=["nw", "Screen", "DesktopCaptureMonitor"], js_name = stop)]
155        /// The `DesktopCaptureMonitor` will stop monitoring the system.
156        /// `DesktopCaptureMonitor` should be stopped after a stream is selected.
157        ///
158        ///
159        /// ⧉ [NWJS Documentation](https://docs.nwjs.io/en/latest/References/Screen/#screendesktopcapturemonitorstop)
160        ///
161        pub fn stop();
162
163        #[wasm_bindgen(js_namespace=["nw", "Screen", "DesktopCaptureMonitor"], js_name = registerStream)]
164        /// Register and return a valid stream id which will be used into
165        /// chromeMediaSourceId in get_user_media constraints.
166        ///
167        /// See Synopsis for the usage.
168        ///
169        ///
170        /// ⧉ [NWJS Documentation](https://docs.nwjs.io/en/latest/References/Screen/#screendesktopcapturemonitorregisterstreamid)
171        ///
172        pub fn register_stream(id: &str) -> String;
173
174        #[wasm_bindgen(static_method_of=DCM, js_namespace=["nw", "Screen"], js_class=DesktopCaptureMonitor, js_name = on)]
175        /// Add event listener
176        ///
177        /// ### Events:
178        /// - added (id, name, order, type, primary)
179        /// - removed (order)
180        /// - orderchanged (id, new_order, old_order)
181        /// - namechanged (id, name)
182        /// - thumbnailchanged (id, thumbnail)
183        ///
184        ///
185        /// ⧉ [NWJS Documentation](https://docs.nwjs.io/en/latest/References/Screen/#event-added-id-name-order-type-primary)
186        ///
187        pub fn on(event_name: &str, callback: &Function);
188    }
189
190    /// Return Boolean of whether the DesktopCaptureMonitor is started.
191    ///
192    ///
193    /// ⧉ [NWJS Documentation](https://docs.nwjs.io/en/latest/References/Screen/#screendesktopcapturemonitorstarted)
194    ///
195    pub fn started() -> bool {
196        DCM::started()
197    }
198
199    /// Add event listener
200    ///
201    /// ### Events:
202    /// - added (id, name, order, type, primary)
203    /// - removed (order)
204    /// - orderchanged (id, new_order, old_order)
205    /// - namechanged (id, name)
206    /// - thumbnailchanged (id, thumbnail)
207    ///
208    ///
209    /// ⧉ [NWJS Documentation](https://docs.nwjs.io/en/latest/References/Screen/#event-added-id-name-order-type-primary)
210    ///
211    pub fn on(event_name: &str, callback: &Function) {
212        DCM::on(event_name, callback)
213    }
214}
215
216static mut INIT: bool = false;
217
218/// Return true is screen is initialized
219pub fn is_initialized() -> bool {
220    unsafe { INIT }
221}
222
223/// Call the screen::init() if screen is not initialized yet
224pub fn init_once() {
225    if !is_initialized() {
226        unsafe { INIT = true };
227        init();
228    }
229}
230
231/// Media source type
232pub enum MediaSources {
233    Screen,
234    Window,
235    ScreenAndWindow,
236}
237
238/// Choose desktop media
239///
240/// Screen sharing by selection; Currently only working in Windows and OSX
241/// and some linux distribution.
242///
243/// ⧉ [NWJS Documentation](https://docs.nwjs.io/en/latest/References/Screen/#screenchoosedesktopmedia-sources-callback)
244///
245pub fn choose_desktop_media(sources: MediaSources, callback: &Function) -> Result<()> {
246    let array = Array::new();
247    match sources {
248        MediaSources::Screen => {
249            array.push(&JsValue::from("screen"));
250        }
251        MediaSources::Window => {
252            array.push(&JsValue::from("window"));
253        }
254        MediaSources::ScreenAndWindow => {
255            array.push(&JsValue::from("screen"));
256            array.push(&JsValue::from("window"));
257        }
258    };
259
260    choose_desktop_media_impl(array, callback);
261    Ok(())
262}
263
264/// Get the array of screen (number of screen connected to the computer)
265///
266/// ⧉ [NWJS Documentation](https://docs.nwjs.io/en/latest/References/Screen/#screenscreens)
267///
268pub fn screens() -> Result<Vec<ScreenInfo>> {
269    let mut result: Vec<ScreenInfo> = Vec::new();
270    let array = ScreenLocal::screens_impl();
271    for index in 0..array.length() {
272        let screen = array.get(index);
273        //log_info!("screen: {:#?}", screen);
274        result.push(screen.try_into()?);
275    }
276    Ok(result)
277}
278
279/// physical screen resolution, can be negative,
280/// not necessarily start from 0,
281/// depending on screen arrangement
282#[derive(Debug)]
283pub struct Bounds {
284    pub x: f64,
285    pub y: f64,
286    pub width: f64,
287    pub height: f64,
288}
289
290/// useable area within the screen bound
291#[derive(Debug)]
292pub struct WorkArea {
293    pub x: f64,
294    pub y: f64,
295    pub width: f64,
296    pub height: f64,
297}
298
299/// Screen Info
300#[derive(Debug)]
301pub struct ScreenInfo {
302    pub id: u64,
303    pub scale_factor: f64,
304    pub is_built_in: bool,
305    pub rotation: u64,
306    pub touch_support: u64,
307    pub bounds: Bounds,
308    pub work_area: WorkArea,
309}
310
311fn read_box(jsv: &JsValue, prop: &str) -> Result<(f64, f64, f64, f64)> {
312    let jsv = utils::try_get_js_value(jsv, prop)?;
313    let x = utils::try_get_f64_from_prop(&jsv, "x")?;
314    let y = utils::try_get_f64_from_prop(&jsv, "y")?;
315    let width = utils::try_get_f64_from_prop(&jsv, "width")?;
316    let height = utils::try_get_f64_from_prop(&jsv, "height")?;
317
318    Ok((x, y, width, height))
319}
320
321impl TryFrom<JsValue> for ScreenInfo {
322    type Error = crate::error::Error;
323    fn try_from(jsv: JsValue) -> std::result::Result<Self, Self::Error> {
324        let id = utils::try_get_u64_from_prop(&jsv, "id")?;
325        let scale_factor = utils::try_get_f64_from_prop(&jsv, "scaleFactor")?;
326        let is_built_in = utils::try_get_bool_from_prop(&jsv, "isBuiltIn")?;
327        let rotation = utils::try_get_u64_from_prop(&jsv, "rotation")?;
328        let touch_support = utils::try_get_u64_from_prop(&jsv, "touchSupport")?;
329
330        let (x, y, width, height) = read_box(&jsv, "bounds")?;
331        let bounds = Bounds {
332            x,
333            y,
334            width,
335            height,
336        };
337
338        let (x, y, width, height) = read_box(&jsv, "work_area")?;
339        let work_area = WorkArea {
340            x,
341            y,
342            width,
343            height,
344        };
345
346        let info = Self {
347            id,
348            scale_factor,
349            is_built_in,
350            rotation,
351            touch_support,
352            bounds,
353            work_area,
354        };
355        Ok(info)
356    }
357}