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