Skip to main content

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