zng_app/
view_process.rs

1//! View process connection and other types.
2
3use std::{
4    collections::HashMap,
5    fmt,
6    path::PathBuf,
7    sync::{self, Arc},
8};
9
10pub mod raw_device_events;
11pub mod raw_events;
12
13use crate::{
14    event::{event, event_args},
15    window::{MonitorId, WindowId},
16};
17
18use parking_lot::{MappedRwLockReadGuard, MappedRwLockWriteGuard};
19use zng_app_context::app_local;
20use zng_layout::unit::{DipPoint, DipRect, DipSideOffsets, DipSize, Factor, Px, PxPoint, PxRect};
21use zng_task::channel::{self, ChannelError, IpcBytes, IpcReceiver, Receiver};
22use zng_txt::Txt;
23use zng_var::{ArcEq, ResponderVar, Var, VarHandle, WeakEq};
24use zng_view_api::{
25    self, DeviceEventsFilter, DragDropId, Event, FocusResult, ViewProcessGen, ViewProcessInfo,
26    api_extension::{ApiExtensionId, ApiExtensionName, ApiExtensionPayload, ApiExtensionRecvError},
27    audio::{
28        AudioDecoded, AudioId, AudioMetadata, AudioMix, AudioOutputConfig, AudioOutputId as ApiAudioOutputId, AudioOutputOpenData,
29        AudioOutputRequest, AudioOutputUpdateRequest, AudioPlayId, AudioPlayRequest, AudioRequest,
30    },
31    dialog::{FileDialog, FileDialogResponse, MsgDialog, MsgDialogResponse, Notification, NotificationResponse},
32    drag_drop::{DragDropData, DragDropEffect, DragDropError},
33    font::{FontOptions, IpcFontBytes},
34    image::{ImageDecoded, ImageEncodeId, ImageEncodeRequest, ImageMaskMode, ImageMetadata, ImageRequest, ImageTextureId},
35    window::{
36        CursorIcon, FocusIndicator, FrameRequest, FrameUpdateRequest, HeadlessOpenData, HeadlessRequest, RenderMode, ResizeDirection,
37        VideoMode, WindowButton, WindowRequest, WindowStateAll,
38    },
39};
40
41pub(crate) use zng_view_api::{
42    Controller, raw_input::InputDeviceId as ApiDeviceId, window::MonitorId as ApiMonitorId, window::WindowId as ApiWindowId,
43};
44use zng_view_api::{
45    clipboard::{ClipboardData, ClipboardError, ClipboardType},
46    font::{FontFaceId, FontId, FontVariationName},
47    image::ImageId,
48};
49
50use self::raw_device_events::InputDeviceId;
51
52use super::{APP, AppId};
53
54/// Connection to the running view-process for the context app.
55#[expect(non_camel_case_types)]
56pub struct VIEW_PROCESS;
57struct ViewProcessService {
58    process: zng_view_api::Controller,
59    input_device_ids: HashMap<ApiDeviceId, InputDeviceId>,
60    monitor_ids: HashMap<ApiMonitorId, MonitorId>,
61
62    data_generation: ViewProcessGen,
63
64    loading_images: Vec<WeakEq<ViewImageHandleData>>,
65    encoding_images: Vec<EncodeRequest>,
66
67    loading_audios: Vec<WeakEq<ViewAudioHandleData>>,
68
69    pending_frames: usize,
70
71    message_dialogs: Vec<(zng_view_api::dialog::DialogId, ResponderVar<MsgDialogResponse>)>,
72    file_dialogs: Vec<(zng_view_api::dialog::DialogId, ResponderVar<FileDialogResponse>)>,
73    notifications: Vec<(zng_view_api::dialog::DialogId, VarHandle, ResponderVar<NotificationResponse>)>,
74
75    ping_count: u16,
76}
77app_local! {
78    static VIEW_PROCESS_SV: Option<ViewProcessService> = None;
79    static VIEW_PROCESS_INFO: ViewProcessInfo = const { ViewProcessInfo::new(ViewProcessGen::INVALID, false) };
80}
81impl VIEW_PROCESS {
82    /// If the `VIEW_PROCESS` can be used, this is only true in app threads for apps with render, all other
83    /// methods will panic if called when this is not true.
84    pub fn is_available(&self) -> bool {
85        APP.is_running() && VIEW_PROCESS_SV.read().is_some()
86    }
87
88    fn read(&self) -> MappedRwLockReadGuard<'_, ViewProcessService> {
89        VIEW_PROCESS_SV.read_map(|e| e.as_ref().expect("VIEW_PROCESS not available"))
90    }
91
92    fn write(&self) -> MappedRwLockWriteGuard<'_, ViewProcessService> {
93        VIEW_PROCESS_SV.write_map(|e| e.as_mut().expect("VIEW_PROCESS not available"))
94    }
95
96    fn try_write(&self) -> Result<MappedRwLockWriteGuard<'_, ViewProcessService>> {
97        let vp = VIEW_PROCESS_SV.write();
98        if let Some(w) = &*vp
99            && w.process.is_connected()
100        {
101            return Ok(MappedRwLockWriteGuard::map(vp, |w| w.as_mut().unwrap()));
102        }
103        Err(ChannelError::disconnected())
104    }
105
106    fn check_app(&self, id: AppId) {
107        let actual = APP.id();
108        if Some(id) != actual {
109            panic!("cannot use view handle from app `{id:?}` in app `{actual:?}`");
110        }
111    }
112
113    fn handle_write(&self, id: AppId) -> MappedRwLockWriteGuard<'_, ViewProcessService> {
114        self.check_app(id);
115        self.write()
116    }
117
118    /// View-process running, connected and ready.
119    pub fn is_connected(&self) -> bool {
120        self.read().process.is_connected()
121    }
122
123    /// If is running in headless renderer mode.
124    pub fn is_headless_with_render(&self) -> bool {
125        self.read().process.headless()
126    }
127
128    /// If is running both view and app in the same process.
129    pub fn is_same_process(&self) -> bool {
130        self.read().process.same_process()
131    }
132
133    /// Read lock view-process and reference current generation info.
134    ///
135    /// Strongly recommend to clone/copy the info required, as the lock prevents info update on respawn.
136    pub fn info(&self) -> MappedRwLockReadGuard<'static, ViewProcessInfo> {
137        VIEW_PROCESS_INFO.read()
138    }
139
140    /// Gets the current view-process generation.
141    pub fn generation(&self) -> ViewProcessGen {
142        self.read().process.generation()
143    }
144
145    /// Enable/disable global device events.
146    ///
147    /// This filter affects device events not targeted at windows, such as mouse move outside windows or
148    /// key presses when the app has no focused window.
149    pub fn set_device_events_filter(&self, filter: DeviceEventsFilter) -> Result<()> {
150        self.write().process.set_device_events_filter(filter)
151    }
152
153    /// Sends a request to open a window and associate it with the `window_id`.
154    ///
155    /// A [`RAW_WINDOW_OPEN_EVENT`] or [`RAW_WINDOW_OR_HEADLESS_OPEN_ERROR_EVENT`] will be received in response to this request.
156    ///
157    /// [`RAW_WINDOW_OPEN_EVENT`]: crate::view_process::raw_events::RAW_WINDOW_OPEN_EVENT
158    /// [`RAW_WINDOW_OR_HEADLESS_OPEN_ERROR_EVENT`]: crate::view_process::raw_events::RAW_WINDOW_OR_HEADLESS_OPEN_ERROR_EVENT
159    pub fn open_window(&self, config: WindowRequest) -> Result<()> {
160        self.write().process.open_window(config)
161    }
162
163    /// Sends a request to open a headless renderer and associate it with the `window_id`.
164    ///
165    /// Note that no actual window is created, only the renderer, the use of window-ids to identify
166    /// this renderer is only for convenience.
167    ///
168    /// A [`RAW_HEADLESS_OPEN_EVENT`] or [`RAW_WINDOW_OR_HEADLESS_OPEN_ERROR_EVENT`] will be received in response to this request.
169    ///
170    /// [`RAW_HEADLESS_OPEN_EVENT`]: crate::view_process::raw_events::RAW_HEADLESS_OPEN_EVENT
171    /// [`RAW_WINDOW_OR_HEADLESS_OPEN_ERROR_EVENT`]: crate::view_process::raw_events::RAW_WINDOW_OR_HEADLESS_OPEN_ERROR_EVENT
172    pub fn open_headless(&self, config: HeadlessRequest) -> Result<()> {
173        self.write().process.open_headless(config)
174    }
175
176    /// Send a request to open a connection to an audio output device.
177    ///
178    /// A [`RAW_AUDIO_OUTPUT_OPEN_EVENT`] or [`RAW_AUDIO_OUTPUT_OPEN_ERROR_EVENT`]
179    ///
180    /// [`RAW_AUDIO_OUTPUT_OPEN_EVENT`]: crate::view_process::raw_events::RAW_AUDIO_OUTPUT_OPEN_EVENT
181    /// [`RAW_AUDIO_OUTPUT_OPEN_ERROR_EVENT`]: crate::view_process::raw_events::RAW_AUDIO_OUTPUT_OPEN_ERROR_EVENT
182    pub fn open_audio_output(&self, request: AudioOutputRequest) -> Result<()> {
183        self.write().process.open_audio_output(request)
184    }
185
186    /// Send an image for decoding and caching.
187    ///
188    /// This function returns immediately, the handle must be held and compared with the [`RAW_IMAGE_METADATA_DECODED_EVENT`],
189    /// [`RAW_IMAGE_DECODED_EVENT`] and [`RAW_IMAGE_DECODE_ERROR_EVENT`] events to receive the data.
190    ///
191    /// [`RAW_IMAGE_METADATA_DECODED_EVENT`]: crate::view_process::raw_events::RAW_IMAGE_METADATA_DECODED_EVENT
192    /// [`RAW_IMAGE_DECODED_EVENT`]: crate::view_process::raw_events::RAW_IMAGE_DECODED_EVENT
193    /// [`RAW_IMAGE_DECODE_ERROR_EVENT`]: crate::view_process::raw_events::RAW_IMAGE_DECODE_ERROR_EVENT
194    pub fn add_image(&self, request: ImageRequest<IpcBytes>) -> Result<ViewImageHandle> {
195        let mut app = self.write();
196
197        let id = app.process.add_image(request)?;
198
199        let handle = ArcEq::new((APP.id().unwrap(), app.process.generation(), id));
200        app.loading_images.push(ArcEq::downgrade(&handle));
201
202        Ok(ViewImageHandle(Some(handle)))
203    }
204
205    /// Starts sending an image for *progressive* decoding and caching.
206    ///
207    /// This function returns immediately, the handle must be held and compared with the [`RAW_IMAGE_METADATA_DECODED_EVENT`],
208    /// [`RAW_IMAGE_DECODED_EVENT`] and [`RAW_IMAGE_DECODE_ERROR_EVENT`] events to receive the data.
209    ///
210    /// [`RAW_IMAGE_METADATA_DECODED_EVENT`]: crate::view_process::raw_events::RAW_IMAGE_METADATA_DECODED_EVENT
211    /// [`RAW_IMAGE_DECODED_EVENT`]: crate::view_process::raw_events::RAW_IMAGE_DECODED_EVENT
212    /// [`RAW_IMAGE_DECODE_ERROR_EVENT`]: crate::view_process::raw_events::RAW_IMAGE_DECODE_ERROR_EVENT
213    pub fn add_image_pro(&self, request: ImageRequest<IpcReceiver<IpcBytes>>) -> Result<ViewImageHandle> {
214        let mut app = self.write();
215
216        let id = app.process.add_image_pro(request)?;
217
218        let handle = ArcEq::new((APP.id().unwrap(), app.process.generation(), id));
219        app.loading_images.push(ArcEq::downgrade(&handle));
220
221        Ok(ViewImageHandle(Some(handle)))
222    }
223
224    /// Starts encoding an image.
225    ///
226    /// The returned channel will update once with the result.
227    pub fn encode_image(&self, request: ImageEncodeRequest) -> Receiver<std::result::Result<IpcBytes, EncodeError>> {
228        let (sender, receiver) = channel::bounded(1);
229
230        if request.id != ImageId::INVALID {
231            let mut app = VIEW_PROCESS.write();
232
233            match app.process.encode_image(request) {
234                Ok(r) => {
235                    app.encoding_images.push(EncodeRequest {
236                        task_id: r,
237                        listener: sender,
238                    });
239                }
240                Err(_) => {
241                    let _ = sender.send_blocking(Err(EncodeError::Disconnected));
242                }
243            }
244        } else {
245            let _ = sender.send_blocking(Err(EncodeError::Dummy));
246        }
247
248        receiver
249    }
250
251    /// Send an audio for decoding and caching.
252    ///
253    /// Depending on the request the audio may be decoded entirely or it may be decoded on demand.
254    ///
255    /// This function returns immediately, the handle must be held and compared with the [`RAW_AUDIO_METADATA_DECODED_EVENT`],
256    /// [`RAW_AUDIO_DECODED_EVENT`] and [`RAW_AUDIO_DECODE_ERROR_EVENT`] events to receive the metadata and data.
257    ///
258    /// [`RAW_AUDIO_METADATA_DECODED_EVENT`]: crate::view_process::raw_events::RAW_AUDIO_METADATA_DECODED_EVENT
259    /// [`RAW_AUDIO_DECODED_EVENT`]: crate::view_process::raw_events::RAW_AUDIO_DECODED_EVENT
260    /// [`RAW_AUDIO_DECODE_ERROR_EVENT`]: crate::view_process::raw_events::RAW_AUDIO_DECODE_ERROR_EVENT
261    pub fn add_audio(&self, request: AudioRequest<IpcBytes>) -> Result<ViewAudioHandle> {
262        let mut app = self.write();
263
264        let id = app.process.add_audio(request)?;
265
266        let handle = ArcEq::new((APP.id().unwrap(), app.process.generation(), id));
267        app.loading_audios.push(ArcEq::downgrade(&handle));
268
269        Ok(ViewAudioHandle(Some(handle)))
270    }
271
272    /// Starts sending an audio for decoding and caching.
273    ///
274    /// Depending on the request the audio may be decoded as it is received or it may be decoded on demand.
275    ///
276    /// This function returns immediately, the handle must be held and compared with the [`RAW_AUDIO_METADATA_DECODED_EVENT`],
277    /// [`RAW_AUDIO_DECODED_EVENT`] and [`RAW_AUDIO_DECODE_ERROR_EVENT`] events to receive the metadata and data.
278    ///
279    /// [`RAW_AUDIO_METADATA_DECODED_EVENT`]: crate::view_process::raw_events::RAW_AUDIO_METADATA_DECODED_EVENT
280    /// [`RAW_AUDIO_DECODED_EVENT`]: crate::view_process::raw_events::RAW_AUDIO_DECODED_EVENT
281    /// [`RAW_AUDIO_DECODE_ERROR_EVENT`]: crate::view_process::raw_events::RAW_AUDIO_DECODE_ERROR_EVENT
282    pub fn add_audio_pro(&self, request: AudioRequest<IpcReceiver<IpcBytes>>) -> Result<ViewAudioHandle> {
283        let mut app = self.write();
284
285        let id = app.process.add_audio_pro(request)?;
286
287        let handle = ArcEq::new((APP.id().unwrap(), app.process.generation(), id));
288        app.loading_audios.push(ArcEq::downgrade(&handle));
289
290        Ok(ViewAudioHandle(Some(handle)))
291    }
292
293    /// View-process clipboard methods.
294    pub fn clipboard(&self) -> Result<&ViewClipboard> {
295        if VIEW_PROCESS.is_connected() {
296            Ok(&ViewClipboard {})
297        } else {
298            Err(ChannelError::disconnected())
299        }
300    }
301
302    /// Register a native notification, either a popup or an entry in the system notifications list.
303    ///
304    /// If the `notification` var updates the notification content updates or closes.
305    ///
306    /// If the notification is responded the `responder` variable is set.
307    pub fn notification_dialog(&self, notification: Var<Notification>, responder: ResponderVar<NotificationResponse>) -> Result<()> {
308        let mut app = self.write();
309        let dlg_id = app.process.notification_dialog(notification.get())?;
310        let handle = notification.hook(move |n| {
311            let mut app = VIEW_PROCESS.write();
312            let retain = app.notifications.iter().any(|(id, _, _)| *id == dlg_id);
313            if retain {
314                app.process.update_notification(dlg_id, n.value().clone()).ok();
315            }
316            retain
317        });
318        app.notifications.push((dlg_id, handle, responder));
319        Ok(())
320    }
321
322    /// Number of frame send that have not finished rendering.
323    ///
324    /// This is the sum of pending frames for all renderers.
325    pub fn pending_frames(&self) -> usize {
326        self.write().pending_frames
327    }
328
329    /// Reopen the view-process, causing another [`Event::Inited`].
330    ///
331    /// [`Event::Inited`]: zng_view_api::Event::Inited
332    pub fn respawn(&self) {
333        self.write().process.respawn()
334    }
335
336    /// Gets the ID for the `extension_name` in the current view-process.
337    ///
338    /// The ID can change for every view-process instance, you must subscribe to the
339    /// [`VIEW_PROCESS_INITED_EVENT`] to refresh the ID. The view-process can respawn
340    /// at any time in case of error.
341    pub fn extension_id(&self, extension_name: impl Into<ApiExtensionName>) -> Result<Option<ApiExtensionId>> {
342        let me = self.read();
343        if me.process.is_connected() {
344            Ok(self.info().extensions.id(&extension_name.into()))
345        } else {
346            Err(ChannelError::disconnected())
347        }
348    }
349
350    /// Licenses that may be required to be displayed in the app about screen.
351    ///
352    /// This is specially important for prebuilt view users, as the tools that scrap licenses
353    /// may not find the prebuilt dependencies.
354    pub fn third_party_licenses(&self) -> Result<Vec<crate::third_party::LicenseUsed>> {
355        self.write().process.third_party_licenses()
356    }
357
358    /// Call an extension with custom encoded payload.
359    pub fn app_extension_raw(&self, extension_id: ApiExtensionId, extension_request: ApiExtensionPayload) -> Result<ApiExtensionPayload> {
360        self.write().process.app_extension(extension_id, extension_request)
361    }
362
363    /// Call an extension with payload `request`.
364    pub fn app_extension<I, O>(&self, extension_id: ApiExtensionId, request: &I) -> Result<std::result::Result<O, ApiExtensionRecvError>>
365    where
366        I: serde::Serialize,
367        O: serde::de::DeserializeOwned,
368    {
369        let payload = ApiExtensionPayload::serialize(&request).unwrap();
370        let response = self.write().process.app_extension(extension_id, payload)?;
371        Ok(response.deserialize::<O>())
372    }
373
374    /// Handle an [`Event::Disconnected`].
375    ///
376    /// The process will exit if the view-process was killed by the user.
377    ///
378    /// [`Event::Disconnected`]: zng_view_api::Event::Disconnected
379    pub fn handle_disconnect(&self, vp_gen: ViewProcessGen) {
380        self.write().process.handle_disconnect(vp_gen)
381    }
382
383    /// Spawn the View Process.
384    pub(super) fn start<F>(&self, view_process_exe: PathBuf, view_process_env: HashMap<Txt, Txt>, headless: bool, on_event: F)
385    where
386        F: FnMut(Event) + Send + 'static,
387    {
388        let _s = tracing::debug_span!("VIEW_PROCESS.start", ?view_process_exe, ?view_process_env, ?headless).entered();
389
390        let process = zng_view_api::Controller::start(view_process_exe, view_process_env, headless, on_event);
391        *VIEW_PROCESS_SV.write() = Some(ViewProcessService {
392            data_generation: process.generation(),
393            process,
394            input_device_ids: HashMap::default(),
395            monitor_ids: HashMap::default(),
396            loading_images: vec![],
397            encoding_images: vec![],
398            loading_audios: vec![],
399            pending_frames: 0,
400            message_dialogs: vec![],
401            file_dialogs: vec![],
402            notifications: vec![],
403            ping_count: 0,
404        });
405    }
406
407    pub(crate) fn on_window_opened(&self, window_id: WindowId, data: zng_view_api::window::WindowOpenData) -> (ViewWindow, WindowOpenData) {
408        let mut app = self.write();
409        let _ = app.check_generation();
410
411        let win = ViewWindow(ArcEq::new(ViewWindowData {
412            app_id: APP.id().unwrap(),
413            id: ApiWindowId::from_raw(window_id.get()),
414            generation: app.data_generation,
415        }));
416        drop(app);
417
418        let data = WindowOpenData::new(data, |id| self.monitor_id(id));
419
420        (win, data)
421    }
422
423    pub(crate) fn on_audio_output_opened(&self, output_id: AudioOutputId, data: AudioOutputOpenData) -> ViewAudioOutput {
424        let mut app = self.write();
425        let _ = app.check_generation();
426
427        ViewAudioOutput(ArcEq::new(ViewAudioOutputData {
428            app_id: APP.id().unwrap(),
429            id: ApiAudioOutputId::from_raw(output_id.get()),
430            generation: app.data_generation,
431            data,
432        }))
433    }
434
435    /// Translate input device ID, generates a device id if it was unknown.
436    pub(super) fn input_device_id(&self, id: ApiDeviceId) -> InputDeviceId {
437        *self.write().input_device_ids.entry(id).or_insert_with(InputDeviceId::new_unique)
438    }
439
440    /// Translate `MonId` to `MonitorId`, generates a monitor id if it was unknown.
441    pub(super) fn monitor_id(&self, id: ApiMonitorId) -> MonitorId {
442        *self.write().monitor_ids.entry(id).or_insert_with(MonitorId::new_unique)
443    }
444
445    /// Handle an [`Event::Inited`].
446    ///
447    /// The view-process becomes "connected" only after this call.
448    ///
449    /// [`Event::Inited`]: zng_view_api::Event::Inited
450    pub(super) fn handle_inited(&self, inited: &zng_view_api::ViewProcessInfo) {
451        let mut me = self.write();
452        *VIEW_PROCESS_INFO.write() = inited.clone();
453        me.process.handle_inited(inited.generation);
454    }
455
456    pub(super) fn handle_suspended(&self) {
457        self.write().process.handle_suspended();
458    }
459
460    pub(crate) fn on_headless_opened(
461        &self,
462        id: WindowId,
463        data: zng_view_api::window::HeadlessOpenData,
464    ) -> (ViewHeadless, HeadlessOpenData) {
465        let mut app = self.write();
466        let _ = app.check_generation();
467
468        let surf = ViewHeadless(ArcEq::new(ViewWindowData {
469            app_id: APP.id().unwrap(),
470            id: ApiWindowId::from_raw(id.get()),
471            generation: app.data_generation,
472        }));
473
474        (surf, data)
475    }
476
477    pub(super) fn on_image_metadata(&self, meta: &ImageMetadata) -> Option<ViewImageHandle> {
478        let mut app = self.write();
479
480        let mut found = None;
481        app.loading_images.retain(|i| {
482            if let Some(h) = i.upgrade() {
483                if found.is_none() && h.2 == meta.id {
484                    found = Some(h);
485                }
486                // retain
487                true
488            } else {
489                false
490            }
491        });
492
493        // Best effort avoid tracking handles already dropped,
494        // the VIEW_PROCESS handles all image requests so we
495        // can track all primary requests, only entry images are send without
496        // knowing so we can skip all not found without parent.
497        //
498        // This could potentially restart tracking an entry that was dropped, but
499        // all that does is generate a no-op event and a second `forget_image` requests for the view-process.
500
501        if found.is_none() && meta.parent.is_some() {
502            // start tracking entry image
503
504            let handle = ArcEq::new((APP.id().unwrap(), app.process.generation(), meta.id));
505            app.loading_images.push(ArcEq::downgrade(&handle));
506
507            return Some(ViewImageHandle(Some(handle)));
508        }
509
510        found.map(|h| ViewImageHandle(Some(h)))
511    }
512
513    pub(super) fn on_image_decoded(&self, data: &ImageDecoded) -> Option<ViewImageHandle> {
514        let mut app = self.write();
515
516        // retain loading handle only for partial decode, cleanup for full decode.
517        //
518        // All valid not dropped requests are already in `loading_images` because they are
519        // either primary requests or are entries (view-process always sends metadata decoded first for entries).
520
521        let mut found = None;
522        app.loading_images.retain(|i| {
523            if let Some(h) = i.upgrade() {
524                if found.is_none() && h.2 == data.meta.id {
525                    found = Some(h);
526                    return data.partial.is_some();
527                }
528                true
529            } else {
530                false
531            }
532        });
533
534        found.map(|h| ViewImageHandle(Some(h)))
535    }
536
537    pub(super) fn on_image_error(&self, id: ImageId) -> Option<ViewImageHandle> {
538        let mut app = self.write();
539
540        let mut found = None;
541        app.loading_images.retain(|i| {
542            if let Some(h) = i.upgrade() {
543                if found.is_none() && h.2 == id {
544                    found = Some(h);
545                    return false;
546                }
547                true
548            } else {
549                false
550            }
551        });
552
553        // error images should already be removed from view-process, handle will request a removal anyway
554
555        found.map(|h| ViewImageHandle(Some(h)))
556    }
557
558    pub(super) fn on_audio_metadata(&self, meta: &AudioMetadata) -> Option<ViewAudioHandle> {
559        // this is very similar to `on_image_metadata`
560
561        let mut app = self.write();
562
563        let mut found = None;
564        app.loading_audios.retain(|i| {
565            if let Some(h) = i.upgrade() {
566                if found.is_none() && h.2 == meta.id {
567                    found = Some(h);
568                }
569                // retain
570                true
571            } else {
572                false
573            }
574        });
575
576        if found.is_none() && meta.parent.is_some() {
577            // start tracking entry track
578
579            let handle = ArcEq::new((APP.id().unwrap(), app.process.generation(), meta.id));
580            app.loading_audios.push(ArcEq::downgrade(&handle));
581
582            return Some(ViewAudioHandle(Some(handle)));
583        }
584
585        found.map(|h| ViewAudioHandle(Some(h)))
586    }
587
588    pub(super) fn on_audio_decoded(&self, audio: &AudioDecoded) -> Option<ViewAudioHandle> {
589        // this is very similar to `on_image_decoded`, the big difference is that
590        // partial decodes represent the latest decoded chunk, not all the previous decoded data,
591        // and it may never finish decoding too, the progressive source can never end or the request
592        // configured it to always decode on demand and drop the buffer as it is played.
593
594        let mut app = self.write();
595
596        let mut found = None;
597        app.loading_audios.retain(|i| {
598            if let Some(h) = i.upgrade() {
599                if found.is_none() && h.2 == audio.id {
600                    found = Some(h);
601                    return !audio.is_full;
602                }
603                true
604            } else {
605                false
606            }
607        });
608
609        found.map(|h| ViewAudioHandle(Some(h)))
610    }
611
612    pub(super) fn on_audio_error(&self, id: AudioId) -> Option<ViewAudioHandle> {
613        let mut app = self.write();
614
615        let mut found = None;
616        app.loading_audios.retain(|i| {
617            if let Some(h) = i.upgrade() {
618                if found.is_none() && h.2 == id {
619                    found = Some(h);
620                    return false;
621                }
622                true
623            } else {
624                false
625            }
626        });
627
628        // error audios should already be removed from view-process, handle will request a removal anyway
629
630        found.map(|h| ViewAudioHandle(Some(h)))
631    }
632
633    pub(crate) fn on_frame_rendered(&self, _id: WindowId) {
634        let mut vp = self.write();
635        vp.pending_frames = vp.pending_frames.saturating_sub(1);
636    }
637
638    pub(crate) fn on_frame_image(&self, data: &ImageDecoded) -> ViewImageHandle {
639        ViewImageHandle(Some(ArcEq::new((APP.id().unwrap(), self.generation(), data.meta.id))))
640    }
641
642    pub(super) fn on_image_encoded(&self, task_id: ImageEncodeId, data: IpcBytes) {
643        self.on_image_encode_result(task_id, Ok(data));
644    }
645    pub(super) fn on_image_encode_error(&self, task_id: ImageEncodeId, error: Txt) {
646        self.on_image_encode_result(task_id, Err(EncodeError::Encode(error)));
647    }
648    fn on_image_encode_result(&self, task_id: ImageEncodeId, result: std::result::Result<IpcBytes, EncodeError>) {
649        let mut app = self.write();
650        app.encoding_images.retain(move |r| {
651            let done = r.task_id == task_id;
652            if done {
653                let _ = r.listener.send_blocking(result.clone());
654            }
655            !done
656        })
657    }
658
659    pub(crate) fn on_message_dlg_response(&self, id: zng_view_api::dialog::DialogId, response: MsgDialogResponse) {
660        let mut app = self.write();
661        if let Some(i) = app.message_dialogs.iter().position(|(i, _)| *i == id) {
662            let (_, r) = app.message_dialogs.swap_remove(i);
663            r.respond(response);
664        }
665    }
666
667    pub(crate) fn on_file_dlg_response(&self, id: zng_view_api::dialog::DialogId, response: FileDialogResponse) {
668        let mut app = self.write();
669        if let Some(i) = app.file_dialogs.iter().position(|(i, _)| *i == id) {
670            let (_, r) = app.file_dialogs.swap_remove(i);
671            r.respond(response);
672        }
673    }
674
675    pub(crate) fn on_notification_dlg_response(&self, id: zng_view_api::dialog::DialogId, response: NotificationResponse) {
676        let mut app = self.write();
677        if let Some(i) = app.notifications.iter().position(|(i, _, _)| *i == id) {
678            let (_, _, r) = app.notifications.swap_remove(i);
679            r.respond(response);
680        }
681    }
682
683    pub(super) fn on_respawned(&self, _gen: ViewProcessGen) {
684        let mut app = self.write();
685        app.pending_frames = 0;
686        for (_, r) in app.message_dialogs.drain(..) {
687            r.respond(MsgDialogResponse::Error(Txt::from_static("respawn")));
688        }
689        for (_, r) in app.file_dialogs.drain(..) {
690            r.respond(FileDialogResponse::Error(Txt::from_static("respawn")));
691        }
692        for (_, _, r) in app.notifications.drain(..) {
693            r.respond(NotificationResponse::Error(Txt::from_static("respawn")));
694        }
695    }
696
697    pub(crate) fn exit(&self) {
698        *VIEW_PROCESS_SV.write() = None;
699    }
700
701    pub(crate) fn ping(&self) {
702        let mut app = self.write();
703        let count = app.ping_count.wrapping_add(1);
704        if let Ok(c) = app.process.ping(count)
705            && c != count
706        {
707            tracing::error!("incorrect ping response, expected {count}, was {c}");
708        }
709        app.ping_count = count;
710    }
711
712    pub(crate) fn on_pong(&self, count: u16) {
713        let expected = self.read().ping_count;
714        if expected != count {
715            // this could indicates a severe slowdown in the event pump
716            tracing::warn!("unexpected pong event, expected {expected}, was {count}");
717        }
718    }
719}
720impl ViewProcessService {
721    #[must_use = "if `true` all current WinId, DevId and MonId are invalid"]
722    fn check_generation(&mut self) -> bool {
723        let vp_gen = self.process.generation();
724        let invalid = vp_gen != self.data_generation;
725        if invalid {
726            self.data_generation = vp_gen;
727            self.input_device_ids.clear();
728            self.monitor_ids.clear();
729        }
730        invalid
731    }
732}
733
734event_args! {
735    /// Arguments for the [`VIEW_PROCESS_INITED_EVENT`].
736    pub struct ViewProcessInitedArgs {
737        /// View-process implementation info.
738        pub info: zng_view_api::ViewProcessInfo,
739
740        ..
741
742        /// Broadcast to all.
743        fn is_in_target(&self, _id: WidgetId) -> bool {
744            true
745        }
746    }
747
748    /// Arguments for the [`VIEW_PROCESS_SUSPENDED_EVENT`].
749    pub struct ViewProcessSuspendedArgs {
750
751        ..
752
753        /// Broadcast to all.
754        fn is_in_target(&self, _id: WidgetId) -> bool {
755            true
756        }
757    }
758}
759impl std::ops::Deref for ViewProcessInitedArgs {
760    type Target = zng_view_api::ViewProcessInfo;
761
762    fn deref(&self) -> &Self::Target {
763        &self.info
764    }
765}
766
767event! {
768    /// View-Process finished initializing and is now connected and ready.
769    pub static VIEW_PROCESS_INITED_EVENT: ViewProcessInitedArgs;
770    /// View-Process suspended, all resources dropped.
771    ///
772    /// The view-process will only be available if the app resumes. On resume [`VIEW_PROCESS_INITED_EVENT`]
773    /// notify a view-process respawn.
774    pub static VIEW_PROCESS_SUSPENDED_EVENT: ViewProcessSuspendedArgs;
775}
776
777/// Information about a successfully opened window.
778#[derive(Debug, Clone, PartialEq)]
779#[non_exhaustive]
780pub struct WindowOpenData {
781    /// Window complete state.
782    pub state: WindowStateAll,
783
784    /// Monitor that contains the window.
785    pub monitor: Option<MonitorId>,
786
787    /// Final top-left offset of the window (excluding outer chrome).
788    ///
789    /// The values are the global position and the position in the monitor.
790    pub position: (PxPoint, DipPoint),
791    /// Final dimensions of the client area of the window (excluding outer chrome).
792    pub size: DipSize,
793
794    /// Final scale factor.
795    pub scale_factor: Factor,
796
797    /// Actual render mode, can be different from the requested mode if it is not available.
798    pub render_mode: RenderMode,
799
800    /// Padding that must be applied to the window content so that it stays clear of screen obstructions
801    /// such as a camera notch cutout.
802    ///
803    /// Note that the *unsafe* area must still be rendered as it may be partially visible, just don't place nay
804    /// interactive or important content outside of this padding.
805    pub safe_padding: DipSideOffsets,
806}
807impl WindowOpenData {
808    pub(crate) fn new(data: zng_view_api::window::WindowOpenData, map_monitor: impl FnOnce(ApiMonitorId) -> MonitorId) -> Self {
809        WindowOpenData {
810            state: data.state,
811            monitor: data.monitor.map(map_monitor),
812            position: data.position,
813            size: data.size,
814            scale_factor: data.scale_factor,
815            render_mode: data.render_mode,
816            safe_padding: data.safe_padding,
817        }
818    }
819}
820
821/// Handle to a window open in the view-process.
822///
823/// The window is closed when all clones of the handle are dropped.
824#[derive(Debug, Clone, PartialEq, Eq)]
825#[must_use = "the window is closed when all clones of the handle are dropped"]
826pub struct ViewWindow(ArcEq<ViewWindowData>);
827impl ViewWindow {
828    /// Returns the view-process generation on which the window was open.
829    pub fn generation(&self) -> ViewProcessGen {
830        self.0.generation
831    }
832
833    /// Set the window title.
834    pub fn set_title(&self, title: Txt) -> Result<()> {
835        self.0.call(|id, p| p.set_title(id, title))
836    }
837
838    /// Set the window visibility.
839    pub fn set_visible(&self, visible: bool) -> Result<()> {
840        self.0.call(|id, p| p.set_visible(id, visible))
841    }
842
843    /// Set if the window is "top-most".
844    pub fn set_always_on_top(&self, always_on_top: bool) -> Result<()> {
845        self.0.call(|id, p| p.set_always_on_top(id, always_on_top))
846    }
847
848    /// Set if the user can drag-move the window.
849    pub fn set_movable(&self, movable: bool) -> Result<()> {
850        self.0.call(|id, p| p.set_movable(id, movable))
851    }
852
853    /// Set if the user can resize the window.
854    pub fn set_resizable(&self, resizable: bool) -> Result<()> {
855        self.0.call(|id, p| p.set_resizable(id, resizable))
856    }
857
858    /// Set the window icon.
859    pub fn set_icon(&self, icon: Option<&ViewImageHandle>) -> Result<()> {
860        self.0.call(|id, p| {
861            if let Some(icon) = icon.and_then(|i| i.0.as_ref()) {
862                if p.generation() == icon.1 {
863                    p.set_icon(id, Some(icon.2))
864                } else {
865                    Err(ChannelError::disconnected())
866                }
867            } else {
868                p.set_icon(id, None)
869            }
870        })
871    }
872
873    /// Set the window cursor icon and visibility.
874    pub fn set_cursor(&self, cursor: Option<CursorIcon>) -> Result<()> {
875        self.0.call(|id, p| p.set_cursor(id, cursor))
876    }
877
878    /// Set the window cursor to a custom image.
879    ///
880    /// Falls back to cursor icon if set to `None`.
881    ///
882    /// The `hotspot` value is an exact point in the image that is the mouse position. This value is only used if
883    /// the image format does not contain a hotspot.
884    pub fn set_cursor_image(&self, cursor: Option<&ViewImageHandle>, hotspot: PxPoint) -> Result<()> {
885        self.0.call(|id, p| {
886            if let Some(cur) = cursor.and_then(|i| i.0.as_ref()) {
887                if p.generation() == cur.1 {
888                    p.set_cursor_image(id, Some(zng_view_api::window::CursorImage::new(cur.2, hotspot)))
889                } else {
890                    Err(ChannelError::disconnected())
891                }
892            } else {
893                p.set_cursor_image(id, None)
894            }
895        })
896    }
897
898    /// Set the window icon visibility in the taskbar.
899    pub fn set_taskbar_visible(&self, visible: bool) -> Result<()> {
900        self.0.call(|id, p| p.set_taskbar_visible(id, visible))
901    }
902
903    /// Bring the window the z top.
904    pub fn bring_to_top(&self) -> Result<()> {
905        self.0.call(|id, p| p.bring_to_top(id))
906    }
907
908    /// Set the window state.
909    pub fn set_state(&self, state: WindowStateAll) -> Result<()> {
910        self.0.call(|id, p| p.set_state(id, state))
911    }
912
913    /// Set video mode used in exclusive fullscreen.
914    pub fn set_video_mode(&self, mode: VideoMode) -> Result<()> {
915        self.0.call(|id, p| p.set_video_mode(id, mode))
916    }
917
918    /// Set enabled window chrome buttons.
919    pub fn set_enabled_buttons(&self, buttons: WindowButton) -> Result<()> {
920        self.0.call(|id, p| p.set_enabled_buttons(id, buttons))
921    }
922
923    /// Reference the window renderer.
924    pub fn renderer(&self) -> ViewRenderer {
925        ViewRenderer(ArcEq::downgrade(&self.0))
926    }
927
928    /// Sets if the headed window is in *capture-mode*. If `true` the resources used to capture
929    /// a screenshot may be kept in memory to be reused in the next screenshot capture.
930    ///
931    /// Note that capture must still be requested in each frame request.
932    pub fn set_capture_mode(&self, enabled: bool) -> Result<()> {
933        self.0.call(|id, p| p.set_capture_mode(id, enabled))
934    }
935
936    /// Brings the window to the front and sets input focus.
937    ///
938    /// This request can steal focus from other apps disrupting the user, be careful with it.
939    pub fn focus(&self) -> Result<FocusResult> {
940        self.0.call(|id, p| p.focus(id))
941    }
942
943    /// Sets the user attention request indicator, the indicator is cleared when the window is focused or
944    /// if canceled by setting to `None`.
945    pub fn set_focus_indicator(&self, indicator: Option<FocusIndicator>) -> Result<()> {
946        self.0.call(|id, p| p.set_focus_indicator(id, indicator))
947    }
948
949    /// Moves the window with the left mouse button until the button is released.
950    ///
951    /// There's no guarantee that this will work unless the left mouse button was pressed immediately before this function is called.
952    pub fn drag_move(&self) -> Result<()> {
953        self.0.call(|id, p| p.drag_move(id))
954    }
955
956    /// Resizes the window with the left mouse button until the button is released.
957    ///
958    /// There's no guarantee that this will work unless the left mouse button was pressed immediately before this function is called.
959    pub fn drag_resize(&self, direction: ResizeDirection) -> Result<()> {
960        self.0.call(|id, p| p.drag_resize(id, direction))
961    }
962
963    /// Start a drag and drop operation, if the window is pressed.
964    ///
965    /// A [`RAW_APP_DRAG_ENDED_EVENT`] will be received when the operation finishes.
966    ///
967    /// [`RAW_APP_DRAG_ENDED_EVENT`]: raw_events::RAW_APP_DRAG_ENDED_EVENT
968    pub fn start_drag_drop(
969        &self,
970        data: Vec<DragDropData>,
971        allowed_effects: DragDropEffect,
972    ) -> Result<std::result::Result<DragDropId, DragDropError>> {
973        self.0.call(|id, p| p.start_drag_drop(id, data, allowed_effects))
974    }
975
976    /// Notify the drag source of what effect was applied for a received drag&drop.
977    pub fn drag_dropped(&self, drop_id: DragDropId, applied: DragDropEffect) -> Result<()> {
978        self.0.call(|id, p| p.drag_dropped(id, drop_id, applied))
979    }
980
981    /// Open system title bar context menu.
982    pub fn open_title_bar_context_menu(&self, position: DipPoint) -> Result<()> {
983        self.0.call(|id, p| p.open_title_bar_context_menu(id, position))
984    }
985
986    /// Shows a native message dialog for the window.
987    ///
988    /// The window is not interactive while the dialog is visible and the dialog may be modal in the view-process.
989    /// In the app-process this is always async, and the response var will update once when the user responds.
990    pub fn message_dialog(&self, dlg: MsgDialog, responder: ResponderVar<MsgDialogResponse>) -> Result<()> {
991        let dlg_id = self.0.call(|id, p| p.message_dialog(id, dlg))?;
992        VIEW_PROCESS.handle_write(self.0.app_id).message_dialogs.push((dlg_id, responder));
993        Ok(())
994    }
995
996    /// Shows a native file/folder dialog for the window.
997    ///
998    /// The window is not interactive while the dialog is visible and the dialog may be modal in the view-process.
999    /// In the app-process this is always async, and the response var will update once when the user responds.
1000    pub fn file_dialog(&self, dlg: FileDialog, responder: ResponderVar<FileDialogResponse>) -> Result<()> {
1001        let dlg_id = self.0.call(|id, p| p.file_dialog(id, dlg))?;
1002        VIEW_PROCESS.handle_write(self.0.app_id).file_dialogs.push((dlg_id, responder));
1003        Ok(())
1004    }
1005
1006    /// Update the window's accessibility info tree.
1007    pub fn access_update(&self, update: zng_view_api::access::AccessTreeUpdate) -> Result<()> {
1008        self.0.call(|id, p| p.access_update(id, update))
1009    }
1010
1011    /// Enable or disable IME by setting a cursor area.
1012    ///
1013    /// In mobile platforms also shows the software keyboard for `Some(_)` and hides it for `None`.
1014    pub fn set_ime_area(&self, area: Option<DipRect>) -> Result<()> {
1015        self.0.call(|id, p| p.set_ime_area(id, area))
1016    }
1017
1018    /// Attempt to set a system wide shutdown warning associated with the window.
1019    ///
1020    /// Operating systems that support this show the `reason` in a warning for the user, it must be a short text
1021    /// that identifies the critical operation that cannot be cancelled.
1022    ///
1023    /// Note that there is no guarantee that the view-process or operating system will actually set a block, there
1024    /// is no error result because operating systems can silently ignore block requests at any moment, even after
1025    /// an initial successful block.
1026    ///
1027    /// Set to an empty text to remove the warning.
1028    pub fn set_system_shutdown_warn(&self, reason: Txt) -> Result<()> {
1029        self.0.call(move |id, p| p.set_system_shutdown_warn(id, reason))
1030    }
1031
1032    /// Drop `self`.
1033    pub fn close(self) {
1034        drop(self)
1035    }
1036
1037    /// Call a window extension with custom encoded payload.
1038    pub fn window_extension_raw(&self, extension_id: ApiExtensionId, request: ApiExtensionPayload) -> Result<ApiExtensionPayload> {
1039        self.0.call(|id, p| p.window_extension(id, extension_id, request))
1040    }
1041
1042    /// Call a window extension with serialized payload.
1043    pub fn window_extension<I, O>(&self, extension_id: ApiExtensionId, request: &I) -> Result<std::result::Result<O, ApiExtensionRecvError>>
1044    where
1045        I: serde::Serialize,
1046        O: serde::de::DeserializeOwned,
1047    {
1048        let r = self.window_extension_raw(extension_id, ApiExtensionPayload::serialize(&request).unwrap())?;
1049        Ok(r.deserialize())
1050    }
1051
1052    /// Create a weak reference to this handle.
1053    pub fn downgrade(&self) -> WeakViewWindow {
1054        WeakViewWindow(ArcEq::downgrade(&self.0))
1055    }
1056}
1057/// Weak reference to a [`ViewWindow`].
1058#[derive(Debug, Clone)]
1059pub struct WeakViewWindow(WeakEq<ViewWindowData>);
1060impl PartialEq for WeakViewWindow {
1061    fn eq(&self, other: &Self) -> bool {
1062        sync::Weak::ptr_eq(&self.0, &other.0)
1063    }
1064}
1065impl Eq for WeakViewWindow {}
1066impl WeakViewWindow {
1067    /// Create a strong reference to the view window, if it is still open.
1068    pub fn upgrade(&self) -> Option<ViewWindow> {
1069        let d = self.0.upgrade()?;
1070        if d.generation == VIEW_PROCESS.generation() {
1071            Some(ViewWindow(d))
1072        } else {
1073            None
1074        }
1075    }
1076}
1077
1078/// View window or headless surface.
1079#[derive(Clone, Debug)]
1080pub enum ViewWindowOrHeadless {
1081    /// Headed window view.
1082    Window(ViewWindow),
1083    /// Headless surface view.
1084    Headless(ViewHeadless),
1085}
1086impl ViewWindowOrHeadless {
1087    /// Reference the window or surface renderer.
1088    pub fn renderer(&self) -> ViewRenderer {
1089        match self {
1090            ViewWindowOrHeadless::Window(w) => w.renderer(),
1091            ViewWindowOrHeadless::Headless(h) => h.renderer(),
1092        }
1093    }
1094
1095    /// Call a window extension with custom encoded payload.
1096    pub fn window_extension_raw(&self, extension_id: ApiExtensionId, request: ApiExtensionPayload) -> Result<ApiExtensionPayload> {
1097        match self {
1098            ViewWindowOrHeadless::Window(w) => w.window_extension_raw(extension_id, request),
1099            ViewWindowOrHeadless::Headless(h) => h.window_extension_raw(extension_id, request),
1100        }
1101    }
1102
1103    /// Call a window extension with serialized payload.
1104    pub fn window_extension<I, O>(&self, extension_id: ApiExtensionId, request: &I) -> Result<std::result::Result<O, ApiExtensionRecvError>>
1105    where
1106        I: serde::Serialize,
1107        O: serde::de::DeserializeOwned,
1108    {
1109        match self {
1110            ViewWindowOrHeadless::Window(w) => w.window_extension(extension_id, request),
1111            ViewWindowOrHeadless::Headless(h) => h.window_extension(extension_id, request),
1112        }
1113    }
1114}
1115impl From<ViewWindow> for ViewWindowOrHeadless {
1116    fn from(w: ViewWindow) -> Self {
1117        ViewWindowOrHeadless::Window(w)
1118    }
1119}
1120impl From<ViewHeadless> for ViewWindowOrHeadless {
1121    fn from(w: ViewHeadless) -> Self {
1122        ViewWindowOrHeadless::Headless(w)
1123    }
1124}
1125
1126#[derive(Debug)]
1127struct ViewAudioOutputData {
1128    app_id: AppId,
1129    id: ApiAudioOutputId,
1130    generation: ViewProcessGen,
1131    data: AudioOutputOpenData,
1132}
1133impl ViewAudioOutputData {
1134    fn call<R>(&self, f: impl FnOnce(ApiAudioOutputId, &mut Controller) -> Result<R>) -> Result<R> {
1135        let mut app = VIEW_PROCESS.handle_write(self.app_id);
1136        if app.check_generation() {
1137            Err(ChannelError::disconnected())
1138        } else {
1139            f(self.id, &mut app.process)
1140        }
1141    }
1142}
1143impl Drop for ViewAudioOutputData {
1144    fn drop(&mut self) {
1145        if VIEW_PROCESS.is_available() {
1146            let mut app = VIEW_PROCESS.handle_write(self.app_id);
1147            if self.generation == app.process.generation() {
1148                let _ = app.process.close_audio_output(self.id);
1149            }
1150        }
1151    }
1152}
1153
1154/// Handle to an audio output stream in the View Process.
1155///
1156/// The stream is disposed when all clones of the handle are dropped.
1157#[derive(Clone, Debug, PartialEq, Eq)]
1158#[must_use = "the audio output is disposed when all clones of the handle are dropped"]
1159pub struct ViewAudioOutput(ArcEq<ViewAudioOutputData>);
1160impl ViewAudioOutput {
1161    /// Play or enqueue audio.
1162    pub fn cue(&self, mix: AudioMix) -> Result<AudioPlayId> {
1163        self.0.call(|id, p| p.cue_audio(AudioPlayRequest::new(id, mix)))
1164    }
1165
1166    /// Update state, volume, speed.
1167    pub fn update(&self, cfg: AudioOutputConfig) -> Result<()> {
1168        self.0.call(|id, p| p.update_audio_output(AudioOutputUpdateRequest::new(id, cfg)))
1169    }
1170
1171    /// Audio output stream data.
1172    pub fn data(&self) -> &AudioOutputOpenData {
1173        &self.0.data
1174    }
1175
1176    /// Create a weak reference to this audio output.
1177    pub fn downgrade(&self) -> WeakViewAudioOutput {
1178        WeakViewAudioOutput(ArcEq::downgrade(&self.0))
1179    }
1180}
1181/// Weak reference to a [`ViewAudioOutput`].
1182#[derive(Clone, Debug, PartialEq, Eq)]
1183pub struct WeakViewAudioOutput(WeakEq<ViewAudioOutputData>);
1184impl WeakViewAudioOutput {
1185    /// Create a strong reference to the audio output, if it is still open.
1186    pub fn upgrade(&self) -> Option<ViewAudioOutput> {
1187        let d = self.0.upgrade()?;
1188        if d.generation == VIEW_PROCESS.generation() {
1189            Some(ViewAudioOutput(d))
1190        } else {
1191            None
1192        }
1193    }
1194}
1195
1196#[derive(Debug)]
1197struct ViewWindowData {
1198    app_id: AppId,
1199    id: ApiWindowId,
1200    generation: ViewProcessGen,
1201}
1202impl ViewWindowData {
1203    fn call<R>(&self, f: impl FnOnce(ApiWindowId, &mut Controller) -> Result<R>) -> Result<R> {
1204        let mut app = VIEW_PROCESS.handle_write(self.app_id);
1205        if app.check_generation() {
1206            Err(ChannelError::disconnected())
1207        } else {
1208            f(self.id, &mut app.process)
1209        }
1210    }
1211}
1212impl Drop for ViewWindowData {
1213    fn drop(&mut self) {
1214        if VIEW_PROCESS.is_available() {
1215            let mut app = VIEW_PROCESS.handle_write(self.app_id);
1216            if self.generation == app.process.generation() {
1217                let _ = app.process.close(self.id);
1218            }
1219        }
1220    }
1221}
1222
1223type Result<T> = std::result::Result<T, ChannelError>;
1224
1225/// Handle to a headless surface/document open in the View Process.
1226///
1227/// The view is disposed when all clones of the handle are dropped.
1228#[derive(Clone, Debug, PartialEq, Eq)]
1229#[must_use = "the view is disposed when all clones of the handle are dropped"]
1230pub struct ViewHeadless(ArcEq<ViewWindowData>);
1231impl ViewHeadless {
1232    /// Resize the headless surface.
1233    pub fn set_size(&self, size: DipSize, scale_factor: Factor) -> Result<()> {
1234        self.0.call(|id, p| p.set_headless_size(id, size, scale_factor))
1235    }
1236
1237    /// Reference the window renderer.
1238    pub fn renderer(&self) -> ViewRenderer {
1239        ViewRenderer(ArcEq::downgrade(&self.0))
1240    }
1241
1242    /// Call a window extension with custom encoded payload.
1243    pub fn window_extension_raw(&self, extension_id: ApiExtensionId, request: ApiExtensionPayload) -> Result<ApiExtensionPayload> {
1244        self.0.call(|id, p| p.window_extension(id, extension_id, request))
1245    }
1246
1247    /// Call a window extension with serialized payload.
1248    pub fn window_extension<I, O>(&self, extension_id: ApiExtensionId, request: &I) -> Result<std::result::Result<O, ApiExtensionRecvError>>
1249    where
1250        I: serde::Serialize,
1251        O: serde::de::DeserializeOwned,
1252    {
1253        let r = self.window_extension_raw(extension_id, ApiExtensionPayload::serialize(&request).unwrap())?;
1254        Ok(r.deserialize())
1255    }
1256
1257    /// Create a weak reference to this handle.
1258    pub fn downgrade(&self) -> WeakViewHeadless {
1259        WeakViewHeadless(ArcEq::downgrade(&self.0))
1260    }
1261}
1262
1263/// Weak reference to a [`ViewHeadless`] handle.
1264#[derive(Clone, Debug)]
1265pub struct WeakViewHeadless(WeakEq<ViewWindowData>);
1266impl PartialEq for WeakViewHeadless {
1267    fn eq(&self, other: &Self) -> bool {
1268        sync::Weak::ptr_eq(&self.0, &other.0)
1269    }
1270}
1271impl WeakViewHeadless {
1272    /// Create a strong reference to the headless surface, if it is still alive.
1273    pub fn upgrade(&self) -> Option<ViewHeadless> {
1274        let d = self.0.upgrade()?;
1275        if d.generation == VIEW_PROCESS.generation() {
1276            Some(ViewHeadless(d))
1277        } else {
1278            None
1279        }
1280    }
1281}
1282
1283/// Weak handle to a window or view.
1284///
1285/// This is only a weak reference, every method returns [`ChannelError::disconnected`] if the
1286/// window is closed or view is disposed.
1287#[derive(Clone, Debug, PartialEq, Eq)]
1288pub struct ViewRenderer(WeakEq<ViewWindowData>);
1289impl ViewRenderer {
1290    fn call<R>(&self, f: impl FnOnce(ApiWindowId, &mut Controller) -> Result<R>) -> Result<R> {
1291        if let Some(c) = self.0.upgrade() {
1292            c.call(f)
1293        } else {
1294            Err(ChannelError::disconnected())
1295        }
1296    }
1297
1298    /// Returns the view-process generation on which the renderer was created.
1299    pub fn generation(&self) -> Result<ViewProcessGen> {
1300        self.0.upgrade().map(|c| c.generation).ok_or(ChannelError::disconnected())
1301    }
1302
1303    /// Use an image resource in the window renderer.
1304    ///
1305    /// Returns the image texture ID.
1306    pub fn use_image(&self, image: &ViewImageHandle) -> Result<ImageTextureId> {
1307        self.call(|id, p| {
1308            if let Some(img) = &image.0 {
1309                if p.generation() == img.1 {
1310                    p.use_image(id, img.2)
1311                } else {
1312                    Err(ChannelError::disconnected())
1313                }
1314            } else {
1315                Ok(ImageTextureId::INVALID)
1316            }
1317        })
1318    }
1319
1320    /// Replace the image resource in the window renderer.
1321    ///
1322    /// The new `image` handle must represent an image with same dimensions and format as the previous. If the
1323    /// image cannot be updated an error is logged and `false` is returned.
1324    ///
1325    /// The `dirty_rect` can be set to optimize texture upload to the GPU, if not set the entire image region updates.
1326    pub fn update_image_use(&mut self, tex_id: ImageTextureId, image: &ViewImageHandle, dirty_rect: Option<PxRect>) -> Result<bool> {
1327        self.call(|id, p| {
1328            if let Some(img) = &image.0 {
1329                if p.generation() == img.1 {
1330                    p.update_image_use(id, tex_id, img.2, dirty_rect)
1331                } else {
1332                    Err(ChannelError::disconnected())
1333                }
1334            } else {
1335                Ok(false)
1336            }
1337        })
1338    }
1339
1340    /// Delete the image resource in the window renderer.
1341    pub fn delete_image_use(&mut self, tex_id: ImageTextureId) -> Result<()> {
1342        self.call(|id, p| p.delete_image_use(id, tex_id))
1343    }
1344
1345    /// Add a raw font resource to the window renderer.
1346    ///
1347    /// Returns the new font face ID, unique for this renderer.
1348    pub fn add_font_face(&self, bytes: IpcFontBytes, index: u32) -> Result<FontFaceId> {
1349        self.call(|id, p| p.add_font_face(id, bytes, index))
1350    }
1351
1352    /// Delete the font resource in the window renderer.
1353    pub fn delete_font_face(&self, font_face_id: FontFaceId) -> Result<()> {
1354        self.call(|id, p| p.delete_font_face(id, font_face_id))
1355    }
1356
1357    /// Add a sized font to the window renderer.
1358    ///
1359    /// Returns the new font ID, unique for this renderer.
1360    pub fn add_font(
1361        &self,
1362        font_face_id: FontFaceId,
1363        glyph_size: Px,
1364        options: FontOptions,
1365        variations: Vec<(FontVariationName, f32)>,
1366    ) -> Result<FontId> {
1367        self.call(|id, p| p.add_font(id, font_face_id, glyph_size, options, variations))
1368    }
1369
1370    /// Delete the sized font.
1371    pub fn delete_font(&self, font_id: FontId) -> Result<()> {
1372        self.call(|id, p| p.delete_font(id, font_id))
1373    }
1374
1375    /// Create a new image resource from the current rendered frame.
1376    pub fn frame_image(&self, mask: Option<ImageMaskMode>) -> Result<ViewImageHandle> {
1377        if let Some(c) = self.0.upgrade() {
1378            let id = c.call(|id, p| p.frame_image(id, mask))?;
1379            Ok(Self::add_frame_image(c.app_id, id))
1380        } else {
1381            Err(ChannelError::disconnected())
1382        }
1383    }
1384
1385    /// Create a new image resource from a selection of the current rendered frame.
1386    pub fn frame_image_rect(&self, rect: PxRect, mask: Option<ImageMaskMode>) -> Result<ViewImageHandle> {
1387        if let Some(c) = self.0.upgrade() {
1388            let id = c.call(|id, p| p.frame_image_rect(id, rect, mask))?;
1389            Ok(Self::add_frame_image(c.app_id, id))
1390        } else {
1391            Err(ChannelError::disconnected())
1392        }
1393    }
1394
1395    fn add_frame_image(app_id: AppId, id: ImageId) -> ViewImageHandle {
1396        if id == ImageId::INVALID {
1397            ViewImageHandle::dummy()
1398        } else {
1399            let mut app = VIEW_PROCESS.handle_write(app_id);
1400            let handle = ArcEq::new((APP.id().unwrap(), app.process.generation(), id));
1401            app.loading_images.push(ArcEq::downgrade(&handle));
1402
1403            ViewImageHandle(Some(handle))
1404        }
1405    }
1406
1407    /// Render a new frame.
1408    pub fn render(&self, frame: FrameRequest) -> Result<()> {
1409        if let Some(w) = self.0.upgrade() {
1410            w.call(|id, p| p.render(id, frame))?;
1411            VIEW_PROCESS.handle_write(w.app_id).pending_frames += 1;
1412            Ok(())
1413        } else {
1414            Err(ChannelError::disconnected())
1415        }
1416    }
1417
1418    /// Update the current frame and re-render it.
1419    pub fn render_update(&self, frame: FrameUpdateRequest) -> Result<()> {
1420        if let Some(w) = self.0.upgrade() {
1421            w.call(|id, p| p.render_update(id, frame))?;
1422            VIEW_PROCESS.handle_write(w.app_id).pending_frames += 1;
1423            Ok(())
1424        } else {
1425            Err(ChannelError::disconnected())
1426        }
1427    }
1428
1429    /// Call a render extension with custom encoded payload.
1430    pub fn render_extension_raw(&self, extension_id: ApiExtensionId, request: ApiExtensionPayload) -> Result<ApiExtensionPayload> {
1431        if let Some(w) = self.0.upgrade() {
1432            w.call(|id, p| p.render_extension(id, extension_id, request))
1433        } else {
1434            Err(ChannelError::disconnected())
1435        }
1436    }
1437
1438    /// Call a render extension with serialized payload.
1439    pub fn render_extension<I, O>(&self, extension_id: ApiExtensionId, request: &I) -> Result<std::result::Result<O, ApiExtensionRecvError>>
1440    where
1441        I: serde::Serialize,
1442        O: serde::de::DeserializeOwned,
1443    {
1444        let r = self.render_extension_raw(extension_id, ApiExtensionPayload::serialize(&request).unwrap())?;
1445        Ok(r.deserialize())
1446    }
1447}
1448
1449type ViewImageHandleData = (AppId, ViewProcessGen, ImageId);
1450
1451/// Handle to an image loading or loaded in the View Process.
1452///
1453/// The image is disposed when all clones of the handle are dropped.
1454#[must_use = "the image is disposed when all clones of the handle are dropped"]
1455#[derive(Clone, Debug, PartialEq, Eq)]
1456pub struct ViewImageHandle(Option<ArcEq<ViewImageHandleData>>);
1457impl ViewImageHandle {
1458    /// New handle to nothing.
1459    pub fn dummy() -> Self {
1460        ViewImageHandle(None)
1461    }
1462
1463    /// Is handle to nothing.
1464    pub fn is_dummy(&self) -> bool {
1465        self.0.is_none()
1466    }
1467
1468    /// Image ID.
1469    ///
1470    /// Is [`ImageId::INVALID`] for dummy.
1471    pub fn image_id(&self) -> ImageId {
1472        self.0.as_ref().map(|h| h.2).unwrap_or(ImageId::INVALID)
1473    }
1474
1475    /// Application that requested this image.
1476    ///
1477    /// Images can only be used in the same app.
1478    ///
1479    /// Is `None` for dummy.
1480    pub fn app_id(&self) -> Option<AppId> {
1481        self.0.as_ref().map(|h| h.0.0)
1482    }
1483
1484    /// View-process generation that provided this image.
1485    ///
1486    /// Images can only be used in the same view-process instance.
1487    ///
1488    /// Is [`ViewProcessGen::INVALID`] for dummy.
1489    pub fn view_process_gen(&self) -> ViewProcessGen {
1490        self.0.as_ref().map(|h| h.1).unwrap_or(ViewProcessGen::INVALID)
1491    }
1492
1493    /// Create a weak reference to this handle.
1494    pub fn downgrade(&self) -> WeakViewImageHandle {
1495        match &self.0 {
1496            Some(h) => WeakViewImageHandle(ArcEq::downgrade(h)),
1497            None => WeakViewImageHandle(WeakEq::new()),
1498        }
1499    }
1500}
1501impl Drop for ViewImageHandle {
1502    fn drop(&mut self) {
1503        if let Some(h) = self.0.take()
1504            && Arc::strong_count(&h) == 1
1505            && let Some(app) = APP.id()
1506        {
1507            if h.0.0 != app {
1508                tracing::error!("image from app `{:?}` dropped in app `{:?}`", h.0, app);
1509                return;
1510            }
1511
1512            if VIEW_PROCESS.is_available() && VIEW_PROCESS.generation() == h.1 {
1513                let _ = VIEW_PROCESS.write().process.forget_image(h.2);
1514            }
1515        }
1516    }
1517}
1518/// Connection to an image loading or loaded in the View Process.
1519///
1520/// The image is removed from the View Process cache when all clones of [`ViewImageHandle`] drops, but
1521/// if there is another image pointer holding the image, this weak pointer can be upgraded back
1522/// to a strong connection to the image.
1523///
1524/// Dummy handles never upgrade back.
1525#[derive(Clone, Debug)]
1526pub struct WeakViewImageHandle(WeakEq<ViewImageHandleData>);
1527impl PartialEq for WeakViewImageHandle {
1528    fn eq(&self, other: &Self) -> bool {
1529        sync::Weak::ptr_eq(&self.0, &other.0)
1530    }
1531}
1532impl WeakViewImageHandle {
1533    /// Attempt to upgrade the weak pointer to the image to a full image.
1534    ///
1535    /// Returns `Some` if the is at least another [`ViewImageHandle`] holding the image alive.
1536    pub fn upgrade(&self) -> Option<ViewImageHandle> {
1537        let d = self.0.upgrade()?;
1538        if d.1 == VIEW_PROCESS.generation() {
1539            Some(ViewImageHandle(Some(d)))
1540        } else {
1541            None
1542        }
1543    }
1544}
1545
1546/// Error returned by [`VIEW_PROCESS::encode_image`].
1547#[derive(Debug, Clone, PartialEq, Eq)]
1548#[non_exhaustive]
1549pub enum EncodeError {
1550    /// Encode error.
1551    Encode(Txt),
1552    /// Attempted to encode dummy image.
1553    ///
1554    /// In a headless-app without renderer all images are dummy because there is no
1555    /// view-process backend running.
1556    Dummy,
1557    /// Image is still loading, await it first.
1558    Loading,
1559    /// The View-Process disconnected or has not finished initializing yet, try again after [`VIEW_PROCESS_INITED_EVENT`].
1560    Disconnected,
1561}
1562impl From<Txt> for EncodeError {
1563    fn from(e: Txt) -> Self {
1564        EncodeError::Encode(e)
1565    }
1566}
1567impl From<ChannelError> for EncodeError {
1568    fn from(_: ChannelError) -> Self {
1569        EncodeError::Disconnected
1570    }
1571}
1572impl fmt::Display for EncodeError {
1573    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1574        match self {
1575            EncodeError::Encode(e) => write!(f, "{e}"),
1576            EncodeError::Dummy => write!(f, "cannot encode dummy image"),
1577            EncodeError::Loading => write!(f, "cannot encode, image is still loading"),
1578            EncodeError::Disconnected => write!(f, "{}", ChannelError::disconnected()),
1579        }
1580    }
1581}
1582impl std::error::Error for EncodeError {}
1583
1584struct EncodeRequest {
1585    task_id: ImageEncodeId,
1586    listener: channel::Sender<std::result::Result<IpcBytes, EncodeError>>,
1587}
1588
1589type ClipboardResult<T> = std::result::Result<T, ClipboardError>;
1590
1591/// View-process clipboard methods.
1592#[non_exhaustive]
1593pub struct ViewClipboard {}
1594impl ViewClipboard {
1595    /// Read [`ClipboardType::Text`].
1596    ///
1597    /// [`ClipboardType::Text`]: zng_view_api::clipboard::ClipboardType::Text
1598    pub fn read_text(&self) -> Result<ClipboardResult<Txt>> {
1599        match VIEW_PROCESS
1600            .try_write()?
1601            .process
1602            .read_clipboard(vec![ClipboardType::Text], true)?
1603            .map(|mut r| r.pop())
1604        {
1605            Ok(Some(ClipboardData::Text(t))) => Ok(Ok(t)),
1606            Err(e) => Ok(Err(e)),
1607            _ => Ok(Err(ClipboardError::Other(Txt::from_static("view-process returned incorrect type")))),
1608        }
1609    }
1610
1611    /// Write [`ClipboardType::Text`].
1612    ///
1613    /// [`ClipboardType::Text`]: zng_view_api::clipboard::ClipboardType::Text
1614    pub fn write_text(&self, txt: Txt) -> Result<ClipboardResult<()>> {
1615        VIEW_PROCESS
1616            .try_write()?
1617            .process
1618            .write_clipboard(vec![ClipboardData::Text(txt)])
1619            .map(|r| r.map(|_| ()))
1620    }
1621
1622    /// Read [`ClipboardType::Image`].
1623    ///
1624    /// [`ClipboardType::Image`]: zng_view_api::clipboard::ClipboardType::Image
1625    pub fn read_image(&self) -> Result<ClipboardResult<ViewImageHandle>> {
1626        let mut app = VIEW_PROCESS.try_write()?;
1627        match app.process.read_clipboard(vec![ClipboardType::Image], true)?.map(|mut r| r.pop()) {
1628            Ok(Some(ClipboardData::Image(id))) => {
1629                if id == ImageId::INVALID {
1630                    Ok(Err(ClipboardError::Other(Txt::from_static("view-process returned invalid image"))))
1631                } else {
1632                    let handle = ArcEq::new((APP.id().unwrap(), app.process.generation(), id));
1633                    app.loading_images.push(ArcEq::downgrade(&handle));
1634                    Ok(Ok(ViewImageHandle(Some(handle))))
1635                }
1636            }
1637            Err(e) => Ok(Err(e)),
1638            _ => Ok(Err(ClipboardError::Other(Txt::from_static("view-process returned incorrect type")))),
1639        }
1640    }
1641
1642    /// Write [`ClipboardType::Image`].
1643    ///
1644    /// [`ClipboardType::Image`]: zng_view_api::clipboard::ClipboardType::Image
1645    pub fn write_image(&self, img: &ViewImageHandle) -> Result<ClipboardResult<()>> {
1646        return VIEW_PROCESS
1647            .try_write()?
1648            .process
1649            .write_clipboard(vec![ClipboardData::Image(img.image_id())])
1650            .map(|r| r.map(|_| ()));
1651    }
1652
1653    /// Read [`ClipboardType::Paths`].
1654    ///
1655    /// [`ClipboardType::Paths`]: zng_view_api::clipboard::ClipboardType::Paths
1656    pub fn read_paths(&self) -> Result<ClipboardResult<Vec<PathBuf>>> {
1657        match VIEW_PROCESS
1658            .try_write()?
1659            .process
1660            .read_clipboard(vec![ClipboardType::Paths], true)?
1661            .map(|mut r| r.pop())
1662        {
1663            Ok(Some(ClipboardData::Paths(f))) => Ok(Ok(f)),
1664            Err(e) => Ok(Err(e)),
1665            _ => Ok(Err(ClipboardError::Other(Txt::from_static("view-process returned incorrect type")))),
1666        }
1667    }
1668
1669    /// Write [`ClipboardType::Paths`].
1670    ///
1671    /// [`ClipboardType::Paths`]: zng_view_api::clipboard::ClipboardType::Paths
1672    pub fn write_paths(&self, list: Vec<PathBuf>) -> Result<ClipboardResult<()>> {
1673        VIEW_PROCESS
1674            .try_write()?
1675            .process
1676            .write_clipboard(vec![ClipboardData::Paths(list)])
1677            .map(|r| r.map(|_| ()))
1678    }
1679
1680    /// Read [`ClipboardType::Extension`].
1681    ///
1682    /// [`ClipboardType::Extension`]: zng_view_api::clipboard::ClipboardType::Extension
1683    pub fn read_extension(&self, data_type: Txt) -> Result<ClipboardResult<IpcBytes>> {
1684        match VIEW_PROCESS
1685            .try_write()?
1686            .process
1687            .read_clipboard(vec![ClipboardType::Extension(data_type.clone())], true)?
1688            .map(|mut r| r.pop())
1689        {
1690            Ok(Some(ClipboardData::Extension { data_type: rt, data })) if rt == data_type => Ok(Ok(data)),
1691            Err(e) => Ok(Err(e)),
1692            _ => Ok(Err(ClipboardError::Other(Txt::from_static("view-process returned incorrect type")))),
1693        }
1694    }
1695
1696    /// Write [`ClipboardType::Extension`].
1697    ///
1698    /// [`ClipboardType::Extension`]: zng_view_api::clipboard::ClipboardType::Extension
1699    pub fn write_extension(&self, data_type: Txt, data: IpcBytes) -> Result<ClipboardResult<()>> {
1700        VIEW_PROCESS
1701            .try_write()?
1702            .process
1703            .write_clipboard(vec![ClipboardData::Extension { data_type, data }])
1704            .map(|r| r.map(|_| ()))
1705    }
1706}
1707
1708type ViewAudioHandleData = (AppId, ViewProcessGen, AudioId);
1709
1710/// Handle to an audio loading or loaded in the View Process.
1711///
1712/// The audio is disposed when all clones of the handle are dropped.
1713#[must_use = "the audio is disposed when all clones of the handle are dropped"]
1714#[derive(Clone, Debug, PartialEq, Eq)]
1715pub struct ViewAudioHandle(Option<ArcEq<ViewAudioHandleData>>);
1716impl ViewAudioHandle {
1717    /// New handle to nothing.
1718    pub fn dummy() -> Self {
1719        ViewAudioHandle(None)
1720    }
1721
1722    /// Is handle to nothing.
1723    pub fn is_dummy(&self) -> bool {
1724        self.0.is_none()
1725    }
1726
1727    /// Audio ID.
1728    ///
1729    /// Is [`AudioId::INVALID`] for dummy.
1730    pub fn audio_id(&self) -> AudioId {
1731        self.0.as_ref().map(|h| h.2).unwrap_or(AudioId::INVALID)
1732    }
1733
1734    /// Application that requested this image.
1735    ///
1736    /// Audios can only be used in the same app.
1737    ///
1738    /// Is `None` for dummy.
1739    pub fn app_id(&self) -> Option<AppId> {
1740        self.0.as_ref().map(|h| h.0.0)
1741    }
1742
1743    /// View-process generation that provided this image.
1744    ///
1745    /// Audios can only be used in the same view-process instance.
1746    ///
1747    /// Is [`ViewProcessGen::INVALID`] for dummy.
1748    pub fn view_process_gen(&self) -> ViewProcessGen {
1749        self.0.as_ref().map(|h| h.1).unwrap_or(ViewProcessGen::INVALID)
1750    }
1751
1752    /// Create a weak reference to this audio.
1753    pub fn downgrade(&self) -> WeakViewAudioHandle {
1754        match &self.0 {
1755            Some(h) => WeakViewAudioHandle(ArcEq::downgrade(h)),
1756            None => WeakViewAudioHandle(WeakEq::new()),
1757        }
1758    }
1759}
1760impl Drop for ViewAudioHandle {
1761    fn drop(&mut self) {
1762        if let Some(h) = self.0.take()
1763            && Arc::strong_count(&h) == 1
1764            && let Some(app) = APP.id()
1765        {
1766            if h.0.0 != app {
1767                tracing::error!("audio from app `{:?}` dropped in app `{:?}`", h.0, app);
1768                return;
1769            }
1770
1771            if VIEW_PROCESS.is_available() && VIEW_PROCESS.generation() == h.1 {
1772                let _ = VIEW_PROCESS.write().process.forget_audio(h.2);
1773            }
1774        }
1775    }
1776}
1777/// Connection to an audio loading or loaded in the View Process.
1778///
1779/// The audio is removed from the View Process cache when all clones of [`ViewAudioHandle`] drops, but
1780/// if there is another audio pointer holding it, this weak pointer can be upgraded back
1781/// to a strong connection to the audio.
1782///
1783/// Dummy handles never upgrade back.
1784#[derive(Clone, Debug, PartialEq, Eq)]
1785pub struct WeakViewAudioHandle(WeakEq<ViewAudioHandleData>);
1786impl WeakViewAudioHandle {
1787    /// Attempt to upgrade the weak pointer to the audio to a full audio.
1788    ///
1789    /// Returns `Some` if the is at least another [`ViewAudioHandle`] holding the audio alive.
1790    pub fn upgrade(&self) -> Option<ViewAudioHandle> {
1791        let h = self.0.upgrade()?;
1792        if h.1 == VIEW_PROCESS.generation() {
1793            Some(ViewAudioHandle(Some(h)))
1794        } else {
1795            None
1796        }
1797    }
1798}
1799
1800zng_unique_id::unique_id_32! {
1801    /// Unique identifier of an open audio output.
1802    ///
1803    /// # Name
1804    ///
1805    /// IDs are only unique for the same process.
1806    /// You can associate a [`name`] with an ID to give it a persistent identifier.
1807    ///
1808    /// [`name`]: AudioOutputId::name
1809    pub struct AudioOutputId;
1810}
1811zng_unique_id::impl_unique_id_name!(AudioOutputId);
1812zng_unique_id::impl_unique_id_fmt!(AudioOutputId);
1813zng_unique_id::impl_unique_id_bytemuck!(AudioOutputId);
1814zng_var::impl_from_and_into_var! {
1815    /// Calls [`AudioOutputId::named`].
1816    fn from(name: &'static str) -> AudioOutputId {
1817        AudioOutputId::named(name)
1818    }
1819    /// Calls [`AudioOutputId::named`].
1820    fn from(name: String) -> AudioOutputId {
1821        AudioOutputId::named(name)
1822    }
1823    /// Calls [`AudioOutputId::named`].
1824    fn from(name: std::borrow::Cow<'static, str>) -> AudioOutputId {
1825        AudioOutputId::named(name)
1826    }
1827    /// Calls [`AudioOutputId::named`].
1828    fn from(name: char) -> AudioOutputId {
1829        AudioOutputId::named(name)
1830    }
1831    /// Calls [`AudioOutputId::named`].
1832    fn from(name: Txt) -> AudioOutputId {
1833        AudioOutputId::named(name)
1834    }
1835
1836    fn from(some: AudioOutputId) -> Option<AudioOutputId>;
1837}
1838impl serde::Serialize for AudioOutputId {
1839    fn serialize<S>(&self, serializer: S) -> std::result::Result<S::Ok, S::Error>
1840    where
1841        S: serde::Serializer,
1842    {
1843        let name = self.name();
1844        if name.is_empty() {
1845            use serde::ser::Error;
1846            return Err(S::Error::custom("cannot serialize unnamed `AudioOutputId`"));
1847        }
1848        name.serialize(serializer)
1849    }
1850}
1851impl<'de> serde::Deserialize<'de> for AudioOutputId {
1852    fn deserialize<D>(deserializer: D) -> std::result::Result<Self, D::Error>
1853    where
1854        D: serde::Deserializer<'de>,
1855    {
1856        let name = Txt::deserialize(deserializer)?;
1857        Ok(AudioOutputId::named(name))
1858    }
1859}