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, Frequency, Px, PxPoint, PxRect};
21use zng_task::channel::{self, ChannelError, IpcBytes, IpcReceiver, Receiver};
22use zng_txt::Txt;
23use zng_unique_id::IdMap;
24use zng_var::{ArcEq, ResponderVar, Var, VarHandle, WeakEq};
25use zng_view_api::{
26 self, DeviceEventsFilter, DragDropId, Event, FocusResult, ViewProcessGen, ViewProcessInfo,
27 api_extension::{ApiExtensionId, ApiExtensionName, ApiExtensionPayload, ApiExtensionRecvError},
28 audio::{
29 AudioDecoded, AudioId, AudioMetadata, AudioMix, AudioOutputConfig, AudioOutputId as ApiAudioOutputId, AudioOutputOpenData,
30 AudioOutputRequest, AudioOutputUpdateRequest, AudioPlayId, AudioPlayRequest, AudioRequest,
31 },
32 dialog::{FileDialog, FileDialogResponse, MsgDialog, MsgDialogResponse, Notification, NotificationResponse},
33 drag_drop::{DragDropData, DragDropEffect, DragDropError},
34 font::{FontOptions, IpcFontBytes},
35 image::{ImageDecoded, ImageEncodeId, ImageEncodeRequest, ImageMaskMode, ImageMetadata, ImageRequest, ImageTextureId},
36 window::{
37 CursorIcon, FocusIndicator, FrameRequest, FrameUpdateRequest, HeadlessOpenData, HeadlessRequest, RenderMode, ResizeDirection,
38 VideoMode, WindowButton, WindowRequest, WindowStateAll,
39 },
40};
41
42pub(crate) use zng_view_api::{
43 Controller, raw_input::InputDeviceId as ApiDeviceId, window::MonitorId as ApiMonitorId, window::WindowId as ApiWindowId,
44};
45use zng_view_api::{
46 clipboard::{ClipboardData, ClipboardError, ClipboardType},
47 font::{FontFaceId, FontId, FontVariationName},
48 image::ImageId,
49};
50
51use self::raw_device_events::InputDeviceId;
52
53use super::{APP, AppId};
54
55#[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 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 pub fn is_connected(&self) -> bool {
121 self.read().process.is_connected()
122 }
123
124 pub fn is_headless_with_render(&self) -> bool {
126 self.read().process.headless()
127 }
128
129 pub fn is_same_process(&self) -> bool {
131 self.read().process.same_process()
132 }
133
134 pub fn info(&self) -> MappedRwLockReadGuard<'static, ViewProcessInfo> {
138 VIEW_PROCESS_INFO.read()
139 }
140
141 pub fn generation(&self) -> ViewProcessGen {
143 self.read().process.generation()
144 }
145
146 pub fn set_device_events_filter(&self, filter: DeviceEventsFilter) -> Result<()> {
151 self.write().process.set_device_events_filter(filter)
152 }
153
154 pub fn open_window(&self, config: WindowRequest) -> Result<()> {
161 self.write().process.open_window(config)
162 }
163
164 pub fn open_headless(&self, config: HeadlessRequest) -> Result<()> {
174 self.write().process.open_headless(config)
175 }
176
177 pub fn open_audio_output(&self, request: AudioOutputRequest) -> Result<()> {
184 self.write().process.open_audio_output(request)
185 }
186
187 pub fn add_image(&self, request: ImageRequest<IpcBytes>) -> Result<ViewImageHandle> {
196 let mut app = self.write();
197
198 let id = app.process.add_image(request)?;
199
200 let handle = ArcEq::new((APP.id().unwrap(), app.process.generation(), id));
201 app.loading_images.push(ArcEq::downgrade(&handle));
202
203 Ok(ViewImageHandle(Some(handle)))
204 }
205
206 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 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 pub fn add_audio(&self, request: AudioRequest<IpcBytes>) -> Result<ViewAudioHandle> {
263 let mut app = self.write();
264
265 let id = app.process.add_audio(request)?;
266
267 let handle = ArcEq::new((APP.id().unwrap(), app.process.generation(), id));
268 app.loading_audios.push(ArcEq::downgrade(&handle));
269
270 Ok(ViewAudioHandle(Some(handle)))
271 }
272
273 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 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 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 #[deprecated = "use `is_busy`"]
327 pub fn pending_frames(&self) -> usize {
328 self.read().pending_frames.values().copied().sum()
329 }
330
331 pub fn is_busy(&self) -> bool {
335 self.read().pending_frames.values().copied().max().unwrap_or(0) > 1
336 }
337
338 pub fn respawn(&self) {
342 self.write().process.respawn()
343 }
344
345 pub fn extension_id(&self, extension_name: impl Into<ApiExtensionName>) -> Result<Option<ApiExtensionId>> {
351 let me = self.read();
352 if me.process.is_connected() {
353 Ok(self.info().extensions.id(&extension_name.into()))
354 } else {
355 Err(ChannelError::disconnected())
356 }
357 }
358
359 pub fn third_party_licenses(&self) -> Result<Vec<crate::third_party::LicenseUsed>> {
364 self.write().process.third_party_licenses()
365 }
366
367 pub fn app_extension_raw(&self, extension_id: ApiExtensionId, extension_request: ApiExtensionPayload) -> Result<ApiExtensionPayload> {
369 self.write().process.app_extension(extension_id, extension_request)
370 }
371
372 pub fn app_extension<I, O>(&self, extension_id: ApiExtensionId, request: &I) -> Result<std::result::Result<O, ApiExtensionRecvError>>
374 where
375 I: serde::Serialize,
376 O: serde::de::DeserializeOwned,
377 {
378 let payload = ApiExtensionPayload::serialize(&request).unwrap();
379 let response = self.write().process.app_extension(extension_id, payload)?;
380 Ok(response.deserialize::<O>())
381 }
382
383 pub fn handle_disconnect(&self, vp_gen: ViewProcessGen) {
389 self.write().process.handle_disconnect(vp_gen)
390 }
391
392 pub(super) fn start<F>(&self, view_process_exe: PathBuf, view_process_env: HashMap<Txt, Txt>, headless: bool, on_event: F)
394 where
395 F: FnMut(Event) + Send + 'static,
396 {
397 let _s = tracing::debug_span!("VIEW_PROCESS.start", ?view_process_exe, ?view_process_env, ?headless).entered();
398
399 let process = zng_view_api::Controller::start(view_process_exe, view_process_env, headless, on_event);
400 *VIEW_PROCESS_SV.write() = Some(ViewProcessService {
401 data_generation: process.generation(),
402 process,
403 input_device_ids: HashMap::default(),
404 monitor_ids: HashMap::default(),
405 loading_images: vec![],
406 encoding_images: vec![],
407 loading_audios: vec![],
408 pending_frames: IdMap::new(),
409 message_dialogs: vec![],
410 file_dialogs: vec![],
411 notifications: vec![],
412 ping_count: 0,
413 });
414 }
415
416 pub(crate) fn on_window_opened(&self, window_id: WindowId, data: zng_view_api::window::WindowOpenData) -> (ViewWindow, WindowOpenData) {
417 let mut app = self.write();
418 let _ = app.check_generation();
419
420 let win = ViewWindow(ArcEq::new(ViewWindowData {
421 app_id: APP.id().unwrap(),
422 id: ApiWindowId::from_raw(window_id.get()),
423 generation: app.data_generation,
424 }));
425 drop(app);
426
427 let data = WindowOpenData::new(data, |id| self.monitor_id(id));
428
429 (win, data)
430 }
431
432 pub(crate) fn on_audio_output_opened(&self, output_id: AudioOutputId, data: AudioOutputOpenData) -> ViewAudioOutput {
433 let mut app = self.write();
434 let _ = app.check_generation();
435
436 ViewAudioOutput(ArcEq::new(ViewAudioOutputData {
437 app_id: APP.id().unwrap(),
438 id: ApiAudioOutputId::from_raw(output_id.get()),
439 generation: app.data_generation,
440 data,
441 }))
442 }
443
444 pub(super) fn input_device_id(&self, id: ApiDeviceId) -> InputDeviceId {
446 *self.write().input_device_ids.entry(id).or_insert_with(InputDeviceId::new_unique)
447 }
448
449 pub(super) fn monitor_id(&self, id: ApiMonitorId) -> MonitorId {
451 *self.write().monitor_ids.entry(id).or_insert_with(MonitorId::new_unique)
452 }
453
454 pub(super) fn handle_inited(&self, inited: &zng_view_api::ViewProcessInfo) {
460 let mut me = self.write();
461 *VIEW_PROCESS_INFO.write() = inited.clone();
462 me.process.handle_inited(inited.generation);
463 }
464
465 pub(super) fn handle_suspended(&self) {
466 self.write().process.handle_suspended();
467 }
468
469 pub(crate) fn on_headless_opened(
470 &self,
471 id: WindowId,
472 data: zng_view_api::window::HeadlessOpenData,
473 ) -> (ViewHeadless, HeadlessOpenData) {
474 let mut app = self.write();
475 let _ = app.check_generation();
476
477 let surf = ViewHeadless(ArcEq::new(ViewWindowData {
478 app_id: APP.id().unwrap(),
479 id: ApiWindowId::from_raw(id.get()),
480 generation: app.data_generation,
481 }));
482
483 (surf, data)
484 }
485
486 pub(super) fn on_image_metadata(&self, meta: &ImageMetadata) -> Option<ViewImageHandle> {
487 let mut app = self.write();
488
489 let mut found = None;
490 app.loading_images.retain(|i| {
491 if let Some(h) = i.upgrade() {
492 if found.is_none() && h.2 == meta.id {
493 found = Some(h);
494 }
495 true
497 } else {
498 false
499 }
500 });
501
502 if found.is_none() && meta.parent.is_some() {
511 let handle = ArcEq::new((APP.id().unwrap(), app.process.generation(), meta.id));
514 app.loading_images.push(ArcEq::downgrade(&handle));
515
516 return Some(ViewImageHandle(Some(handle)));
517 }
518
519 found.map(|h| ViewImageHandle(Some(h)))
520 }
521
522 pub(super) fn on_image_decoded(&self, data: &ImageDecoded) -> Option<ViewImageHandle> {
523 let mut app = self.write();
524
525 let mut found = None;
531 app.loading_images.retain(|i| {
532 if let Some(h) = i.upgrade() {
533 if found.is_none() && h.2 == data.meta.id {
534 found = Some(h);
535 return data.partial.is_some();
536 }
537 true
538 } else {
539 false
540 }
541 });
542
543 found.map(|h| ViewImageHandle(Some(h)))
544 }
545
546 pub(super) fn on_image_error(&self, id: ImageId) -> Option<ViewImageHandle> {
547 let mut app = self.write();
548
549 let mut found = None;
550 app.loading_images.retain(|i| {
551 if let Some(h) = i.upgrade() {
552 if found.is_none() && h.2 == id {
553 found = Some(h);
554 return false;
555 }
556 true
557 } else {
558 false
559 }
560 });
561
562 found.map(|h| ViewImageHandle(Some(h)))
565 }
566
567 pub(super) fn on_audio_metadata(&self, meta: &AudioMetadata) -> Option<ViewAudioHandle> {
568 let mut app = self.write();
571
572 let mut found = None;
573 app.loading_audios.retain(|i| {
574 if let Some(h) = i.upgrade() {
575 if found.is_none() && h.2 == meta.id {
576 found = Some(h);
577 }
578 true
580 } else {
581 false
582 }
583 });
584
585 if found.is_none() && meta.parent.is_some() {
586 let handle = ArcEq::new((APP.id().unwrap(), app.process.generation(), meta.id));
589 app.loading_audios.push(ArcEq::downgrade(&handle));
590
591 return Some(ViewAudioHandle(Some(handle)));
592 }
593
594 found.map(|h| ViewAudioHandle(Some(h)))
595 }
596
597 pub(super) fn on_audio_decoded(&self, audio: &AudioDecoded) -> Option<ViewAudioHandle> {
598 let mut app = self.write();
604
605 let mut found = None;
606 app.loading_audios.retain(|i| {
607 if let Some(h) = i.upgrade() {
608 if found.is_none() && h.2 == audio.id {
609 found = Some(h);
610 return !audio.is_full;
611 }
612 true
613 } else {
614 false
615 }
616 });
617
618 found.map(|h| ViewAudioHandle(Some(h)))
619 }
620
621 pub(super) fn on_audio_error(&self, id: AudioId) -> Option<ViewAudioHandle> {
622 let mut app = self.write();
623
624 let mut found = None;
625 app.loading_audios.retain(|i| {
626 if let Some(h) = i.upgrade() {
627 if found.is_none() && h.2 == id {
628 found = Some(h);
629 return false;
630 }
631 true
632 } else {
633 false
634 }
635 });
636
637 found.map(|h| ViewAudioHandle(Some(h)))
640 }
641
642 pub(crate) fn on_frame_rendered(&self, id: WindowId) {
643 let mut vp = self.write();
644 if let Some(c) = vp.pending_frames.get_mut(&id) {
645 *c = c.saturating_sub(1);
646 }
647 }
648
649 pub(crate) fn on_frame_image(&self, data: &ImageDecoded) -> ViewImageHandle {
650 ViewImageHandle(Some(ArcEq::new((APP.id().unwrap(), self.generation(), data.meta.id))))
651 }
652
653 pub(super) fn on_image_encoded(&self, task_id: ImageEncodeId, data: IpcBytes) {
654 self.on_image_encode_result(task_id, Ok(data));
655 }
656 pub(super) fn on_image_encode_error(&self, task_id: ImageEncodeId, error: Txt) {
657 self.on_image_encode_result(task_id, Err(EncodeError::Encode(error)));
658 }
659 fn on_image_encode_result(&self, task_id: ImageEncodeId, result: std::result::Result<IpcBytes, EncodeError>) {
660 let mut app = self.write();
661 app.encoding_images.retain(move |r| {
662 let done = r.task_id == task_id;
663 if done {
664 let _ = r.listener.send_blocking(result.clone());
665 }
666 !done
667 })
668 }
669
670 pub(crate) fn on_message_dlg_response(&self, id: zng_view_api::dialog::DialogId, response: MsgDialogResponse) {
671 let mut app = self.write();
672 if let Some(i) = app.message_dialogs.iter().position(|(i, _)| *i == id) {
673 let (_, r) = app.message_dialogs.swap_remove(i);
674 r.respond(response);
675 }
676 }
677
678 pub(crate) fn on_file_dlg_response(&self, id: zng_view_api::dialog::DialogId, response: FileDialogResponse) {
679 let mut app = self.write();
680 if let Some(i) = app.file_dialogs.iter().position(|(i, _)| *i == id) {
681 let (_, r) = app.file_dialogs.swap_remove(i);
682 r.respond(response);
683 }
684 }
685
686 pub(crate) fn on_notification_dlg_response(&self, id: zng_view_api::dialog::DialogId, response: NotificationResponse) {
687 let mut app = self.write();
688 if let Some(i) = app.notifications.iter().position(|(i, _, _)| *i == id) {
689 let (_, _, r) = app.notifications.swap_remove(i);
690 r.respond(response);
691 }
692 }
693
694 pub(super) fn on_respawned(&self, _gen: ViewProcessGen) {
695 let mut app = self.write();
696 app.pending_frames.clear();
697 for (_, r) in app.message_dialogs.drain(..) {
698 r.respond(MsgDialogResponse::Error(Txt::from_static("respawn")));
699 }
700 for (_, r) in app.file_dialogs.drain(..) {
701 r.respond(FileDialogResponse::Error(Txt::from_static("respawn")));
702 }
703 for (_, _, r) in app.notifications.drain(..) {
704 r.respond(NotificationResponse::Error(Txt::from_static("respawn")));
705 }
706 }
707
708 pub(crate) fn exit(&self) {
709 *VIEW_PROCESS_SV.write() = None;
710 }
711
712 pub(crate) fn ping(&self) {
713 let mut app = self.write();
714 let count = app.ping_count.wrapping_add(1);
715 if let Ok(c) = app.process.ping(count)
716 && c != count
717 {
718 tracing::error!("incorrect ping response, expected {count}, was {c}");
719 }
720 app.ping_count = count;
721 }
722
723 pub(crate) fn on_pong(&self, count: u16) {
724 let expected = self.read().ping_count;
725 if expected > count + 1 {
726 tracing::warn!("unexpected pong event, expected {expected}, was {count}");
729 }
730 }
731}
732impl ViewProcessService {
733 #[must_use = "if `true` all current WinId, DevId and MonId are invalid"]
734 fn check_generation(&mut self) -> bool {
735 let vp_gen = self.process.generation();
736 let invalid = vp_gen != self.data_generation;
737 if invalid {
738 self.data_generation = vp_gen;
739 self.input_device_ids.clear();
740 self.monitor_ids.clear();
741 }
742 invalid
743 }
744}
745
746event_args! {
747 pub struct ViewProcessInitedArgs {
749 pub info: zng_view_api::ViewProcessInfo,
751
752 ..
753
754 fn is_in_target(&self, _id: WidgetId) -> bool {
756 true
757 }
758 }
759
760 pub struct ViewProcessSuspendedArgs {
762
763 ..
764
765 fn is_in_target(&self, _id: WidgetId) -> bool {
767 true
768 }
769 }
770}
771impl std::ops::Deref for ViewProcessInitedArgs {
772 type Target = zng_view_api::ViewProcessInfo;
773
774 fn deref(&self) -> &Self::Target {
775 &self.info
776 }
777}
778
779event! {
780 pub static VIEW_PROCESS_INITED_EVENT: ViewProcessInitedArgs;
782 pub static VIEW_PROCESS_SUSPENDED_EVENT: ViewProcessSuspendedArgs;
787}
788
789#[derive(Debug, Clone, PartialEq)]
791#[non_exhaustive]
792pub struct WindowOpenData {
793 pub state: WindowStateAll,
795
796 pub monitor: Option<MonitorId>,
798
799 pub position: (PxPoint, DipPoint),
803 pub size: DipSize,
805
806 pub scale_factor: Factor,
808
809 pub refresh_rate: Frequency,
811
812 pub render_mode: RenderMode,
814
815 pub safe_padding: DipSideOffsets,
821}
822impl WindowOpenData {
823 pub(crate) fn new(data: zng_view_api::window::WindowOpenData, map_monitor: impl FnOnce(ApiMonitorId) -> MonitorId) -> Self {
824 WindowOpenData {
825 state: data.state,
826 monitor: data.monitor.map(map_monitor),
827 position: data.position,
828 size: data.size,
829 scale_factor: data.scale_factor,
830 render_mode: data.render_mode,
831 safe_padding: data.safe_padding,
832 refresh_rate: data.refresh_rate,
833 }
834 }
835}
836
837#[derive(Debug, Clone, PartialEq, Eq)]
841#[must_use = "the window is closed when all clones of the handle are dropped"]
842pub struct ViewWindow(ArcEq<ViewWindowData>);
843impl ViewWindow {
844 pub fn generation(&self) -> ViewProcessGen {
846 self.0.generation
847 }
848
849 pub fn set_title(&self, title: Txt) -> Result<()> {
851 self.0.call(|id, p| p.set_title(id, title))
852 }
853
854 pub fn set_visible(&self, visible: bool) -> Result<()> {
856 self.0.call(|id, p| p.set_visible(id, visible))
857 }
858
859 pub fn set_always_on_top(&self, always_on_top: bool) -> Result<()> {
861 self.0.call(|id, p| p.set_always_on_top(id, always_on_top))
862 }
863
864 pub fn set_movable(&self, movable: bool) -> Result<()> {
866 self.0.call(|id, p| p.set_movable(id, movable))
867 }
868
869 pub fn set_resizable(&self, resizable: bool) -> Result<()> {
871 self.0.call(|id, p| p.set_resizable(id, resizable))
872 }
873
874 pub fn set_icon(&self, icon: Option<&ViewImageHandle>) -> Result<()> {
876 self.0.call(|id, p| {
877 if let Some(icon) = icon.and_then(|i| i.0.as_ref()) {
878 if p.generation() == icon.1 {
879 p.set_icon(id, Some(icon.2))
880 } else {
881 Err(ChannelError::disconnected())
882 }
883 } else {
884 p.set_icon(id, None)
885 }
886 })
887 }
888
889 pub fn set_cursor(&self, cursor: Option<CursorIcon>) -> Result<()> {
891 self.0.call(|id, p| p.set_cursor(id, cursor))
892 }
893
894 pub fn set_cursor_image(&self, cursor: Option<&ViewImageHandle>, hotspot: PxPoint) -> Result<()> {
901 self.0.call(|id, p| {
902 if let Some(cur) = cursor.and_then(|i| i.0.as_ref()) {
903 if p.generation() == cur.1 {
904 p.set_cursor_image(id, Some(zng_view_api::window::CursorImage::new(cur.2, hotspot)))
905 } else {
906 Err(ChannelError::disconnected())
907 }
908 } else {
909 p.set_cursor_image(id, None)
910 }
911 })
912 }
913
914 pub fn set_taskbar_visible(&self, visible: bool) -> Result<()> {
916 self.0.call(|id, p| p.set_taskbar_visible(id, visible))
917 }
918
919 pub fn bring_to_top(&self) -> Result<()> {
921 self.0.call(|id, p| p.bring_to_top(id))
922 }
923
924 pub fn set_state(&self, state: WindowStateAll) -> Result<()> {
926 self.0.call(|id, p| p.set_state(id, state))
927 }
928
929 pub fn set_video_mode(&self, mode: VideoMode) -> Result<()> {
931 self.0.call(|id, p| p.set_video_mode(id, mode))
932 }
933
934 pub fn set_enabled_buttons(&self, buttons: WindowButton) -> Result<()> {
936 self.0.call(|id, p| p.set_enabled_buttons(id, buttons))
937 }
938
939 pub fn renderer(&self) -> ViewRenderer {
941 ViewRenderer(ArcEq::downgrade(&self.0))
942 }
943
944 pub fn set_capture_mode(&self, enabled: bool) -> Result<()> {
949 self.0.call(|id, p| p.set_capture_mode(id, enabled))
950 }
951
952 pub fn focus(&self) -> Result<FocusResult> {
956 self.0.call(|id, p| p.focus(id))
957 }
958
959 pub fn set_focus_indicator(&self, indicator: Option<FocusIndicator>) -> Result<()> {
962 self.0.call(|id, p| p.set_focus_indicator(id, indicator))
963 }
964
965 pub fn drag_move(&self) -> Result<()> {
969 self.0.call(|id, p| p.drag_move(id))
970 }
971
972 pub fn drag_resize(&self, direction: ResizeDirection) -> Result<()> {
976 self.0.call(|id, p| p.drag_resize(id, direction))
977 }
978
979 pub fn start_drag_drop(
985 &self,
986 data: Vec<DragDropData>,
987 allowed_effects: DragDropEffect,
988 ) -> Result<std::result::Result<DragDropId, DragDropError>> {
989 self.0.call(|id, p| p.start_drag_drop(id, data, allowed_effects))
990 }
991
992 pub fn drag_dropped(&self, drop_id: DragDropId, applied: DragDropEffect) -> Result<()> {
994 self.0.call(|id, p| p.drag_dropped(id, drop_id, applied))
995 }
996
997 pub fn open_title_bar_context_menu(&self, position: DipPoint) -> Result<()> {
999 self.0.call(|id, p| p.open_title_bar_context_menu(id, position))
1000 }
1001
1002 pub fn message_dialog(&self, dlg: MsgDialog, responder: ResponderVar<MsgDialogResponse>) -> Result<()> {
1007 let dlg_id = self.0.call(|id, p| p.message_dialog(id, dlg))?;
1008 VIEW_PROCESS.handle_write(self.0.app_id).message_dialogs.push((dlg_id, responder));
1009 Ok(())
1010 }
1011
1012 pub fn file_dialog(&self, dlg: FileDialog, responder: ResponderVar<FileDialogResponse>) -> Result<()> {
1017 let dlg_id = self.0.call(|id, p| p.file_dialog(id, dlg))?;
1018 VIEW_PROCESS.handle_write(self.0.app_id).file_dialogs.push((dlg_id, responder));
1019 Ok(())
1020 }
1021
1022 pub fn access_update(&self, update: zng_view_api::access::AccessTreeUpdate) -> Result<()> {
1024 self.0.call(|id, p| p.access_update(id, update))
1025 }
1026
1027 pub fn set_ime_area(&self, area: Option<DipRect>) -> Result<()> {
1031 self.0.call(|id, p| p.set_ime_area(id, area))
1032 }
1033
1034 pub fn set_system_shutdown_warn(&self, reason: Txt) -> Result<()> {
1045 self.0.call(move |id, p| p.set_system_shutdown_warn(id, reason))
1046 }
1047
1048 pub fn close(self) {
1050 drop(self)
1051 }
1052
1053 pub fn window_extension_raw(&self, extension_id: ApiExtensionId, request: ApiExtensionPayload) -> Result<ApiExtensionPayload> {
1055 self.0.call(|id, p| p.window_extension(id, extension_id, request))
1056 }
1057
1058 pub fn window_extension<I, O>(&self, extension_id: ApiExtensionId, request: &I) -> Result<std::result::Result<O, ApiExtensionRecvError>>
1060 where
1061 I: serde::Serialize,
1062 O: serde::de::DeserializeOwned,
1063 {
1064 let r = self.window_extension_raw(extension_id, ApiExtensionPayload::serialize(&request).unwrap())?;
1065 Ok(r.deserialize())
1066 }
1067
1068 pub fn downgrade(&self) -> WeakViewWindow {
1070 WeakViewWindow(ArcEq::downgrade(&self.0))
1071 }
1072}
1073#[derive(Debug, Clone)]
1075pub struct WeakViewWindow(WeakEq<ViewWindowData>);
1076impl PartialEq for WeakViewWindow {
1077 fn eq(&self, other: &Self) -> bool {
1078 sync::Weak::ptr_eq(&self.0, &other.0)
1079 }
1080}
1081impl Eq for WeakViewWindow {}
1082impl WeakViewWindow {
1083 pub fn upgrade(&self) -> Option<ViewWindow> {
1085 let d = self.0.upgrade()?;
1086 if d.generation == VIEW_PROCESS.generation() {
1087 Some(ViewWindow(d))
1088 } else {
1089 None
1090 }
1091 }
1092}
1093
1094#[derive(Clone, Debug)]
1096pub enum ViewWindowOrHeadless {
1097 Window(ViewWindow),
1099 Headless(ViewHeadless),
1101}
1102impl ViewWindowOrHeadless {
1103 pub fn renderer(&self) -> ViewRenderer {
1105 match self {
1106 ViewWindowOrHeadless::Window(w) => w.renderer(),
1107 ViewWindowOrHeadless::Headless(h) => h.renderer(),
1108 }
1109 }
1110
1111 pub fn window_extension_raw(&self, extension_id: ApiExtensionId, request: ApiExtensionPayload) -> Result<ApiExtensionPayload> {
1113 match self {
1114 ViewWindowOrHeadless::Window(w) => w.window_extension_raw(extension_id, request),
1115 ViewWindowOrHeadless::Headless(h) => h.window_extension_raw(extension_id, request),
1116 }
1117 }
1118
1119 pub fn window_extension<I, O>(&self, extension_id: ApiExtensionId, request: &I) -> Result<std::result::Result<O, ApiExtensionRecvError>>
1121 where
1122 I: serde::Serialize,
1123 O: serde::de::DeserializeOwned,
1124 {
1125 match self {
1126 ViewWindowOrHeadless::Window(w) => w.window_extension(extension_id, request),
1127 ViewWindowOrHeadless::Headless(h) => h.window_extension(extension_id, request),
1128 }
1129 }
1130}
1131impl From<ViewWindow> for ViewWindowOrHeadless {
1132 fn from(w: ViewWindow) -> Self {
1133 ViewWindowOrHeadless::Window(w)
1134 }
1135}
1136impl From<ViewHeadless> for ViewWindowOrHeadless {
1137 fn from(w: ViewHeadless) -> Self {
1138 ViewWindowOrHeadless::Headless(w)
1139 }
1140}
1141
1142#[derive(Debug)]
1143struct ViewAudioOutputData {
1144 app_id: AppId,
1145 id: ApiAudioOutputId,
1146 generation: ViewProcessGen,
1147 data: AudioOutputOpenData,
1148}
1149impl ViewAudioOutputData {
1150 fn call<R>(&self, f: impl FnOnce(ApiAudioOutputId, &mut Controller) -> Result<R>) -> Result<R> {
1151 let mut app = VIEW_PROCESS.handle_write(self.app_id);
1152 if app.check_generation() {
1153 Err(ChannelError::disconnected())
1154 } else {
1155 f(self.id, &mut app.process)
1156 }
1157 }
1158}
1159impl Drop for ViewAudioOutputData {
1160 fn drop(&mut self) {
1161 if VIEW_PROCESS.is_available() {
1162 let mut app = VIEW_PROCESS.handle_write(self.app_id);
1163 if self.generation == app.process.generation() {
1164 let _ = app.process.close_audio_output(self.id);
1165 }
1166 }
1167 }
1168}
1169
1170#[derive(Clone, Debug, PartialEq, Eq)]
1174#[must_use = "the audio output is disposed when all clones of the handle are dropped"]
1175pub struct ViewAudioOutput(ArcEq<ViewAudioOutputData>);
1176impl ViewAudioOutput {
1177 pub fn cue(&self, mix: AudioMix) -> Result<AudioPlayId> {
1179 self.0.call(|id, p| p.cue_audio(AudioPlayRequest::new(id, mix)))
1180 }
1181
1182 pub fn update(&self, cfg: AudioOutputConfig) -> Result<()> {
1184 self.0.call(|id, p| p.update_audio_output(AudioOutputUpdateRequest::new(id, cfg)))
1185 }
1186
1187 pub fn data(&self) -> &AudioOutputOpenData {
1189 &self.0.data
1190 }
1191
1192 pub fn downgrade(&self) -> WeakViewAudioOutput {
1194 WeakViewAudioOutput(ArcEq::downgrade(&self.0))
1195 }
1196}
1197#[derive(Clone, Debug, PartialEq, Eq)]
1199pub struct WeakViewAudioOutput(WeakEq<ViewAudioOutputData>);
1200impl WeakViewAudioOutput {
1201 pub fn upgrade(&self) -> Option<ViewAudioOutput> {
1203 let d = self.0.upgrade()?;
1204 if d.generation == VIEW_PROCESS.generation() {
1205 Some(ViewAudioOutput(d))
1206 } else {
1207 None
1208 }
1209 }
1210}
1211
1212#[derive(Debug)]
1213struct ViewWindowData {
1214 app_id: AppId,
1215 id: ApiWindowId,
1216 generation: ViewProcessGen,
1217}
1218impl ViewWindowData {
1219 fn call<R>(&self, f: impl FnOnce(ApiWindowId, &mut Controller) -> Result<R>) -> Result<R> {
1220 let mut app = VIEW_PROCESS.handle_write(self.app_id);
1221 if app.check_generation() {
1222 Err(ChannelError::disconnected())
1223 } else {
1224 f(self.id, &mut app.process)
1225 }
1226 }
1227}
1228impl Drop for ViewWindowData {
1229 fn drop(&mut self) {
1230 if VIEW_PROCESS.is_available() {
1231 let mut app = VIEW_PROCESS.handle_write(self.app_id);
1232 if self.generation == app.process.generation() {
1233 let _ = app.process.close(self.id);
1234 app.pending_frames.remove(&WindowId::from_raw(self.id.get()));
1235 }
1236 }
1237 }
1238}
1239
1240type Result<T> = std::result::Result<T, ChannelError>;
1241
1242#[derive(Clone, Debug, PartialEq, Eq)]
1246#[must_use = "the view is disposed when all clones of the handle are dropped"]
1247pub struct ViewHeadless(ArcEq<ViewWindowData>);
1248impl ViewHeadless {
1249 pub fn set_size(&self, size: DipSize, scale_factor: Factor) -> Result<()> {
1251 self.0.call(|id, p| p.set_headless_size(id, size, scale_factor))
1252 }
1253
1254 pub fn renderer(&self) -> ViewRenderer {
1256 ViewRenderer(ArcEq::downgrade(&self.0))
1257 }
1258
1259 pub fn window_extension_raw(&self, extension_id: ApiExtensionId, request: ApiExtensionPayload) -> Result<ApiExtensionPayload> {
1261 self.0.call(|id, p| p.window_extension(id, extension_id, request))
1262 }
1263
1264 pub fn window_extension<I, O>(&self, extension_id: ApiExtensionId, request: &I) -> Result<std::result::Result<O, ApiExtensionRecvError>>
1266 where
1267 I: serde::Serialize,
1268 O: serde::de::DeserializeOwned,
1269 {
1270 let r = self.window_extension_raw(extension_id, ApiExtensionPayload::serialize(&request).unwrap())?;
1271 Ok(r.deserialize())
1272 }
1273
1274 pub fn downgrade(&self) -> WeakViewHeadless {
1276 WeakViewHeadless(ArcEq::downgrade(&self.0))
1277 }
1278}
1279
1280#[derive(Clone, Debug)]
1282pub struct WeakViewHeadless(WeakEq<ViewWindowData>);
1283impl PartialEq for WeakViewHeadless {
1284 fn eq(&self, other: &Self) -> bool {
1285 sync::Weak::ptr_eq(&self.0, &other.0)
1286 }
1287}
1288impl WeakViewHeadless {
1289 pub fn upgrade(&self) -> Option<ViewHeadless> {
1291 let d = self.0.upgrade()?;
1292 if d.generation == VIEW_PROCESS.generation() {
1293 Some(ViewHeadless(d))
1294 } else {
1295 None
1296 }
1297 }
1298}
1299
1300#[derive(Clone, Debug, PartialEq, Eq)]
1305pub struct ViewRenderer(WeakEq<ViewWindowData>);
1306impl ViewRenderer {
1307 fn call<R>(&self, f: impl FnOnce(ApiWindowId, &mut Controller) -> Result<R>) -> Result<R> {
1308 if let Some(c) = self.0.upgrade() {
1309 c.call(f)
1310 } else {
1311 Err(ChannelError::disconnected())
1312 }
1313 }
1314
1315 pub fn generation(&self) -> Result<ViewProcessGen> {
1317 self.0.upgrade().map(|c| c.generation).ok_or(ChannelError::disconnected())
1318 }
1319
1320 pub fn use_image(&self, image: &ViewImageHandle) -> Result<ImageTextureId> {
1324 self.call(|id, p| {
1325 if let Some(img) = &image.0 {
1326 if p.generation() == img.1 {
1327 p.use_image(id, img.2)
1328 } else {
1329 Err(ChannelError::disconnected())
1330 }
1331 } else {
1332 Ok(ImageTextureId::INVALID)
1333 }
1334 })
1335 }
1336
1337 pub fn update_image_use(&mut self, tex_id: ImageTextureId, image: &ViewImageHandle, dirty_rect: Option<PxRect>) -> Result<bool> {
1344 self.call(|id, p| {
1345 if let Some(img) = &image.0 {
1346 if p.generation() == img.1 {
1347 p.update_image_use(id, tex_id, img.2, dirty_rect)
1348 } else {
1349 Err(ChannelError::disconnected())
1350 }
1351 } else {
1352 Ok(false)
1353 }
1354 })
1355 }
1356
1357 pub fn delete_image_use(&mut self, tex_id: ImageTextureId) -> Result<()> {
1359 self.call(|id, p| p.delete_image_use(id, tex_id))
1360 }
1361
1362 pub fn add_font_face(&self, bytes: IpcFontBytes, index: u32) -> Result<FontFaceId> {
1366 self.call(|id, p| p.add_font_face(id, bytes, index))
1367 }
1368
1369 pub fn delete_font_face(&self, font_face_id: FontFaceId) -> Result<()> {
1371 self.call(|id, p| p.delete_font_face(id, font_face_id))
1372 }
1373
1374 pub fn add_font(
1378 &self,
1379 font_face_id: FontFaceId,
1380 glyph_size: Px,
1381 options: FontOptions,
1382 variations: Vec<(FontVariationName, f32)>,
1383 ) -> Result<FontId> {
1384 self.call(|id, p| p.add_font(id, font_face_id, glyph_size, options, variations))
1385 }
1386
1387 pub fn delete_font(&self, font_id: FontId) -> Result<()> {
1389 self.call(|id, p| p.delete_font(id, font_id))
1390 }
1391
1392 pub fn frame_image(&self, mask: Option<ImageMaskMode>) -> Result<ViewImageHandle> {
1394 if let Some(c) = self.0.upgrade() {
1395 let id = c.call(|id, p| p.frame_image(id, mask))?;
1396 Ok(Self::add_frame_image(c.app_id, id))
1397 } else {
1398 Err(ChannelError::disconnected())
1399 }
1400 }
1401
1402 pub fn frame_image_rect(&self, rect: PxRect, mask: Option<ImageMaskMode>) -> Result<ViewImageHandle> {
1404 if let Some(c) = self.0.upgrade() {
1405 let id = c.call(|id, p| p.frame_image_rect(id, rect, mask))?;
1406 Ok(Self::add_frame_image(c.app_id, id))
1407 } else {
1408 Err(ChannelError::disconnected())
1409 }
1410 }
1411
1412 fn add_frame_image(app_id: AppId, id: ImageId) -> ViewImageHandle {
1413 if id == ImageId::INVALID {
1414 ViewImageHandle::dummy()
1415 } else {
1416 let mut app = VIEW_PROCESS.handle_write(app_id);
1417 let handle = ArcEq::new((APP.id().unwrap(), app.process.generation(), id));
1418 app.loading_images.push(ArcEq::downgrade(&handle));
1419
1420 ViewImageHandle(Some(handle))
1421 }
1422 }
1423
1424 pub fn render(&self, frame: FrameRequest) -> Result<()> {
1426 if let Some(w) = self.0.upgrade() {
1427 w.call(|id, p| p.render(id, frame))?;
1428 *VIEW_PROCESS
1429 .handle_write(w.app_id)
1430 .pending_frames
1431 .entry(WindowId::from_raw(w.id.get()))
1432 .or_default() += 1;
1433 Ok(())
1434 } else {
1435 Err(ChannelError::disconnected())
1436 }
1437 }
1438
1439 pub fn render_update(&self, frame: FrameUpdateRequest) -> Result<()> {
1441 if let Some(w) = self.0.upgrade() {
1442 w.call(|id, p| p.render_update(id, frame))?;
1443 *VIEW_PROCESS
1444 .handle_write(w.app_id)
1445 .pending_frames
1446 .entry(WindowId::from_raw(w.id.get()))
1447 .or_default() += 1;
1448 Ok(())
1449 } else {
1450 Err(ChannelError::disconnected())
1451 }
1452 }
1453
1454 pub fn render_extension_raw(&self, extension_id: ApiExtensionId, request: ApiExtensionPayload) -> Result<ApiExtensionPayload> {
1456 if let Some(w) = self.0.upgrade() {
1457 w.call(|id, p| p.render_extension(id, extension_id, request))
1458 } else {
1459 Err(ChannelError::disconnected())
1460 }
1461 }
1462
1463 pub fn render_extension<I, O>(&self, extension_id: ApiExtensionId, request: &I) -> Result<std::result::Result<O, ApiExtensionRecvError>>
1465 where
1466 I: serde::Serialize,
1467 O: serde::de::DeserializeOwned,
1468 {
1469 let r = self.render_extension_raw(extension_id, ApiExtensionPayload::serialize(&request).unwrap())?;
1470 Ok(r.deserialize())
1471 }
1472}
1473
1474type ViewImageHandleData = (AppId, ViewProcessGen, ImageId);
1475
1476#[must_use = "the image is disposed when all clones of the handle are dropped"]
1480#[derive(Clone, Debug, PartialEq, Eq)]
1481pub struct ViewImageHandle(Option<ArcEq<ViewImageHandleData>>);
1482impl ViewImageHandle {
1483 pub fn dummy() -> Self {
1485 ViewImageHandle(None)
1486 }
1487
1488 pub fn is_dummy(&self) -> bool {
1490 self.0.is_none()
1491 }
1492
1493 pub fn image_id(&self) -> ImageId {
1497 self.0.as_ref().map(|h| h.2).unwrap_or(ImageId::INVALID)
1498 }
1499
1500 pub fn app_id(&self) -> Option<AppId> {
1506 self.0.as_ref().map(|h| h.0.0)
1507 }
1508
1509 pub fn view_process_gen(&self) -> ViewProcessGen {
1515 self.0.as_ref().map(|h| h.1).unwrap_or(ViewProcessGen::INVALID)
1516 }
1517
1518 pub fn downgrade(&self) -> WeakViewImageHandle {
1520 match &self.0 {
1521 Some(h) => WeakViewImageHandle(ArcEq::downgrade(h)),
1522 None => WeakViewImageHandle(WeakEq::new()),
1523 }
1524 }
1525}
1526impl Drop for ViewImageHandle {
1527 fn drop(&mut self) {
1528 if let Some(h) = self.0.take()
1529 && Arc::strong_count(&h) == 1
1530 && let Some(app) = APP.id()
1531 {
1532 if h.0.0 != app {
1533 tracing::error!("image from app `{:?}` dropped in app `{:?}`", h.0, app);
1534 return;
1535 }
1536
1537 if VIEW_PROCESS.is_available() && VIEW_PROCESS.generation() == h.1 {
1538 let _ = VIEW_PROCESS.write().process.forget_image(h.2);
1539 }
1540 }
1541 }
1542}
1543#[derive(Clone, Debug)]
1551pub struct WeakViewImageHandle(WeakEq<ViewImageHandleData>);
1552impl PartialEq for WeakViewImageHandle {
1553 fn eq(&self, other: &Self) -> bool {
1554 sync::Weak::ptr_eq(&self.0, &other.0)
1555 }
1556}
1557impl WeakViewImageHandle {
1558 pub fn upgrade(&self) -> Option<ViewImageHandle> {
1562 let d = self.0.upgrade()?;
1563 if d.1 == VIEW_PROCESS.generation() {
1564 Some(ViewImageHandle(Some(d)))
1565 } else {
1566 None
1567 }
1568 }
1569}
1570
1571#[derive(Debug, Clone, PartialEq, Eq)]
1573#[non_exhaustive]
1574pub enum EncodeError {
1575 Encode(Txt),
1577 Dummy,
1582 Loading,
1584 Disconnected,
1586}
1587impl From<Txt> for EncodeError {
1588 fn from(e: Txt) -> Self {
1589 EncodeError::Encode(e)
1590 }
1591}
1592impl From<ChannelError> for EncodeError {
1593 fn from(_: ChannelError) -> Self {
1594 EncodeError::Disconnected
1595 }
1596}
1597impl fmt::Display for EncodeError {
1598 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1599 match self {
1600 EncodeError::Encode(e) => write!(f, "{e}"),
1601 EncodeError::Dummy => write!(f, "cannot encode dummy image"),
1602 EncodeError::Loading => write!(f, "cannot encode, image is still loading"),
1603 EncodeError::Disconnected => write!(f, "{}", ChannelError::disconnected()),
1604 }
1605 }
1606}
1607impl std::error::Error for EncodeError {}
1608
1609struct EncodeRequest {
1610 task_id: ImageEncodeId,
1611 listener: channel::Sender<std::result::Result<IpcBytes, EncodeError>>,
1612}
1613
1614type ClipboardResult<T> = std::result::Result<T, ClipboardError>;
1615
1616#[non_exhaustive]
1618pub struct ViewClipboard {}
1619impl ViewClipboard {
1620 pub fn read_text(&self) -> Result<ClipboardResult<Txt>> {
1624 match VIEW_PROCESS
1625 .try_write()?
1626 .process
1627 .read_clipboard(vec![ClipboardType::Text], true)?
1628 .map(|mut r| r.pop())
1629 {
1630 Ok(Some(ClipboardData::Text(t))) => Ok(Ok(t)),
1631 Err(e) => Ok(Err(e)),
1632 _ => Ok(Err(ClipboardError::Other(Txt::from_static("view-process returned incorrect type")))),
1633 }
1634 }
1635
1636 pub fn write_text(&self, txt: Txt) -> Result<ClipboardResult<()>> {
1640 VIEW_PROCESS
1641 .try_write()?
1642 .process
1643 .write_clipboard(vec![ClipboardData::Text(txt)])
1644 .map(|r| r.map(|_| ()))
1645 }
1646
1647 pub fn read_image(&self) -> Result<ClipboardResult<ViewImageHandle>> {
1651 let mut app = VIEW_PROCESS.try_write()?;
1652 match app.process.read_clipboard(vec![ClipboardType::Image], true)?.map(|mut r| r.pop()) {
1653 Ok(Some(ClipboardData::Image(id))) => {
1654 if id == ImageId::INVALID {
1655 Ok(Err(ClipboardError::Other(Txt::from_static("view-process returned invalid image"))))
1656 } else {
1657 let handle = ArcEq::new((APP.id().unwrap(), app.process.generation(), id));
1658 app.loading_images.push(ArcEq::downgrade(&handle));
1659 Ok(Ok(ViewImageHandle(Some(handle))))
1660 }
1661 }
1662 Err(e) => Ok(Err(e)),
1663 _ => Ok(Err(ClipboardError::Other(Txt::from_static("view-process returned incorrect type")))),
1664 }
1665 }
1666
1667 pub fn write_image(&self, img: &ViewImageHandle) -> Result<ClipboardResult<()>> {
1671 return VIEW_PROCESS
1672 .try_write()?
1673 .process
1674 .write_clipboard(vec![ClipboardData::Image(img.image_id())])
1675 .map(|r| r.map(|_| ()));
1676 }
1677
1678 pub fn read_paths(&self) -> Result<ClipboardResult<Vec<PathBuf>>> {
1682 match VIEW_PROCESS
1683 .try_write()?
1684 .process
1685 .read_clipboard(vec![ClipboardType::Paths], true)?
1686 .map(|mut r| r.pop())
1687 {
1688 Ok(Some(ClipboardData::Paths(f))) => Ok(Ok(f)),
1689 Err(e) => Ok(Err(e)),
1690 _ => Ok(Err(ClipboardError::Other(Txt::from_static("view-process returned incorrect type")))),
1691 }
1692 }
1693
1694 pub fn write_paths(&self, list: Vec<PathBuf>) -> Result<ClipboardResult<()>> {
1698 VIEW_PROCESS
1699 .try_write()?
1700 .process
1701 .write_clipboard(vec![ClipboardData::Paths(list)])
1702 .map(|r| r.map(|_| ()))
1703 }
1704
1705 pub fn read_extension(&self, data_type: Txt) -> Result<ClipboardResult<IpcBytes>> {
1709 match VIEW_PROCESS
1710 .try_write()?
1711 .process
1712 .read_clipboard(vec![ClipboardType::Extension(data_type.clone())], true)?
1713 .map(|mut r| r.pop())
1714 {
1715 Ok(Some(ClipboardData::Extension { data_type: rt, data })) if rt == data_type => Ok(Ok(data)),
1716 Err(e) => Ok(Err(e)),
1717 _ => Ok(Err(ClipboardError::Other(Txt::from_static("view-process returned incorrect type")))),
1718 }
1719 }
1720
1721 pub fn write_extension(&self, data_type: Txt, data: IpcBytes) -> Result<ClipboardResult<()>> {
1725 VIEW_PROCESS
1726 .try_write()?
1727 .process
1728 .write_clipboard(vec![ClipboardData::Extension { data_type, data }])
1729 .map(|r| r.map(|_| ()))
1730 }
1731}
1732
1733type ViewAudioHandleData = (AppId, ViewProcessGen, AudioId);
1734
1735#[must_use = "the audio is disposed when all clones of the handle are dropped"]
1739#[derive(Clone, Debug, PartialEq, Eq)]
1740pub struct ViewAudioHandle(Option<ArcEq<ViewAudioHandleData>>);
1741impl ViewAudioHandle {
1742 pub fn dummy() -> Self {
1744 ViewAudioHandle(None)
1745 }
1746
1747 pub fn is_dummy(&self) -> bool {
1749 self.0.is_none()
1750 }
1751
1752 pub fn audio_id(&self) -> AudioId {
1756 self.0.as_ref().map(|h| h.2).unwrap_or(AudioId::INVALID)
1757 }
1758
1759 pub fn app_id(&self) -> Option<AppId> {
1765 self.0.as_ref().map(|h| h.0.0)
1766 }
1767
1768 pub fn view_process_gen(&self) -> ViewProcessGen {
1774 self.0.as_ref().map(|h| h.1).unwrap_or(ViewProcessGen::INVALID)
1775 }
1776
1777 pub fn downgrade(&self) -> WeakViewAudioHandle {
1779 match &self.0 {
1780 Some(h) => WeakViewAudioHandle(ArcEq::downgrade(h)),
1781 None => WeakViewAudioHandle(WeakEq::new()),
1782 }
1783 }
1784}
1785impl Drop for ViewAudioHandle {
1786 fn drop(&mut self) {
1787 if let Some(h) = self.0.take()
1788 && Arc::strong_count(&h) == 1
1789 && let Some(app) = APP.id()
1790 {
1791 if h.0.0 != app {
1792 tracing::error!("audio from app `{:?}` dropped in app `{:?}`", h.0, app);
1793 return;
1794 }
1795
1796 if VIEW_PROCESS.is_available() && VIEW_PROCESS.generation() == h.1 {
1797 let _ = VIEW_PROCESS.write().process.forget_audio(h.2);
1798 }
1799 }
1800 }
1801}
1802#[derive(Clone, Debug, PartialEq, Eq)]
1810pub struct WeakViewAudioHandle(WeakEq<ViewAudioHandleData>);
1811impl WeakViewAudioHandle {
1812 pub fn upgrade(&self) -> Option<ViewAudioHandle> {
1816 let h = self.0.upgrade()?;
1817 if h.1 == VIEW_PROCESS.generation() {
1818 Some(ViewAudioHandle(Some(h)))
1819 } else {
1820 None
1821 }
1822 }
1823}
1824
1825zng_unique_id::unique_id_32! {
1826 pub struct AudioOutputId;
1835}
1836zng_unique_id::impl_unique_id_name!(AudioOutputId);
1837zng_unique_id::impl_unique_id_fmt!(AudioOutputId);
1838zng_unique_id::impl_unique_id_bytemuck!(AudioOutputId);
1839zng_var::impl_from_and_into_var! {
1840 fn from(name: &'static str) -> AudioOutputId {
1842 AudioOutputId::named(name)
1843 }
1844 fn from(name: String) -> AudioOutputId {
1846 AudioOutputId::named(name)
1847 }
1848 fn from(name: std::borrow::Cow<'static, str>) -> AudioOutputId {
1850 AudioOutputId::named(name)
1851 }
1852 fn from(name: char) -> AudioOutputId {
1854 AudioOutputId::named(name)
1855 }
1856 fn from(name: Txt) -> AudioOutputId {
1858 AudioOutputId::named(name)
1859 }
1860
1861 fn from(some: AudioOutputId) -> Option<AudioOutputId>;
1862}
1863impl serde::Serialize for AudioOutputId {
1864 fn serialize<S>(&self, serializer: S) -> std::result::Result<S::Ok, S::Error>
1865 where
1866 S: serde::Serializer,
1867 {
1868 let name = self.name();
1869 if name.is_empty() {
1870 use serde::ser::Error;
1871 return Err(S::Error::custom("cannot serialize unnamed `AudioOutputId`"));
1872 }
1873 name.serialize(serializer)
1874 }
1875}
1876impl<'de> serde::Deserialize<'de> for AudioOutputId {
1877 fn deserialize<D>(deserializer: D) -> std::result::Result<Self, D::Error>
1878 where
1879 D: serde::Deserializer<'de>,
1880 {
1881 let name = Txt::deserialize(deserializer)?;
1882 Ok(AudioOutputId::named(name))
1883 }
1884}