1use std::{
4 collections::HashMap,
5 fmt,
6 path::PathBuf,
7 sync::{self, Arc},
8};
9
10pub mod raw_device_events;
11pub mod raw_events;
12
13use crate::{
14 event::{event, event_args},
15 window::{MonitorId, WindowId},
16};
17
18use parking_lot::{MappedRwLockReadGuard, MappedRwLockWriteGuard};
19use zng_app_context::app_local;
20use zng_layout::unit::{DipPoint, DipRect, DipSideOffsets, DipSize, Factor, Px, PxPoint, PxRect};
21use zng_task::channel::{self, ChannelError, IpcBytes, IpcReceiver, Receiver};
22use zng_txt::Txt;
23use zng_var::{ArcEq, ResponderVar, Var, VarHandle, WeakEq};
24use zng_view_api::{
25 self, DeviceEventsFilter, DragDropId, Event, FocusResult, ViewProcessGen, ViewProcessInfo,
26 api_extension::{ApiExtensionId, ApiExtensionName, ApiExtensionPayload, ApiExtensionRecvError},
27 audio::{
28 AudioDecoded, AudioId, AudioMetadata, AudioMix, AudioOutputConfig, AudioOutputId as ApiAudioOutputId, AudioOutputOpenData,
29 AudioOutputRequest, AudioOutputUpdateRequest, AudioPlayId, AudioPlayRequest, AudioRequest,
30 },
31 dialog::{FileDialog, FileDialogResponse, MsgDialog, MsgDialogResponse, Notification, NotificationResponse},
32 drag_drop::{DragDropData, DragDropEffect, DragDropError},
33 font::{FontOptions, IpcFontBytes},
34 image::{ImageDecoded, ImageEncodeId, ImageEncodeRequest, ImageMaskMode, ImageMetadata, ImageRequest, ImageTextureId},
35 window::{
36 CursorIcon, FocusIndicator, FrameRequest, FrameUpdateRequest, HeadlessOpenData, HeadlessRequest, RenderMode, ResizeDirection,
37 VideoMode, WindowButton, WindowRequest, WindowStateAll,
38 },
39};
40
41pub(crate) use zng_view_api::{
42 Controller, raw_input::InputDeviceId as ApiDeviceId, window::MonitorId as ApiMonitorId, window::WindowId as ApiWindowId,
43};
44use zng_view_api::{
45 clipboard::{ClipboardData, ClipboardError, ClipboardType},
46 font::{FontFaceId, FontId, FontVariationName},
47 image::ImageId,
48};
49
50use self::raw_device_events::InputDeviceId;
51
52use super::{APP, AppId};
53
54#[expect(non_camel_case_types)]
56pub struct VIEW_PROCESS;
57struct ViewProcessService {
58 process: zng_view_api::Controller,
59 input_device_ids: HashMap<ApiDeviceId, InputDeviceId>,
60 monitor_ids: HashMap<ApiMonitorId, MonitorId>,
61
62 data_generation: ViewProcessGen,
63
64 loading_images: Vec<WeakEq<ViewImageHandleData>>,
65 encoding_images: Vec<EncodeRequest>,
66
67 loading_audios: Vec<WeakEq<ViewAudioHandleData>>,
68
69 pending_frames: usize,
70
71 message_dialogs: Vec<(zng_view_api::dialog::DialogId, ResponderVar<MsgDialogResponse>)>,
72 file_dialogs: Vec<(zng_view_api::dialog::DialogId, ResponderVar<FileDialogResponse>)>,
73 notifications: Vec<(zng_view_api::dialog::DialogId, VarHandle, ResponderVar<NotificationResponse>)>,
74
75 ping_count: u16,
76}
77app_local! {
78 static VIEW_PROCESS_SV: Option<ViewProcessService> = None;
79 static VIEW_PROCESS_INFO: ViewProcessInfo = const { ViewProcessInfo::new(ViewProcessGen::INVALID, false) };
80}
81impl VIEW_PROCESS {
82 pub fn is_available(&self) -> bool {
85 APP.is_running() && VIEW_PROCESS_SV.read().is_some()
86 }
87
88 fn read(&self) -> MappedRwLockReadGuard<'_, ViewProcessService> {
89 VIEW_PROCESS_SV.read_map(|e| e.as_ref().expect("VIEW_PROCESS not available"))
90 }
91
92 fn write(&self) -> MappedRwLockWriteGuard<'_, ViewProcessService> {
93 VIEW_PROCESS_SV.write_map(|e| e.as_mut().expect("VIEW_PROCESS not available"))
94 }
95
96 fn try_write(&self) -> Result<MappedRwLockWriteGuard<'_, ViewProcessService>> {
97 let vp = VIEW_PROCESS_SV.write();
98 if let Some(w) = &*vp
99 && w.process.is_connected()
100 {
101 return Ok(MappedRwLockWriteGuard::map(vp, |w| w.as_mut().unwrap()));
102 }
103 Err(ChannelError::disconnected())
104 }
105
106 fn check_app(&self, id: AppId) {
107 let actual = APP.id();
108 if Some(id) != actual {
109 panic!("cannot use view handle from app `{id:?}` in app `{actual:?}`");
110 }
111 }
112
113 fn handle_write(&self, id: AppId) -> MappedRwLockWriteGuard<'_, ViewProcessService> {
114 self.check_app(id);
115 self.write()
116 }
117
118 pub fn is_connected(&self) -> bool {
120 self.read().process.is_connected()
121 }
122
123 pub fn is_headless_with_render(&self) -> bool {
125 self.read().process.headless()
126 }
127
128 pub fn is_same_process(&self) -> bool {
130 self.read().process.same_process()
131 }
132
133 pub fn info(&self) -> MappedRwLockReadGuard<'static, ViewProcessInfo> {
137 VIEW_PROCESS_INFO.read()
138 }
139
140 pub fn generation(&self) -> ViewProcessGen {
142 self.read().process.generation()
143 }
144
145 pub fn set_device_events_filter(&self, filter: DeviceEventsFilter) -> Result<()> {
150 self.write().process.set_device_events_filter(filter)
151 }
152
153 pub fn open_window(&self, config: WindowRequest) -> Result<()> {
160 self.write().process.open_window(config)
161 }
162
163 pub fn open_headless(&self, config: HeadlessRequest) -> Result<()> {
173 self.write().process.open_headless(config)
174 }
175
176 pub fn open_audio_output(&self, request: AudioOutputRequest) -> Result<()> {
183 self.write().process.open_audio_output(request)
184 }
185
186 pub fn add_image(&self, request: ImageRequest<IpcBytes>) -> Result<ViewImageHandle> {
195 let mut app = self.write();
196
197 let id = app.process.add_image(request)?;
198
199 let handle = ArcEq::new((APP.id().unwrap(), app.process.generation(), id));
200 app.loading_images.push(ArcEq::downgrade(&handle));
201
202 Ok(ViewImageHandle(Some(handle)))
203 }
204
205 pub fn add_image_pro(&self, request: ImageRequest<IpcReceiver<IpcBytes>>) -> Result<ViewImageHandle> {
214 let mut app = self.write();
215
216 let id = app.process.add_image_pro(request)?;
217
218 let handle = ArcEq::new((APP.id().unwrap(), app.process.generation(), id));
219 app.loading_images.push(ArcEq::downgrade(&handle));
220
221 Ok(ViewImageHandle(Some(handle)))
222 }
223
224 pub fn encode_image(&self, request: ImageEncodeRequest) -> Receiver<std::result::Result<IpcBytes, EncodeError>> {
228 let (sender, receiver) = channel::bounded(1);
229
230 if request.id != ImageId::INVALID {
231 let mut app = VIEW_PROCESS.write();
232
233 match app.process.encode_image(request) {
234 Ok(r) => {
235 app.encoding_images.push(EncodeRequest {
236 task_id: r,
237 listener: sender,
238 });
239 }
240 Err(_) => {
241 let _ = sender.send_blocking(Err(EncodeError::Disconnected));
242 }
243 }
244 } else {
245 let _ = sender.send_blocking(Err(EncodeError::Dummy));
246 }
247
248 receiver
249 }
250
251 pub fn add_audio(&self, request: AudioRequest<IpcBytes>) -> Result<ViewAudioHandle> {
262 let mut app = self.write();
263
264 let id = app.process.add_audio(request)?;
265
266 let handle = ArcEq::new((APP.id().unwrap(), app.process.generation(), id));
267 app.loading_audios.push(ArcEq::downgrade(&handle));
268
269 Ok(ViewAudioHandle(Some(handle)))
270 }
271
272 pub fn add_audio_pro(&self, request: AudioRequest<IpcReceiver<IpcBytes>>) -> Result<ViewAudioHandle> {
283 let mut app = self.write();
284
285 let id = app.process.add_audio_pro(request)?;
286
287 let handle = ArcEq::new((APP.id().unwrap(), app.process.generation(), id));
288 app.loading_audios.push(ArcEq::downgrade(&handle));
289
290 Ok(ViewAudioHandle(Some(handle)))
291 }
292
293 pub fn clipboard(&self) -> Result<&ViewClipboard> {
295 if VIEW_PROCESS.is_connected() {
296 Ok(&ViewClipboard {})
297 } else {
298 Err(ChannelError::disconnected())
299 }
300 }
301
302 pub fn notification_dialog(&self, notification: Var<Notification>, responder: ResponderVar<NotificationResponse>) -> Result<()> {
308 let mut app = self.write();
309 let dlg_id = app.process.notification_dialog(notification.get())?;
310 let handle = notification.hook(move |n| {
311 let mut app = VIEW_PROCESS.write();
312 let retain = app.notifications.iter().any(|(id, _, _)| *id == dlg_id);
313 if retain {
314 app.process.update_notification(dlg_id, n.value().clone()).ok();
315 }
316 retain
317 });
318 app.notifications.push((dlg_id, handle, responder));
319 Ok(())
320 }
321
322 pub fn pending_frames(&self) -> usize {
326 self.write().pending_frames
327 }
328
329 pub fn respawn(&self) {
333 self.write().process.respawn()
334 }
335
336 pub fn extension_id(&self, extension_name: impl Into<ApiExtensionName>) -> Result<Option<ApiExtensionId>> {
342 let me = self.read();
343 if me.process.is_connected() {
344 Ok(self.info().extensions.id(&extension_name.into()))
345 } else {
346 Err(ChannelError::disconnected())
347 }
348 }
349
350 pub fn third_party_licenses(&self) -> Result<Vec<crate::third_party::LicenseUsed>> {
355 self.write().process.third_party_licenses()
356 }
357
358 pub fn app_extension_raw(&self, extension_id: ApiExtensionId, extension_request: ApiExtensionPayload) -> Result<ApiExtensionPayload> {
360 self.write().process.app_extension(extension_id, extension_request)
361 }
362
363 pub fn app_extension<I, O>(&self, extension_id: ApiExtensionId, request: &I) -> Result<std::result::Result<O, ApiExtensionRecvError>>
365 where
366 I: serde::Serialize,
367 O: serde::de::DeserializeOwned,
368 {
369 let payload = ApiExtensionPayload::serialize(&request).unwrap();
370 let response = self.write().process.app_extension(extension_id, payload)?;
371 Ok(response.deserialize::<O>())
372 }
373
374 pub fn handle_disconnect(&self, vp_gen: ViewProcessGen) {
380 self.write().process.handle_disconnect(vp_gen)
381 }
382
383 pub(super) fn start<F>(&self, view_process_exe: PathBuf, view_process_env: HashMap<Txt, Txt>, headless: bool, on_event: F)
385 where
386 F: FnMut(Event) + Send + 'static,
387 {
388 let _s = tracing::debug_span!("VIEW_PROCESS.start", ?view_process_exe, ?view_process_env, ?headless).entered();
389
390 let process = zng_view_api::Controller::start(view_process_exe, view_process_env, headless, on_event);
391 *VIEW_PROCESS_SV.write() = Some(ViewProcessService {
392 data_generation: process.generation(),
393 process,
394 input_device_ids: HashMap::default(),
395 monitor_ids: HashMap::default(),
396 loading_images: vec![],
397 encoding_images: vec![],
398 loading_audios: vec![],
399 pending_frames: 0,
400 message_dialogs: vec![],
401 file_dialogs: vec![],
402 notifications: vec![],
403 ping_count: 0,
404 });
405 }
406
407 pub(crate) fn on_window_opened(&self, window_id: WindowId, data: zng_view_api::window::WindowOpenData) -> (ViewWindow, WindowOpenData) {
408 let mut app = self.write();
409 let _ = app.check_generation();
410
411 let win = ViewWindow(ArcEq::new(ViewWindowData {
412 app_id: APP.id().unwrap(),
413 id: ApiWindowId::from_raw(window_id.get()),
414 generation: app.data_generation,
415 }));
416 drop(app);
417
418 let data = WindowOpenData::new(data, |id| self.monitor_id(id));
419
420 (win, data)
421 }
422
423 pub(crate) fn on_audio_output_opened(&self, output_id: AudioOutputId, data: AudioOutputOpenData) -> ViewAudioOutput {
424 let mut app = self.write();
425 let _ = app.check_generation();
426
427 ViewAudioOutput(ArcEq::new(ViewAudioOutputData {
428 app_id: APP.id().unwrap(),
429 id: ApiAudioOutputId::from_raw(output_id.get()),
430 generation: app.data_generation,
431 data,
432 }))
433 }
434
435 pub(super) fn input_device_id(&self, id: ApiDeviceId) -> InputDeviceId {
437 *self.write().input_device_ids.entry(id).or_insert_with(InputDeviceId::new_unique)
438 }
439
440 pub(super) fn monitor_id(&self, id: ApiMonitorId) -> MonitorId {
442 *self.write().monitor_ids.entry(id).or_insert_with(MonitorId::new_unique)
443 }
444
445 pub(super) fn handle_inited(&self, inited: &zng_view_api::ViewProcessInfo) {
451 let mut me = self.write();
452 *VIEW_PROCESS_INFO.write() = inited.clone();
453 me.process.handle_inited(inited.generation);
454 }
455
456 pub(super) fn handle_suspended(&self) {
457 self.write().process.handle_suspended();
458 }
459
460 pub(crate) fn on_headless_opened(
461 &self,
462 id: WindowId,
463 data: zng_view_api::window::HeadlessOpenData,
464 ) -> (ViewHeadless, HeadlessOpenData) {
465 let mut app = self.write();
466 let _ = app.check_generation();
467
468 let surf = ViewHeadless(ArcEq::new(ViewWindowData {
469 app_id: APP.id().unwrap(),
470 id: ApiWindowId::from_raw(id.get()),
471 generation: app.data_generation,
472 }));
473
474 (surf, data)
475 }
476
477 pub(super) fn on_image_metadata(&self, meta: &ImageMetadata) -> Option<ViewImageHandle> {
478 let mut app = self.write();
479
480 let mut found = None;
481 app.loading_images.retain(|i| {
482 if let Some(h) = i.upgrade() {
483 if found.is_none() && h.2 == meta.id {
484 found = Some(h);
485 }
486 true
488 } else {
489 false
490 }
491 });
492
493 if found.is_none() && meta.parent.is_some() {
502 let handle = ArcEq::new((APP.id().unwrap(), app.process.generation(), meta.id));
505 app.loading_images.push(ArcEq::downgrade(&handle));
506
507 return Some(ViewImageHandle(Some(handle)));
508 }
509
510 found.map(|h| ViewImageHandle(Some(h)))
511 }
512
513 pub(super) fn on_image_decoded(&self, data: &ImageDecoded) -> Option<ViewImageHandle> {
514 let mut app = self.write();
515
516 let mut found = None;
522 app.loading_images.retain(|i| {
523 if let Some(h) = i.upgrade() {
524 if found.is_none() && h.2 == data.meta.id {
525 found = Some(h);
526 return data.partial.is_some();
527 }
528 true
529 } else {
530 false
531 }
532 });
533
534 found.map(|h| ViewImageHandle(Some(h)))
535 }
536
537 pub(super) fn on_image_error(&self, id: ImageId) -> Option<ViewImageHandle> {
538 let mut app = self.write();
539
540 let mut found = None;
541 app.loading_images.retain(|i| {
542 if let Some(h) = i.upgrade() {
543 if found.is_none() && h.2 == id {
544 found = Some(h);
545 return false;
546 }
547 true
548 } else {
549 false
550 }
551 });
552
553 found.map(|h| ViewImageHandle(Some(h)))
556 }
557
558 pub(super) fn on_audio_metadata(&self, meta: &AudioMetadata) -> Option<ViewAudioHandle> {
559 let mut app = self.write();
562
563 let mut found = None;
564 app.loading_audios.retain(|i| {
565 if let Some(h) = i.upgrade() {
566 if found.is_none() && h.2 == meta.id {
567 found = Some(h);
568 }
569 true
571 } else {
572 false
573 }
574 });
575
576 if found.is_none() && meta.parent.is_some() {
577 let handle = ArcEq::new((APP.id().unwrap(), app.process.generation(), meta.id));
580 app.loading_audios.push(ArcEq::downgrade(&handle));
581
582 return Some(ViewAudioHandle(Some(handle)));
583 }
584
585 found.map(|h| ViewAudioHandle(Some(h)))
586 }
587
588 pub(super) fn on_audio_decoded(&self, audio: &AudioDecoded) -> Option<ViewAudioHandle> {
589 let mut app = self.write();
595
596 let mut found = None;
597 app.loading_audios.retain(|i| {
598 if let Some(h) = i.upgrade() {
599 if found.is_none() && h.2 == audio.id {
600 found = Some(h);
601 return !audio.is_full;
602 }
603 true
604 } else {
605 false
606 }
607 });
608
609 found.map(|h| ViewAudioHandle(Some(h)))
610 }
611
612 pub(super) fn on_audio_error(&self, id: AudioId) -> Option<ViewAudioHandle> {
613 let mut app = self.write();
614
615 let mut found = None;
616 app.loading_audios.retain(|i| {
617 if let Some(h) = i.upgrade() {
618 if found.is_none() && h.2 == id {
619 found = Some(h);
620 return false;
621 }
622 true
623 } else {
624 false
625 }
626 });
627
628 found.map(|h| ViewAudioHandle(Some(h)))
631 }
632
633 pub(crate) fn on_frame_rendered(&self, _id: WindowId) {
634 let mut vp = self.write();
635 vp.pending_frames = vp.pending_frames.saturating_sub(1);
636 }
637
638 pub(crate) fn on_frame_image(&self, data: &ImageDecoded) -> ViewImageHandle {
639 ViewImageHandle(Some(ArcEq::new((APP.id().unwrap(), self.generation(), data.meta.id))))
640 }
641
642 pub(super) fn on_image_encoded(&self, task_id: ImageEncodeId, data: IpcBytes) {
643 self.on_image_encode_result(task_id, Ok(data));
644 }
645 pub(super) fn on_image_encode_error(&self, task_id: ImageEncodeId, error: Txt) {
646 self.on_image_encode_result(task_id, Err(EncodeError::Encode(error)));
647 }
648 fn on_image_encode_result(&self, task_id: ImageEncodeId, result: std::result::Result<IpcBytes, EncodeError>) {
649 let mut app = self.write();
650 app.encoding_images.retain(move |r| {
651 let done = r.task_id == task_id;
652 if done {
653 let _ = r.listener.send_blocking(result.clone());
654 }
655 !done
656 })
657 }
658
659 pub(crate) fn on_message_dlg_response(&self, id: zng_view_api::dialog::DialogId, response: MsgDialogResponse) {
660 let mut app = self.write();
661 if let Some(i) = app.message_dialogs.iter().position(|(i, _)| *i == id) {
662 let (_, r) = app.message_dialogs.swap_remove(i);
663 r.respond(response);
664 }
665 }
666
667 pub(crate) fn on_file_dlg_response(&self, id: zng_view_api::dialog::DialogId, response: FileDialogResponse) {
668 let mut app = self.write();
669 if let Some(i) = app.file_dialogs.iter().position(|(i, _)| *i == id) {
670 let (_, r) = app.file_dialogs.swap_remove(i);
671 r.respond(response);
672 }
673 }
674
675 pub(crate) fn on_notification_dlg_response(&self, id: zng_view_api::dialog::DialogId, response: NotificationResponse) {
676 let mut app = self.write();
677 if let Some(i) = app.notifications.iter().position(|(i, _, _)| *i == id) {
678 let (_, _, r) = app.notifications.swap_remove(i);
679 r.respond(response);
680 }
681 }
682
683 pub(super) fn on_respawned(&self, _gen: ViewProcessGen) {
684 let mut app = self.write();
685 app.pending_frames = 0;
686 for (_, r) in app.message_dialogs.drain(..) {
687 r.respond(MsgDialogResponse::Error(Txt::from_static("respawn")));
688 }
689 for (_, r) in app.file_dialogs.drain(..) {
690 r.respond(FileDialogResponse::Error(Txt::from_static("respawn")));
691 }
692 for (_, _, r) in app.notifications.drain(..) {
693 r.respond(NotificationResponse::Error(Txt::from_static("respawn")));
694 }
695 }
696
697 pub(crate) fn exit(&self) {
698 *VIEW_PROCESS_SV.write() = None;
699 }
700
701 pub(crate) fn ping(&self) {
702 let mut app = self.write();
703 let count = app.ping_count.wrapping_add(1);
704 if let Ok(c) = app.process.ping(count)
705 && c != count
706 {
707 tracing::error!("incorrect ping response, expected {count}, was {c}");
708 }
709 app.ping_count = count;
710 }
711
712 pub(crate) fn on_pong(&self, count: u16) {
713 let expected = self.read().ping_count;
714 if expected != count {
715 tracing::warn!("unexpected pong event, expected {expected}, was {count}");
717 }
718 }
719}
720impl ViewProcessService {
721 #[must_use = "if `true` all current WinId, DevId and MonId are invalid"]
722 fn check_generation(&mut self) -> bool {
723 let vp_gen = self.process.generation();
724 let invalid = vp_gen != self.data_generation;
725 if invalid {
726 self.data_generation = vp_gen;
727 self.input_device_ids.clear();
728 self.monitor_ids.clear();
729 }
730 invalid
731 }
732}
733
734event_args! {
735 pub struct ViewProcessInitedArgs {
737 pub info: zng_view_api::ViewProcessInfo,
739
740 ..
741
742 fn is_in_target(&self, _id: WidgetId) -> bool {
744 true
745 }
746 }
747
748 pub struct ViewProcessSuspendedArgs {
750
751 ..
752
753 fn is_in_target(&self, _id: WidgetId) -> bool {
755 true
756 }
757 }
758}
759impl std::ops::Deref for ViewProcessInitedArgs {
760 type Target = zng_view_api::ViewProcessInfo;
761
762 fn deref(&self) -> &Self::Target {
763 &self.info
764 }
765}
766
767event! {
768 pub static VIEW_PROCESS_INITED_EVENT: ViewProcessInitedArgs;
770 pub static VIEW_PROCESS_SUSPENDED_EVENT: ViewProcessSuspendedArgs;
775}
776
777#[derive(Debug, Clone, PartialEq)]
779#[non_exhaustive]
780pub struct WindowOpenData {
781 pub state: WindowStateAll,
783
784 pub monitor: Option<MonitorId>,
786
787 pub position: (PxPoint, DipPoint),
791 pub size: DipSize,
793
794 pub scale_factor: Factor,
796
797 pub render_mode: RenderMode,
799
800 pub safe_padding: DipSideOffsets,
806}
807impl WindowOpenData {
808 pub(crate) fn new(data: zng_view_api::window::WindowOpenData, map_monitor: impl FnOnce(ApiMonitorId) -> MonitorId) -> Self {
809 WindowOpenData {
810 state: data.state,
811 monitor: data.monitor.map(map_monitor),
812 position: data.position,
813 size: data.size,
814 scale_factor: data.scale_factor,
815 render_mode: data.render_mode,
816 safe_padding: data.safe_padding,
817 }
818 }
819}
820
821#[derive(Debug, Clone, PartialEq, Eq)]
825#[must_use = "the window is closed when all clones of the handle are dropped"]
826pub struct ViewWindow(ArcEq<ViewWindowData>);
827impl ViewWindow {
828 pub fn generation(&self) -> ViewProcessGen {
830 self.0.generation
831 }
832
833 pub fn set_title(&self, title: Txt) -> Result<()> {
835 self.0.call(|id, p| p.set_title(id, title))
836 }
837
838 pub fn set_visible(&self, visible: bool) -> Result<()> {
840 self.0.call(|id, p| p.set_visible(id, visible))
841 }
842
843 pub fn set_always_on_top(&self, always_on_top: bool) -> Result<()> {
845 self.0.call(|id, p| p.set_always_on_top(id, always_on_top))
846 }
847
848 pub fn set_movable(&self, movable: bool) -> Result<()> {
850 self.0.call(|id, p| p.set_movable(id, movable))
851 }
852
853 pub fn set_resizable(&self, resizable: bool) -> Result<()> {
855 self.0.call(|id, p| p.set_resizable(id, resizable))
856 }
857
858 pub fn set_icon(&self, icon: Option<&ViewImageHandle>) -> Result<()> {
860 self.0.call(|id, p| {
861 if let Some(icon) = icon.and_then(|i| i.0.as_ref()) {
862 if p.generation() == icon.1 {
863 p.set_icon(id, Some(icon.2))
864 } else {
865 Err(ChannelError::disconnected())
866 }
867 } else {
868 p.set_icon(id, None)
869 }
870 })
871 }
872
873 pub fn set_cursor(&self, cursor: Option<CursorIcon>) -> Result<()> {
875 self.0.call(|id, p| p.set_cursor(id, cursor))
876 }
877
878 pub fn set_cursor_image(&self, cursor: Option<&ViewImageHandle>, hotspot: PxPoint) -> Result<()> {
885 self.0.call(|id, p| {
886 if let Some(cur) = cursor.and_then(|i| i.0.as_ref()) {
887 if p.generation() == cur.1 {
888 p.set_cursor_image(id, Some(zng_view_api::window::CursorImage::new(cur.2, hotspot)))
889 } else {
890 Err(ChannelError::disconnected())
891 }
892 } else {
893 p.set_cursor_image(id, None)
894 }
895 })
896 }
897
898 pub fn set_taskbar_visible(&self, visible: bool) -> Result<()> {
900 self.0.call(|id, p| p.set_taskbar_visible(id, visible))
901 }
902
903 pub fn bring_to_top(&self) -> Result<()> {
905 self.0.call(|id, p| p.bring_to_top(id))
906 }
907
908 pub fn set_state(&self, state: WindowStateAll) -> Result<()> {
910 self.0.call(|id, p| p.set_state(id, state))
911 }
912
913 pub fn set_video_mode(&self, mode: VideoMode) -> Result<()> {
915 self.0.call(|id, p| p.set_video_mode(id, mode))
916 }
917
918 pub fn set_enabled_buttons(&self, buttons: WindowButton) -> Result<()> {
920 self.0.call(|id, p| p.set_enabled_buttons(id, buttons))
921 }
922
923 pub fn renderer(&self) -> ViewRenderer {
925 ViewRenderer(ArcEq::downgrade(&self.0))
926 }
927
928 pub fn set_capture_mode(&self, enabled: bool) -> Result<()> {
933 self.0.call(|id, p| p.set_capture_mode(id, enabled))
934 }
935
936 pub fn focus(&self) -> Result<FocusResult> {
940 self.0.call(|id, p| p.focus(id))
941 }
942
943 pub fn set_focus_indicator(&self, indicator: Option<FocusIndicator>) -> Result<()> {
946 self.0.call(|id, p| p.set_focus_indicator(id, indicator))
947 }
948
949 pub fn drag_move(&self) -> Result<()> {
953 self.0.call(|id, p| p.drag_move(id))
954 }
955
956 pub fn drag_resize(&self, direction: ResizeDirection) -> Result<()> {
960 self.0.call(|id, p| p.drag_resize(id, direction))
961 }
962
963 pub fn start_drag_drop(
969 &self,
970 data: Vec<DragDropData>,
971 allowed_effects: DragDropEffect,
972 ) -> Result<std::result::Result<DragDropId, DragDropError>> {
973 self.0.call(|id, p| p.start_drag_drop(id, data, allowed_effects))
974 }
975
976 pub fn drag_dropped(&self, drop_id: DragDropId, applied: DragDropEffect) -> Result<()> {
978 self.0.call(|id, p| p.drag_dropped(id, drop_id, applied))
979 }
980
981 pub fn open_title_bar_context_menu(&self, position: DipPoint) -> Result<()> {
983 self.0.call(|id, p| p.open_title_bar_context_menu(id, position))
984 }
985
986 pub fn message_dialog(&self, dlg: MsgDialog, responder: ResponderVar<MsgDialogResponse>) -> Result<()> {
991 let dlg_id = self.0.call(|id, p| p.message_dialog(id, dlg))?;
992 VIEW_PROCESS.handle_write(self.0.app_id).message_dialogs.push((dlg_id, responder));
993 Ok(())
994 }
995
996 pub fn file_dialog(&self, dlg: FileDialog, responder: ResponderVar<FileDialogResponse>) -> Result<()> {
1001 let dlg_id = self.0.call(|id, p| p.file_dialog(id, dlg))?;
1002 VIEW_PROCESS.handle_write(self.0.app_id).file_dialogs.push((dlg_id, responder));
1003 Ok(())
1004 }
1005
1006 pub fn access_update(&self, update: zng_view_api::access::AccessTreeUpdate) -> Result<()> {
1008 self.0.call(|id, p| p.access_update(id, update))
1009 }
1010
1011 pub fn set_ime_area(&self, area: Option<DipRect>) -> Result<()> {
1015 self.0.call(|id, p| p.set_ime_area(id, area))
1016 }
1017
1018 pub fn set_system_shutdown_warn(&self, reason: Txt) -> Result<()> {
1029 self.0.call(move |id, p| p.set_system_shutdown_warn(id, reason))
1030 }
1031
1032 pub fn close(self) {
1034 drop(self)
1035 }
1036
1037 pub fn window_extension_raw(&self, extension_id: ApiExtensionId, request: ApiExtensionPayload) -> Result<ApiExtensionPayload> {
1039 self.0.call(|id, p| p.window_extension(id, extension_id, request))
1040 }
1041
1042 pub fn window_extension<I, O>(&self, extension_id: ApiExtensionId, request: &I) -> Result<std::result::Result<O, ApiExtensionRecvError>>
1044 where
1045 I: serde::Serialize,
1046 O: serde::de::DeserializeOwned,
1047 {
1048 let r = self.window_extension_raw(extension_id, ApiExtensionPayload::serialize(&request).unwrap())?;
1049 Ok(r.deserialize())
1050 }
1051
1052 pub fn downgrade(&self) -> WeakViewWindow {
1054 WeakViewWindow(ArcEq::downgrade(&self.0))
1055 }
1056}
1057#[derive(Debug, Clone)]
1059pub struct WeakViewWindow(WeakEq<ViewWindowData>);
1060impl PartialEq for WeakViewWindow {
1061 fn eq(&self, other: &Self) -> bool {
1062 sync::Weak::ptr_eq(&self.0, &other.0)
1063 }
1064}
1065impl Eq for WeakViewWindow {}
1066impl WeakViewWindow {
1067 pub fn upgrade(&self) -> Option<ViewWindow> {
1069 let d = self.0.upgrade()?;
1070 if d.generation == VIEW_PROCESS.generation() {
1071 Some(ViewWindow(d))
1072 } else {
1073 None
1074 }
1075 }
1076}
1077
1078#[derive(Clone, Debug)]
1080pub enum ViewWindowOrHeadless {
1081 Window(ViewWindow),
1083 Headless(ViewHeadless),
1085}
1086impl ViewWindowOrHeadless {
1087 pub fn renderer(&self) -> ViewRenderer {
1089 match self {
1090 ViewWindowOrHeadless::Window(w) => w.renderer(),
1091 ViewWindowOrHeadless::Headless(h) => h.renderer(),
1092 }
1093 }
1094
1095 pub fn window_extension_raw(&self, extension_id: ApiExtensionId, request: ApiExtensionPayload) -> Result<ApiExtensionPayload> {
1097 match self {
1098 ViewWindowOrHeadless::Window(w) => w.window_extension_raw(extension_id, request),
1099 ViewWindowOrHeadless::Headless(h) => h.window_extension_raw(extension_id, request),
1100 }
1101 }
1102
1103 pub fn window_extension<I, O>(&self, extension_id: ApiExtensionId, request: &I) -> Result<std::result::Result<O, ApiExtensionRecvError>>
1105 where
1106 I: serde::Serialize,
1107 O: serde::de::DeserializeOwned,
1108 {
1109 match self {
1110 ViewWindowOrHeadless::Window(w) => w.window_extension(extension_id, request),
1111 ViewWindowOrHeadless::Headless(h) => h.window_extension(extension_id, request),
1112 }
1113 }
1114}
1115impl From<ViewWindow> for ViewWindowOrHeadless {
1116 fn from(w: ViewWindow) -> Self {
1117 ViewWindowOrHeadless::Window(w)
1118 }
1119}
1120impl From<ViewHeadless> for ViewWindowOrHeadless {
1121 fn from(w: ViewHeadless) -> Self {
1122 ViewWindowOrHeadless::Headless(w)
1123 }
1124}
1125
1126#[derive(Debug)]
1127struct ViewAudioOutputData {
1128 app_id: AppId,
1129 id: ApiAudioOutputId,
1130 generation: ViewProcessGen,
1131 data: AudioOutputOpenData,
1132}
1133impl ViewAudioOutputData {
1134 fn call<R>(&self, f: impl FnOnce(ApiAudioOutputId, &mut Controller) -> Result<R>) -> Result<R> {
1135 let mut app = VIEW_PROCESS.handle_write(self.app_id);
1136 if app.check_generation() {
1137 Err(ChannelError::disconnected())
1138 } else {
1139 f(self.id, &mut app.process)
1140 }
1141 }
1142}
1143impl Drop for ViewAudioOutputData {
1144 fn drop(&mut self) {
1145 if VIEW_PROCESS.is_available() {
1146 let mut app = VIEW_PROCESS.handle_write(self.app_id);
1147 if self.generation == app.process.generation() {
1148 let _ = app.process.close_audio_output(self.id);
1149 }
1150 }
1151 }
1152}
1153
1154#[derive(Clone, Debug, PartialEq, Eq)]
1158#[must_use = "the audio output is disposed when all clones of the handle are dropped"]
1159pub struct ViewAudioOutput(ArcEq<ViewAudioOutputData>);
1160impl ViewAudioOutput {
1161 pub fn cue(&self, mix: AudioMix) -> Result<AudioPlayId> {
1163 self.0.call(|id, p| p.cue_audio(AudioPlayRequest::new(id, mix)))
1164 }
1165
1166 pub fn update(&self, cfg: AudioOutputConfig) -> Result<()> {
1168 self.0.call(|id, p| p.update_audio_output(AudioOutputUpdateRequest::new(id, cfg)))
1169 }
1170
1171 pub fn data(&self) -> &AudioOutputOpenData {
1173 &self.0.data
1174 }
1175
1176 pub fn downgrade(&self) -> WeakViewAudioOutput {
1178 WeakViewAudioOutput(ArcEq::downgrade(&self.0))
1179 }
1180}
1181#[derive(Clone, Debug, PartialEq, Eq)]
1183pub struct WeakViewAudioOutput(WeakEq<ViewAudioOutputData>);
1184impl WeakViewAudioOutput {
1185 pub fn upgrade(&self) -> Option<ViewAudioOutput> {
1187 let d = self.0.upgrade()?;
1188 if d.generation == VIEW_PROCESS.generation() {
1189 Some(ViewAudioOutput(d))
1190 } else {
1191 None
1192 }
1193 }
1194}
1195
1196#[derive(Debug)]
1197struct ViewWindowData {
1198 app_id: AppId,
1199 id: ApiWindowId,
1200 generation: ViewProcessGen,
1201}
1202impl ViewWindowData {
1203 fn call<R>(&self, f: impl FnOnce(ApiWindowId, &mut Controller) -> Result<R>) -> Result<R> {
1204 let mut app = VIEW_PROCESS.handle_write(self.app_id);
1205 if app.check_generation() {
1206 Err(ChannelError::disconnected())
1207 } else {
1208 f(self.id, &mut app.process)
1209 }
1210 }
1211}
1212impl Drop for ViewWindowData {
1213 fn drop(&mut self) {
1214 if VIEW_PROCESS.is_available() {
1215 let mut app = VIEW_PROCESS.handle_write(self.app_id);
1216 if self.generation == app.process.generation() {
1217 let _ = app.process.close(self.id);
1218 }
1219 }
1220 }
1221}
1222
1223type Result<T> = std::result::Result<T, ChannelError>;
1224
1225#[derive(Clone, Debug, PartialEq, Eq)]
1229#[must_use = "the view is disposed when all clones of the handle are dropped"]
1230pub struct ViewHeadless(ArcEq<ViewWindowData>);
1231impl ViewHeadless {
1232 pub fn set_size(&self, size: DipSize, scale_factor: Factor) -> Result<()> {
1234 self.0.call(|id, p| p.set_headless_size(id, size, scale_factor))
1235 }
1236
1237 pub fn renderer(&self) -> ViewRenderer {
1239 ViewRenderer(ArcEq::downgrade(&self.0))
1240 }
1241
1242 pub fn window_extension_raw(&self, extension_id: ApiExtensionId, request: ApiExtensionPayload) -> Result<ApiExtensionPayload> {
1244 self.0.call(|id, p| p.window_extension(id, extension_id, request))
1245 }
1246
1247 pub fn window_extension<I, O>(&self, extension_id: ApiExtensionId, request: &I) -> Result<std::result::Result<O, ApiExtensionRecvError>>
1249 where
1250 I: serde::Serialize,
1251 O: serde::de::DeserializeOwned,
1252 {
1253 let r = self.window_extension_raw(extension_id, ApiExtensionPayload::serialize(&request).unwrap())?;
1254 Ok(r.deserialize())
1255 }
1256
1257 pub fn downgrade(&self) -> WeakViewHeadless {
1259 WeakViewHeadless(ArcEq::downgrade(&self.0))
1260 }
1261}
1262
1263#[derive(Clone, Debug)]
1265pub struct WeakViewHeadless(WeakEq<ViewWindowData>);
1266impl PartialEq for WeakViewHeadless {
1267 fn eq(&self, other: &Self) -> bool {
1268 sync::Weak::ptr_eq(&self.0, &other.0)
1269 }
1270}
1271impl WeakViewHeadless {
1272 pub fn upgrade(&self) -> Option<ViewHeadless> {
1274 let d = self.0.upgrade()?;
1275 if d.generation == VIEW_PROCESS.generation() {
1276 Some(ViewHeadless(d))
1277 } else {
1278 None
1279 }
1280 }
1281}
1282
1283#[derive(Clone, Debug, PartialEq, Eq)]
1288pub struct ViewRenderer(WeakEq<ViewWindowData>);
1289impl ViewRenderer {
1290 fn call<R>(&self, f: impl FnOnce(ApiWindowId, &mut Controller) -> Result<R>) -> Result<R> {
1291 if let Some(c) = self.0.upgrade() {
1292 c.call(f)
1293 } else {
1294 Err(ChannelError::disconnected())
1295 }
1296 }
1297
1298 pub fn generation(&self) -> Result<ViewProcessGen> {
1300 self.0.upgrade().map(|c| c.generation).ok_or(ChannelError::disconnected())
1301 }
1302
1303 pub fn use_image(&self, image: &ViewImageHandle) -> Result<ImageTextureId> {
1307 self.call(|id, p| {
1308 if let Some(img) = &image.0 {
1309 if p.generation() == img.1 {
1310 p.use_image(id, img.2)
1311 } else {
1312 Err(ChannelError::disconnected())
1313 }
1314 } else {
1315 Ok(ImageTextureId::INVALID)
1316 }
1317 })
1318 }
1319
1320 pub fn update_image_use(&mut self, tex_id: ImageTextureId, image: &ViewImageHandle, dirty_rect: Option<PxRect>) -> Result<bool> {
1327 self.call(|id, p| {
1328 if let Some(img) = &image.0 {
1329 if p.generation() == img.1 {
1330 p.update_image_use(id, tex_id, img.2, dirty_rect)
1331 } else {
1332 Err(ChannelError::disconnected())
1333 }
1334 } else {
1335 Ok(false)
1336 }
1337 })
1338 }
1339
1340 pub fn delete_image_use(&mut self, tex_id: ImageTextureId) -> Result<()> {
1342 self.call(|id, p| p.delete_image_use(id, tex_id))
1343 }
1344
1345 pub fn add_font_face(&self, bytes: IpcFontBytes, index: u32) -> Result<FontFaceId> {
1349 self.call(|id, p| p.add_font_face(id, bytes, index))
1350 }
1351
1352 pub fn delete_font_face(&self, font_face_id: FontFaceId) -> Result<()> {
1354 self.call(|id, p| p.delete_font_face(id, font_face_id))
1355 }
1356
1357 pub fn add_font(
1361 &self,
1362 font_face_id: FontFaceId,
1363 glyph_size: Px,
1364 options: FontOptions,
1365 variations: Vec<(FontVariationName, f32)>,
1366 ) -> Result<FontId> {
1367 self.call(|id, p| p.add_font(id, font_face_id, glyph_size, options, variations))
1368 }
1369
1370 pub fn delete_font(&self, font_id: FontId) -> Result<()> {
1372 self.call(|id, p| p.delete_font(id, font_id))
1373 }
1374
1375 pub fn frame_image(&self, mask: Option<ImageMaskMode>) -> Result<ViewImageHandle> {
1377 if let Some(c) = self.0.upgrade() {
1378 let id = c.call(|id, p| p.frame_image(id, mask))?;
1379 Ok(Self::add_frame_image(c.app_id, id))
1380 } else {
1381 Err(ChannelError::disconnected())
1382 }
1383 }
1384
1385 pub fn frame_image_rect(&self, rect: PxRect, mask: Option<ImageMaskMode>) -> Result<ViewImageHandle> {
1387 if let Some(c) = self.0.upgrade() {
1388 let id = c.call(|id, p| p.frame_image_rect(id, rect, mask))?;
1389 Ok(Self::add_frame_image(c.app_id, id))
1390 } else {
1391 Err(ChannelError::disconnected())
1392 }
1393 }
1394
1395 fn add_frame_image(app_id: AppId, id: ImageId) -> ViewImageHandle {
1396 if id == ImageId::INVALID {
1397 ViewImageHandle::dummy()
1398 } else {
1399 let mut app = VIEW_PROCESS.handle_write(app_id);
1400 let handle = ArcEq::new((APP.id().unwrap(), app.process.generation(), id));
1401 app.loading_images.push(ArcEq::downgrade(&handle));
1402
1403 ViewImageHandle(Some(handle))
1404 }
1405 }
1406
1407 pub fn render(&self, frame: FrameRequest) -> Result<()> {
1409 if let Some(w) = self.0.upgrade() {
1410 w.call(|id, p| p.render(id, frame))?;
1411 VIEW_PROCESS.handle_write(w.app_id).pending_frames += 1;
1412 Ok(())
1413 } else {
1414 Err(ChannelError::disconnected())
1415 }
1416 }
1417
1418 pub fn render_update(&self, frame: FrameUpdateRequest) -> Result<()> {
1420 if let Some(w) = self.0.upgrade() {
1421 w.call(|id, p| p.render_update(id, frame))?;
1422 VIEW_PROCESS.handle_write(w.app_id).pending_frames += 1;
1423 Ok(())
1424 } else {
1425 Err(ChannelError::disconnected())
1426 }
1427 }
1428
1429 pub fn render_extension_raw(&self, extension_id: ApiExtensionId, request: ApiExtensionPayload) -> Result<ApiExtensionPayload> {
1431 if let Some(w) = self.0.upgrade() {
1432 w.call(|id, p| p.render_extension(id, extension_id, request))
1433 } else {
1434 Err(ChannelError::disconnected())
1435 }
1436 }
1437
1438 pub fn render_extension<I, O>(&self, extension_id: ApiExtensionId, request: &I) -> Result<std::result::Result<O, ApiExtensionRecvError>>
1440 where
1441 I: serde::Serialize,
1442 O: serde::de::DeserializeOwned,
1443 {
1444 let r = self.render_extension_raw(extension_id, ApiExtensionPayload::serialize(&request).unwrap())?;
1445 Ok(r.deserialize())
1446 }
1447}
1448
1449type ViewImageHandleData = (AppId, ViewProcessGen, ImageId);
1450
1451#[must_use = "the image is disposed when all clones of the handle are dropped"]
1455#[derive(Clone, Debug, PartialEq, Eq)]
1456pub struct ViewImageHandle(Option<ArcEq<ViewImageHandleData>>);
1457impl ViewImageHandle {
1458 pub fn dummy() -> Self {
1460 ViewImageHandle(None)
1461 }
1462
1463 pub fn is_dummy(&self) -> bool {
1465 self.0.is_none()
1466 }
1467
1468 pub fn image_id(&self) -> ImageId {
1472 self.0.as_ref().map(|h| h.2).unwrap_or(ImageId::INVALID)
1473 }
1474
1475 pub fn app_id(&self) -> Option<AppId> {
1481 self.0.as_ref().map(|h| h.0.0)
1482 }
1483
1484 pub fn view_process_gen(&self) -> ViewProcessGen {
1490 self.0.as_ref().map(|h| h.1).unwrap_or(ViewProcessGen::INVALID)
1491 }
1492
1493 pub fn downgrade(&self) -> WeakViewImageHandle {
1495 match &self.0 {
1496 Some(h) => WeakViewImageHandle(ArcEq::downgrade(h)),
1497 None => WeakViewImageHandle(WeakEq::new()),
1498 }
1499 }
1500}
1501impl Drop for ViewImageHandle {
1502 fn drop(&mut self) {
1503 if let Some(h) = self.0.take()
1504 && Arc::strong_count(&h) == 1
1505 && let Some(app) = APP.id()
1506 {
1507 if h.0.0 != app {
1508 tracing::error!("image from app `{:?}` dropped in app `{:?}`", h.0, app);
1509 return;
1510 }
1511
1512 if VIEW_PROCESS.is_available() && VIEW_PROCESS.generation() == h.1 {
1513 let _ = VIEW_PROCESS.write().process.forget_image(h.2);
1514 }
1515 }
1516 }
1517}
1518#[derive(Clone, Debug)]
1526pub struct WeakViewImageHandle(WeakEq<ViewImageHandleData>);
1527impl PartialEq for WeakViewImageHandle {
1528 fn eq(&self, other: &Self) -> bool {
1529 sync::Weak::ptr_eq(&self.0, &other.0)
1530 }
1531}
1532impl WeakViewImageHandle {
1533 pub fn upgrade(&self) -> Option<ViewImageHandle> {
1537 let d = self.0.upgrade()?;
1538 if d.1 == VIEW_PROCESS.generation() {
1539 Some(ViewImageHandle(Some(d)))
1540 } else {
1541 None
1542 }
1543 }
1544}
1545
1546#[derive(Debug, Clone, PartialEq, Eq)]
1548#[non_exhaustive]
1549pub enum EncodeError {
1550 Encode(Txt),
1552 Dummy,
1557 Loading,
1559 Disconnected,
1561}
1562impl From<Txt> for EncodeError {
1563 fn from(e: Txt) -> Self {
1564 EncodeError::Encode(e)
1565 }
1566}
1567impl From<ChannelError> for EncodeError {
1568 fn from(_: ChannelError) -> Self {
1569 EncodeError::Disconnected
1570 }
1571}
1572impl fmt::Display for EncodeError {
1573 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1574 match self {
1575 EncodeError::Encode(e) => write!(f, "{e}"),
1576 EncodeError::Dummy => write!(f, "cannot encode dummy image"),
1577 EncodeError::Loading => write!(f, "cannot encode, image is still loading"),
1578 EncodeError::Disconnected => write!(f, "{}", ChannelError::disconnected()),
1579 }
1580 }
1581}
1582impl std::error::Error for EncodeError {}
1583
1584struct EncodeRequest {
1585 task_id: ImageEncodeId,
1586 listener: channel::Sender<std::result::Result<IpcBytes, EncodeError>>,
1587}
1588
1589type ClipboardResult<T> = std::result::Result<T, ClipboardError>;
1590
1591#[non_exhaustive]
1593pub struct ViewClipboard {}
1594impl ViewClipboard {
1595 pub fn read_text(&self) -> Result<ClipboardResult<Txt>> {
1599 match VIEW_PROCESS
1600 .try_write()?
1601 .process
1602 .read_clipboard(vec![ClipboardType::Text], true)?
1603 .map(|mut r| r.pop())
1604 {
1605 Ok(Some(ClipboardData::Text(t))) => Ok(Ok(t)),
1606 Err(e) => Ok(Err(e)),
1607 _ => Ok(Err(ClipboardError::Other(Txt::from_static("view-process returned incorrect type")))),
1608 }
1609 }
1610
1611 pub fn write_text(&self, txt: Txt) -> Result<ClipboardResult<()>> {
1615 VIEW_PROCESS
1616 .try_write()?
1617 .process
1618 .write_clipboard(vec![ClipboardData::Text(txt)])
1619 .map(|r| r.map(|_| ()))
1620 }
1621
1622 pub fn read_image(&self) -> Result<ClipboardResult<ViewImageHandle>> {
1626 let mut app = VIEW_PROCESS.try_write()?;
1627 match app.process.read_clipboard(vec![ClipboardType::Image], true)?.map(|mut r| r.pop()) {
1628 Ok(Some(ClipboardData::Image(id))) => {
1629 if id == ImageId::INVALID {
1630 Ok(Err(ClipboardError::Other(Txt::from_static("view-process returned invalid image"))))
1631 } else {
1632 let handle = ArcEq::new((APP.id().unwrap(), app.process.generation(), id));
1633 app.loading_images.push(ArcEq::downgrade(&handle));
1634 Ok(Ok(ViewImageHandle(Some(handle))))
1635 }
1636 }
1637 Err(e) => Ok(Err(e)),
1638 _ => Ok(Err(ClipboardError::Other(Txt::from_static("view-process returned incorrect type")))),
1639 }
1640 }
1641
1642 pub fn write_image(&self, img: &ViewImageHandle) -> Result<ClipboardResult<()>> {
1646 return VIEW_PROCESS
1647 .try_write()?
1648 .process
1649 .write_clipboard(vec![ClipboardData::Image(img.image_id())])
1650 .map(|r| r.map(|_| ()));
1651 }
1652
1653 pub fn read_paths(&self) -> Result<ClipboardResult<Vec<PathBuf>>> {
1657 match VIEW_PROCESS
1658 .try_write()?
1659 .process
1660 .read_clipboard(vec![ClipboardType::Paths], true)?
1661 .map(|mut r| r.pop())
1662 {
1663 Ok(Some(ClipboardData::Paths(f))) => Ok(Ok(f)),
1664 Err(e) => Ok(Err(e)),
1665 _ => Ok(Err(ClipboardError::Other(Txt::from_static("view-process returned incorrect type")))),
1666 }
1667 }
1668
1669 pub fn write_paths(&self, list: Vec<PathBuf>) -> Result<ClipboardResult<()>> {
1673 VIEW_PROCESS
1674 .try_write()?
1675 .process
1676 .write_clipboard(vec![ClipboardData::Paths(list)])
1677 .map(|r| r.map(|_| ()))
1678 }
1679
1680 pub fn read_extension(&self, data_type: Txt) -> Result<ClipboardResult<IpcBytes>> {
1684 match VIEW_PROCESS
1685 .try_write()?
1686 .process
1687 .read_clipboard(vec![ClipboardType::Extension(data_type.clone())], true)?
1688 .map(|mut r| r.pop())
1689 {
1690 Ok(Some(ClipboardData::Extension { data_type: rt, data })) if rt == data_type => Ok(Ok(data)),
1691 Err(e) => Ok(Err(e)),
1692 _ => Ok(Err(ClipboardError::Other(Txt::from_static("view-process returned incorrect type")))),
1693 }
1694 }
1695
1696 pub fn write_extension(&self, data_type: Txt, data: IpcBytes) -> Result<ClipboardResult<()>> {
1700 VIEW_PROCESS
1701 .try_write()?
1702 .process
1703 .write_clipboard(vec![ClipboardData::Extension { data_type, data }])
1704 .map(|r| r.map(|_| ()))
1705 }
1706}
1707
1708type ViewAudioHandleData = (AppId, ViewProcessGen, AudioId);
1709
1710#[must_use = "the audio is disposed when all clones of the handle are dropped"]
1714#[derive(Clone, Debug, PartialEq, Eq)]
1715pub struct ViewAudioHandle(Option<ArcEq<ViewAudioHandleData>>);
1716impl ViewAudioHandle {
1717 pub fn dummy() -> Self {
1719 ViewAudioHandle(None)
1720 }
1721
1722 pub fn is_dummy(&self) -> bool {
1724 self.0.is_none()
1725 }
1726
1727 pub fn audio_id(&self) -> AudioId {
1731 self.0.as_ref().map(|h| h.2).unwrap_or(AudioId::INVALID)
1732 }
1733
1734 pub fn app_id(&self) -> Option<AppId> {
1740 self.0.as_ref().map(|h| h.0.0)
1741 }
1742
1743 pub fn view_process_gen(&self) -> ViewProcessGen {
1749 self.0.as_ref().map(|h| h.1).unwrap_or(ViewProcessGen::INVALID)
1750 }
1751
1752 pub fn downgrade(&self) -> WeakViewAudioHandle {
1754 match &self.0 {
1755 Some(h) => WeakViewAudioHandle(ArcEq::downgrade(h)),
1756 None => WeakViewAudioHandle(WeakEq::new()),
1757 }
1758 }
1759}
1760impl Drop for ViewAudioHandle {
1761 fn drop(&mut self) {
1762 if let Some(h) = self.0.take()
1763 && Arc::strong_count(&h) == 1
1764 && let Some(app) = APP.id()
1765 {
1766 if h.0.0 != app {
1767 tracing::error!("audio from app `{:?}` dropped in app `{:?}`", h.0, app);
1768 return;
1769 }
1770
1771 if VIEW_PROCESS.is_available() && VIEW_PROCESS.generation() == h.1 {
1772 let _ = VIEW_PROCESS.write().process.forget_audio(h.2);
1773 }
1774 }
1775 }
1776}
1777#[derive(Clone, Debug, PartialEq, Eq)]
1785pub struct WeakViewAudioHandle(WeakEq<ViewAudioHandleData>);
1786impl WeakViewAudioHandle {
1787 pub fn upgrade(&self) -> Option<ViewAudioHandle> {
1791 let h = self.0.upgrade()?;
1792 if h.1 == VIEW_PROCESS.generation() {
1793 Some(ViewAudioHandle(Some(h)))
1794 } else {
1795 None
1796 }
1797 }
1798}
1799
1800zng_unique_id::unique_id_32! {
1801 pub struct AudioOutputId;
1810}
1811zng_unique_id::impl_unique_id_name!(AudioOutputId);
1812zng_unique_id::impl_unique_id_fmt!(AudioOutputId);
1813zng_unique_id::impl_unique_id_bytemuck!(AudioOutputId);
1814zng_var::impl_from_and_into_var! {
1815 fn from(name: &'static str) -> AudioOutputId {
1817 AudioOutputId::named(name)
1818 }
1819 fn from(name: String) -> AudioOutputId {
1821 AudioOutputId::named(name)
1822 }
1823 fn from(name: std::borrow::Cow<'static, str>) -> AudioOutputId {
1825 AudioOutputId::named(name)
1826 }
1827 fn from(name: char) -> AudioOutputId {
1829 AudioOutputId::named(name)
1830 }
1831 fn from(name: Txt) -> AudioOutputId {
1833 AudioOutputId::named(name)
1834 }
1835
1836 fn from(some: AudioOutputId) -> Option<AudioOutputId>;
1837}
1838impl serde::Serialize for AudioOutputId {
1839 fn serialize<S>(&self, serializer: S) -> std::result::Result<S::Ok, S::Error>
1840 where
1841 S: serde::Serializer,
1842 {
1843 let name = self.name();
1844 if name.is_empty() {
1845 use serde::ser::Error;
1846 return Err(S::Error::custom("cannot serialize unnamed `AudioOutputId`"));
1847 }
1848 name.serialize(serializer)
1849 }
1850}
1851impl<'de> serde::Deserialize<'de> for AudioOutputId {
1852 fn deserialize<D>(deserializer: D) -> std::result::Result<Self, D::Error>
1853 where
1854 D: serde::Deserializer<'de>,
1855 {
1856 let name = Txt::deserialize(deserializer)?;
1857 Ok(AudioOutputId::named(name))
1858 }
1859}