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, 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<IpcBytes>) -> 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<IpcBytes>) -> 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    /// Resize the headless surface.
1250    pub fn set_size(&self, size: DipSize, scale_factor: Factor) -> Result<()> {
1251        self.0.call(|id, p| p.set_headless_size(id, size, scale_factor))
1252    }
1253
1254    /// Reference the window renderer.
1255    pub fn renderer(&self) -> ViewRenderer {
1256        ViewRenderer(ArcEq::downgrade(&self.0))
1257    }
1258
1259    /// Call a window extension with custom encoded payload.
1260    pub fn window_extension_raw(&self, extension_id: ApiExtensionId, request: ApiExtensionPayload) -> Result<ApiExtensionPayload> {
1261        self.0.call(|id, p| p.window_extension(id, extension_id, request))
1262    }
1263
1264    /// Call a window extension with serialized payload.
1265    pub fn window_extension<I, O>(&self, extension_id: ApiExtensionId, request: &I) -> Result<std::result::Result<O, ApiExtensionRecvError>>
1266    where
1267        I: serde::Serialize,
1268        O: serde::de::DeserializeOwned,
1269    {
1270        let r = self.window_extension_raw(extension_id, ApiExtensionPayload::serialize(&request).unwrap())?;
1271        Ok(r.deserialize())
1272    }
1273
1274    /// Create a weak reference to this handle.
1275    pub fn downgrade(&self) -> WeakViewHeadless {
1276        WeakViewHeadless(ArcEq::downgrade(&self.0))
1277    }
1278}
1279
1280/// Weak reference to a [`ViewHeadless`] handle.
1281#[derive(Clone, Debug)]
1282pub struct WeakViewHeadless(WeakEq<ViewWindowData>);
1283impl PartialEq for WeakViewHeadless {
1284    fn eq(&self, other: &Self) -> bool {
1285        sync::Weak::ptr_eq(&self.0, &other.0)
1286    }
1287}
1288impl WeakViewHeadless {
1289    /// Create a strong reference to the headless surface, if it is still alive.
1290    pub fn upgrade(&self) -> Option<ViewHeadless> {
1291        let d = self.0.upgrade()?;
1292        if d.generation == VIEW_PROCESS.generation() {
1293            Some(ViewHeadless(d))
1294        } else {
1295            None
1296        }
1297    }
1298}
1299
1300/// Weak handle to a window or view.
1301///
1302/// This is only a weak reference, every method returns [`ChannelError::disconnected`] if the
1303/// window is closed or view is disposed.
1304#[derive(Clone, Debug, PartialEq, Eq)]
1305pub struct ViewRenderer(WeakEq<ViewWindowData>);
1306impl ViewRenderer {
1307    fn call<R>(&self, f: impl FnOnce(ApiWindowId, &mut Controller) -> Result<R>) -> Result<R> {
1308        if let Some(c) = self.0.upgrade() {
1309            c.call(f)
1310        } else {
1311            Err(ChannelError::disconnected())
1312        }
1313    }
1314
1315    /// Returns the view-process generation on which the renderer was created.
1316    pub fn generation(&self) -> Result<ViewProcessGen> {
1317        self.0.upgrade().map(|c| c.generation).ok_or(ChannelError::disconnected())
1318    }
1319
1320    /// Use an image resource in the window renderer.
1321    ///
1322    /// Returns the image texture ID.
1323    pub fn use_image(&self, image: &ViewImageHandle) -> Result<ImageTextureId> {
1324        self.call(|id, p| {
1325            if let Some(img) = &image.0 {
1326                if p.generation() == img.1 {
1327                    p.use_image(id, img.2)
1328                } else {
1329                    Err(ChannelError::disconnected())
1330                }
1331            } else {
1332                Ok(ImageTextureId::INVALID)
1333            }
1334        })
1335    }
1336
1337    /// Replace the image resource in the window renderer.
1338    ///
1339    /// The new `image` handle must represent an image with same dimensions and format as the previous. If the
1340    /// image cannot be updated an error is logged and `false` is returned.
1341    ///
1342    /// The `dirty_rect` can be set to optimize texture upload to the GPU, if not set the entire image region updates.
1343    pub fn update_image_use(&mut self, tex_id: ImageTextureId, image: &ViewImageHandle, dirty_rect: Option<PxRect>) -> Result<bool> {
1344        self.call(|id, p| {
1345            if let Some(img) = &image.0 {
1346                if p.generation() == img.1 {
1347                    p.update_image_use(id, tex_id, img.2, dirty_rect)
1348                } else {
1349                    Err(ChannelError::disconnected())
1350                }
1351            } else {
1352                Ok(false)
1353            }
1354        })
1355    }
1356
1357    /// Delete the image resource in the window renderer.
1358    pub fn delete_image_use(&mut self, tex_id: ImageTextureId) -> Result<()> {
1359        self.call(|id, p| p.delete_image_use(id, tex_id))
1360    }
1361
1362    /// Add a raw font resource to the window renderer.
1363    ///
1364    /// Returns the new font face ID, unique for this renderer.
1365    pub fn add_font_face(&self, bytes: IpcFontBytes, index: u32) -> Result<FontFaceId> {
1366        self.call(|id, p| p.add_font_face(id, bytes, index))
1367    }
1368
1369    /// Delete the font resource in the window renderer.
1370    pub fn delete_font_face(&self, font_face_id: FontFaceId) -> Result<()> {
1371        self.call(|id, p| p.delete_font_face(id, font_face_id))
1372    }
1373
1374    /// Add a sized font to the window renderer.
1375    ///
1376    /// Returns the new font ID, unique for this renderer.
1377    pub fn add_font(
1378        &self,
1379        font_face_id: FontFaceId,
1380        glyph_size: Px,
1381        options: FontOptions,
1382        variations: Vec<(FontVariationName, f32)>,
1383    ) -> Result<FontId> {
1384        self.call(|id, p| p.add_font(id, font_face_id, glyph_size, options, variations))
1385    }
1386
1387    /// Delete the sized font.
1388    pub fn delete_font(&self, font_id: FontId) -> Result<()> {
1389        self.call(|id, p| p.delete_font(id, font_id))
1390    }
1391
1392    /// Create a new image resource from the current rendered frame.
1393    pub fn frame_image(&self, mask: Option<ImageMaskMode>) -> Result<ViewImageHandle> {
1394        if let Some(c) = self.0.upgrade() {
1395            let id = c.call(|id, p| p.frame_image(id, mask))?;
1396            Ok(Self::add_frame_image(c.app_id, id))
1397        } else {
1398            Err(ChannelError::disconnected())
1399        }
1400    }
1401
1402    /// Create a new image resource from a selection of the current rendered frame.
1403    pub fn frame_image_rect(&self, rect: PxRect, mask: Option<ImageMaskMode>) -> Result<ViewImageHandle> {
1404        if let Some(c) = self.0.upgrade() {
1405            let id = c.call(|id, p| p.frame_image_rect(id, rect, mask))?;
1406            Ok(Self::add_frame_image(c.app_id, id))
1407        } else {
1408            Err(ChannelError::disconnected())
1409        }
1410    }
1411
1412    fn add_frame_image(app_id: AppId, id: ImageId) -> ViewImageHandle {
1413        if id == ImageId::INVALID {
1414            ViewImageHandle::dummy()
1415        } else {
1416            let mut app = VIEW_PROCESS.handle_write(app_id);
1417            let handle = ArcEq::new((APP.id().unwrap(), app.process.generation(), id));
1418            app.loading_images.push(ArcEq::downgrade(&handle));
1419
1420            ViewImageHandle(Some(handle))
1421        }
1422    }
1423
1424    /// Render a new frame.
1425    pub fn render(&self, frame: FrameRequest) -> Result<()> {
1426        if let Some(w) = self.0.upgrade() {
1427            w.call(|id, p| p.render(id, frame))?;
1428            *VIEW_PROCESS
1429                .handle_write(w.app_id)
1430                .pending_frames
1431                .entry(WindowId::from_raw(w.id.get()))
1432                .or_default() += 1;
1433            Ok(())
1434        } else {
1435            Err(ChannelError::disconnected())
1436        }
1437    }
1438
1439    /// Update the current frame and re-render it.
1440    pub fn render_update(&self, frame: FrameUpdateRequest) -> Result<()> {
1441        if let Some(w) = self.0.upgrade() {
1442            w.call(|id, p| p.render_update(id, frame))?;
1443            *VIEW_PROCESS
1444                .handle_write(w.app_id)
1445                .pending_frames
1446                .entry(WindowId::from_raw(w.id.get()))
1447                .or_default() += 1;
1448            Ok(())
1449        } else {
1450            Err(ChannelError::disconnected())
1451        }
1452    }
1453
1454    /// Call a render extension with custom encoded payload.
1455    pub fn render_extension_raw(&self, extension_id: ApiExtensionId, request: ApiExtensionPayload) -> Result<ApiExtensionPayload> {
1456        if let Some(w) = self.0.upgrade() {
1457            w.call(|id, p| p.render_extension(id, extension_id, request))
1458        } else {
1459            Err(ChannelError::disconnected())
1460        }
1461    }
1462
1463    /// Call a render extension with serialized payload.
1464    pub fn render_extension<I, O>(&self, extension_id: ApiExtensionId, request: &I) -> Result<std::result::Result<O, ApiExtensionRecvError>>
1465    where
1466        I: serde::Serialize,
1467        O: serde::de::DeserializeOwned,
1468    {
1469        let r = self.render_extension_raw(extension_id, ApiExtensionPayload::serialize(&request).unwrap())?;
1470        Ok(r.deserialize())
1471    }
1472}
1473
1474type ViewImageHandleData = (AppId, ViewProcessGen, ImageId);
1475
1476/// Handle to an image loading or loaded in the View Process.
1477///
1478/// The image is disposed when all clones of the handle are dropped.
1479#[must_use = "the image is disposed when all clones of the handle are dropped"]
1480#[derive(Clone, Debug, PartialEq, Eq)]
1481pub struct ViewImageHandle(Option<ArcEq<ViewImageHandleData>>);
1482impl ViewImageHandle {
1483    /// New handle to nothing.
1484    pub fn dummy() -> Self {
1485        ViewImageHandle(None)
1486    }
1487
1488    /// Is handle to nothing.
1489    pub fn is_dummy(&self) -> bool {
1490        self.0.is_none()
1491    }
1492
1493    /// Image ID.
1494    ///
1495    /// Is [`ImageId::INVALID`] for dummy.
1496    pub fn image_id(&self) -> ImageId {
1497        self.0.as_ref().map(|h| h.2).unwrap_or(ImageId::INVALID)
1498    }
1499
1500    /// Application that requested this image.
1501    ///
1502    /// Images can only be used in the same app.
1503    ///
1504    /// Is `None` for dummy.
1505    pub fn app_id(&self) -> Option<AppId> {
1506        self.0.as_ref().map(|h| h.0.0)
1507    }
1508
1509    /// View-process generation that provided this image.
1510    ///
1511    /// Images can only be used in the same view-process instance.
1512    ///
1513    /// Is [`ViewProcessGen::INVALID`] for dummy.
1514    pub fn view_process_gen(&self) -> ViewProcessGen {
1515        self.0.as_ref().map(|h| h.1).unwrap_or(ViewProcessGen::INVALID)
1516    }
1517
1518    /// Create a weak reference to this handle.
1519    pub fn downgrade(&self) -> WeakViewImageHandle {
1520        match &self.0 {
1521            Some(h) => WeakViewImageHandle(ArcEq::downgrade(h)),
1522            None => WeakViewImageHandle(WeakEq::new()),
1523        }
1524    }
1525}
1526impl Drop for ViewImageHandle {
1527    fn drop(&mut self) {
1528        if let Some(h) = self.0.take()
1529            && Arc::strong_count(&h) == 1
1530            && let Some(app) = APP.id()
1531        {
1532            if h.0.0 != app {
1533                tracing::error!("image from app `{:?}` dropped in app `{:?}`", h.0, app);
1534                return;
1535            }
1536
1537            if VIEW_PROCESS.is_available() && VIEW_PROCESS.generation() == h.1 {
1538                let _ = VIEW_PROCESS.write().process.forget_image(h.2);
1539            }
1540        }
1541    }
1542}
1543/// Connection to an image loading or loaded in the View Process.
1544///
1545/// The image is removed from the View Process cache when all clones of [`ViewImageHandle`] drops, but
1546/// if there is another image pointer holding the image, this weak pointer can be upgraded back
1547/// to a strong connection to the image.
1548///
1549/// Dummy handles never upgrade back.
1550#[derive(Clone, Debug)]
1551pub struct WeakViewImageHandle(WeakEq<ViewImageHandleData>);
1552impl PartialEq for WeakViewImageHandle {
1553    fn eq(&self, other: &Self) -> bool {
1554        sync::Weak::ptr_eq(&self.0, &other.0)
1555    }
1556}
1557impl WeakViewImageHandle {
1558    /// Attempt to upgrade the weak pointer to the image to a full image.
1559    ///
1560    /// Returns `Some` if the is at least another [`ViewImageHandle`] holding the image alive.
1561    pub fn upgrade(&self) -> Option<ViewImageHandle> {
1562        let d = self.0.upgrade()?;
1563        if d.1 == VIEW_PROCESS.generation() {
1564            Some(ViewImageHandle(Some(d)))
1565        } else {
1566            None
1567        }
1568    }
1569}
1570
1571/// Error returned by [`VIEW_PROCESS::encode_image`].
1572#[derive(Debug, Clone, PartialEq, Eq)]
1573#[non_exhaustive]
1574pub enum EncodeError {
1575    /// Encode error.
1576    Encode(Txt),
1577    /// Attempted to encode dummy image.
1578    ///
1579    /// In a headless-app without renderer all images are dummy because there is no
1580    /// view-process backend running.
1581    Dummy,
1582    /// Image is still loading, await it first.
1583    Loading,
1584    /// The View-Process disconnected or has not finished initializing yet, try again after [`VIEW_PROCESS_INITED_EVENT`].
1585    Disconnected,
1586}
1587impl From<Txt> for EncodeError {
1588    fn from(e: Txt) -> Self {
1589        EncodeError::Encode(e)
1590    }
1591}
1592impl From<ChannelError> for EncodeError {
1593    fn from(_: ChannelError) -> Self {
1594        EncodeError::Disconnected
1595    }
1596}
1597impl fmt::Display for EncodeError {
1598    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1599        match self {
1600            EncodeError::Encode(e) => write!(f, "{e}"),
1601            EncodeError::Dummy => write!(f, "cannot encode dummy image"),
1602            EncodeError::Loading => write!(f, "cannot encode, image is still loading"),
1603            EncodeError::Disconnected => write!(f, "{}", ChannelError::disconnected()),
1604        }
1605    }
1606}
1607impl std::error::Error for EncodeError {}
1608
1609struct EncodeRequest {
1610    task_id: ImageEncodeId,
1611    listener: channel::Sender<std::result::Result<IpcBytes, EncodeError>>,
1612}
1613
1614type ClipboardResult<T> = std::result::Result<T, ClipboardError>;
1615
1616/// View-process clipboard methods.
1617#[non_exhaustive]
1618pub struct ViewClipboard {}
1619impl ViewClipboard {
1620    /// Read [`ClipboardType::Text`].
1621    ///
1622    /// [`ClipboardType::Text`]: zng_view_api::clipboard::ClipboardType::Text
1623    pub fn read_text(&self) -> Result<ClipboardResult<Txt>> {
1624        match VIEW_PROCESS
1625            .try_write()?
1626            .process
1627            .read_clipboard(vec![ClipboardType::Text], true)?
1628            .map(|mut r| r.pop())
1629        {
1630            Ok(Some(ClipboardData::Text(t))) => Ok(Ok(t)),
1631            Err(e) => Ok(Err(e)),
1632            _ => Ok(Err(ClipboardError::Other(Txt::from_static("view-process returned incorrect type")))),
1633        }
1634    }
1635
1636    /// Write [`ClipboardType::Text`].
1637    ///
1638    /// [`ClipboardType::Text`]: zng_view_api::clipboard::ClipboardType::Text
1639    pub fn write_text(&self, txt: Txt) -> Result<ClipboardResult<()>> {
1640        VIEW_PROCESS
1641            .try_write()?
1642            .process
1643            .write_clipboard(vec![ClipboardData::Text(txt)])
1644            .map(|r| r.map(|_| ()))
1645    }
1646
1647    /// Read [`ClipboardType::Image`].
1648    ///
1649    /// [`ClipboardType::Image`]: zng_view_api::clipboard::ClipboardType::Image
1650    pub fn read_image(&self) -> Result<ClipboardResult<ViewImageHandle>> {
1651        let mut app = VIEW_PROCESS.try_write()?;
1652        match app.process.read_clipboard(vec![ClipboardType::Image], true)?.map(|mut r| r.pop()) {
1653            Ok(Some(ClipboardData::Image(id))) => {
1654                if id == ImageId::INVALID {
1655                    Ok(Err(ClipboardError::Other(Txt::from_static("view-process returned invalid image"))))
1656                } else {
1657                    let handle = ArcEq::new((APP.id().unwrap(), app.process.generation(), id));
1658                    app.loading_images.push(ArcEq::downgrade(&handle));
1659                    Ok(Ok(ViewImageHandle(Some(handle))))
1660                }
1661            }
1662            Err(e) => Ok(Err(e)),
1663            _ => Ok(Err(ClipboardError::Other(Txt::from_static("view-process returned incorrect type")))),
1664        }
1665    }
1666
1667    /// Write [`ClipboardType::Image`].
1668    ///
1669    /// [`ClipboardType::Image`]: zng_view_api::clipboard::ClipboardType::Image
1670    pub fn write_image(&self, img: &ViewImageHandle) -> Result<ClipboardResult<()>> {
1671        return VIEW_PROCESS
1672            .try_write()?
1673            .process
1674            .write_clipboard(vec![ClipboardData::Image(img.image_id())])
1675            .map(|r| r.map(|_| ()));
1676    }
1677
1678    /// Read [`ClipboardType::Paths`].
1679    ///
1680    /// [`ClipboardType::Paths`]: zng_view_api::clipboard::ClipboardType::Paths
1681    pub fn read_paths(&self) -> Result<ClipboardResult<Vec<PathBuf>>> {
1682        match VIEW_PROCESS
1683            .try_write()?
1684            .process
1685            .read_clipboard(vec![ClipboardType::Paths], true)?
1686            .map(|mut r| r.pop())
1687        {
1688            Ok(Some(ClipboardData::Paths(f))) => Ok(Ok(f)),
1689            Err(e) => Ok(Err(e)),
1690            _ => Ok(Err(ClipboardError::Other(Txt::from_static("view-process returned incorrect type")))),
1691        }
1692    }
1693
1694    /// Write [`ClipboardType::Paths`].
1695    ///
1696    /// [`ClipboardType::Paths`]: zng_view_api::clipboard::ClipboardType::Paths
1697    pub fn write_paths(&self, list: Vec<PathBuf>) -> Result<ClipboardResult<()>> {
1698        VIEW_PROCESS
1699            .try_write()?
1700            .process
1701            .write_clipboard(vec![ClipboardData::Paths(list)])
1702            .map(|r| r.map(|_| ()))
1703    }
1704
1705    /// Read [`ClipboardType::Extension`].
1706    ///
1707    /// [`ClipboardType::Extension`]: zng_view_api::clipboard::ClipboardType::Extension
1708    pub fn read_extension(&self, data_type: Txt) -> Result<ClipboardResult<IpcBytes>> {
1709        match VIEW_PROCESS
1710            .try_write()?
1711            .process
1712            .read_clipboard(vec![ClipboardType::Extension(data_type.clone())], true)?
1713            .map(|mut r| r.pop())
1714        {
1715            Ok(Some(ClipboardData::Extension { data_type: rt, data })) if rt == data_type => Ok(Ok(data)),
1716            Err(e) => Ok(Err(e)),
1717            _ => Ok(Err(ClipboardError::Other(Txt::from_static("view-process returned incorrect type")))),
1718        }
1719    }
1720
1721    /// Write [`ClipboardType::Extension`].
1722    ///
1723    /// [`ClipboardType::Extension`]: zng_view_api::clipboard::ClipboardType::Extension
1724    pub fn write_extension(&self, data_type: Txt, data: IpcBytes) -> Result<ClipboardResult<()>> {
1725        VIEW_PROCESS
1726            .try_write()?
1727            .process
1728            .write_clipboard(vec![ClipboardData::Extension { data_type, data }])
1729            .map(|r| r.map(|_| ()))
1730    }
1731}
1732
1733type ViewAudioHandleData = (AppId, ViewProcessGen, AudioId);
1734
1735/// Handle to an audio loading or loaded in the View Process.
1736///
1737/// The audio is disposed when all clones of the handle are dropped.
1738#[must_use = "the audio is disposed when all clones of the handle are dropped"]
1739#[derive(Clone, Debug, PartialEq, Eq)]
1740pub struct ViewAudioHandle(Option<ArcEq<ViewAudioHandleData>>);
1741impl ViewAudioHandle {
1742    /// New handle to nothing.
1743    pub fn dummy() -> Self {
1744        ViewAudioHandle(None)
1745    }
1746
1747    /// Is handle to nothing.
1748    pub fn is_dummy(&self) -> bool {
1749        self.0.is_none()
1750    }
1751
1752    /// Audio ID.
1753    ///
1754    /// Is [`AudioId::INVALID`] for dummy.
1755    pub fn audio_id(&self) -> AudioId {
1756        self.0.as_ref().map(|h| h.2).unwrap_or(AudioId::INVALID)
1757    }
1758
1759    /// Application that requested this image.
1760    ///
1761    /// Audios can only be used in the same app.
1762    ///
1763    /// Is `None` for dummy.
1764    pub fn app_id(&self) -> Option<AppId> {
1765        self.0.as_ref().map(|h| h.0.0)
1766    }
1767
1768    /// View-process generation that provided this image.
1769    ///
1770    /// Audios can only be used in the same view-process instance.
1771    ///
1772    /// Is [`ViewProcessGen::INVALID`] for dummy.
1773    pub fn view_process_gen(&self) -> ViewProcessGen {
1774        self.0.as_ref().map(|h| h.1).unwrap_or(ViewProcessGen::INVALID)
1775    }
1776
1777    /// Create a weak reference to this audio.
1778    pub fn downgrade(&self) -> WeakViewAudioHandle {
1779        match &self.0 {
1780            Some(h) => WeakViewAudioHandle(ArcEq::downgrade(h)),
1781            None => WeakViewAudioHandle(WeakEq::new()),
1782        }
1783    }
1784}
1785impl Drop for ViewAudioHandle {
1786    fn drop(&mut self) {
1787        if let Some(h) = self.0.take()
1788            && Arc::strong_count(&h) == 1
1789            && let Some(app) = APP.id()
1790        {
1791            if h.0.0 != app {
1792                tracing::error!("audio from app `{:?}` dropped in app `{:?}`", h.0, app);
1793                return;
1794            }
1795
1796            if VIEW_PROCESS.is_available() && VIEW_PROCESS.generation() == h.1 {
1797                let _ = VIEW_PROCESS.write().process.forget_audio(h.2);
1798            }
1799        }
1800    }
1801}
1802/// Connection to an audio loading or loaded in the View Process.
1803///
1804/// The audio is removed from the View Process cache when all clones of [`ViewAudioHandle`] drops, but
1805/// if there is another audio pointer holding it, this weak pointer can be upgraded back
1806/// to a strong connection to the audio.
1807///
1808/// Dummy handles never upgrade back.
1809#[derive(Clone, Debug, PartialEq, Eq)]
1810pub struct WeakViewAudioHandle(WeakEq<ViewAudioHandleData>);
1811impl WeakViewAudioHandle {
1812    /// Attempt to upgrade the weak pointer to the audio to a full audio.
1813    ///
1814    /// Returns `Some` if the is at least another [`ViewAudioHandle`] holding the audio alive.
1815    pub fn upgrade(&self) -> Option<ViewAudioHandle> {
1816        let h = self.0.upgrade()?;
1817        if h.1 == VIEW_PROCESS.generation() {
1818            Some(ViewAudioHandle(Some(h)))
1819        } else {
1820            None
1821        }
1822    }
1823}
1824
1825zng_unique_id::unique_id_32! {
1826    /// Unique identifier of an open audio output.
1827    ///
1828    /// # Name
1829    ///
1830    /// IDs are only unique for the same process.
1831    /// You can associate a [`name`] with an ID to give it a persistent identifier.
1832    ///
1833    /// [`name`]: AudioOutputId::name
1834    pub struct AudioOutputId;
1835}
1836zng_unique_id::impl_unique_id_name!(AudioOutputId);
1837zng_unique_id::impl_unique_id_fmt!(AudioOutputId);
1838zng_unique_id::impl_unique_id_bytemuck!(AudioOutputId);
1839zng_var::impl_from_and_into_var! {
1840    /// Calls [`AudioOutputId::named`].
1841    fn from(name: &'static str) -> AudioOutputId {
1842        AudioOutputId::named(name)
1843    }
1844    /// Calls [`AudioOutputId::named`].
1845    fn from(name: String) -> AudioOutputId {
1846        AudioOutputId::named(name)
1847    }
1848    /// Calls [`AudioOutputId::named`].
1849    fn from(name: std::borrow::Cow<'static, str>) -> AudioOutputId {
1850        AudioOutputId::named(name)
1851    }
1852    /// Calls [`AudioOutputId::named`].
1853    fn from(name: char) -> AudioOutputId {
1854        AudioOutputId::named(name)
1855    }
1856    /// Calls [`AudioOutputId::named`].
1857    fn from(name: Txt) -> AudioOutputId {
1858        AudioOutputId::named(name)
1859    }
1860
1861    fn from(some: AudioOutputId) -> Option<AudioOutputId>;
1862}
1863impl serde::Serialize for AudioOutputId {
1864    fn serialize<S>(&self, serializer: S) -> std::result::Result<S::Ok, S::Error>
1865    where
1866        S: serde::Serializer,
1867    {
1868        let name = self.name();
1869        if name.is_empty() {
1870            use serde::ser::Error;
1871            return Err(S::Error::custom("cannot serialize unnamed `AudioOutputId`"));
1872        }
1873        name.serialize(serializer)
1874    }
1875}
1876impl<'de> serde::Deserialize<'de> for AudioOutputId {
1877    fn deserialize<D>(deserializer: D) -> std::result::Result<Self, D::Error>
1878    where
1879        D: serde::Deserializer<'de>,
1880    {
1881        let name = Txt::deserialize(deserializer)?;
1882        Ok(AudioOutputId::named(name))
1883    }
1884}