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 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#[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<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 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<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 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 pub fn is_busy(&self) -> bool {
327 self.read().pending_frames.values().copied().max().unwrap_or(0) > 1
328 }
329
330 pub fn respawn(&self) {
334 self.write().process.respawn()
335 }
336
337 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 pub fn third_party_licenses(&self) -> Result<Vec<crate::third_party::LicenseUsed>> {
356 self.write().process.third_party_licenses()
357 }
358
359 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 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 pub fn handle_disconnect(&self, vp_gen: ViewProcessGen) {
381 self.write().process.handle_disconnect(vp_gen)
382 }
383
384 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 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 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 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 true
489 } else {
490 false
491 }
492 });
493
494 if found.is_none() && meta.parent.is_some() {
503 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 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 found.map(|h| ViewImageHandle(Some(h)))
557 }
558
559 pub(super) fn on_audio_metadata(&self, meta: &AudioMetadata) -> Option<ViewAudioHandle> {
560 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 true
572 } else {
573 false
574 }
575 });
576
577 if found.is_none() && meta.parent.is_some() {
578 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 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 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 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 pub struct ViewProcessInitedArgs {
741 pub info: zng_view_api::ViewProcessInfo,
743
744 ..
745
746 fn is_in_target(&self, _id: WidgetId) -> bool {
748 true
749 }
750 }
751
752 pub struct ViewProcessSuspendedArgs {
754
755 ..
756
757 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 pub static VIEW_PROCESS_INITED_EVENT: ViewProcessInitedArgs;
774 pub static VIEW_PROCESS_SUSPENDED_EVENT: ViewProcessSuspendedArgs;
779}
780
781#[derive(Debug, Clone, PartialEq)]
783#[non_exhaustive]
784pub struct WindowOpenData {
785 pub state: WindowStateAll,
787
788 pub monitor: Option<MonitorId>,
790
791 pub position: (PxPoint, DipPoint),
795 pub size: DipSize,
797
798 pub scale_factor: Factor,
800
801 pub refresh_rate: Frequency,
803
804 pub render_mode: RenderMode,
806
807 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#[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 pub fn generation(&self) -> ViewProcessGen {
838 self.0.generation
839 }
840
841 pub fn set_title(&self, title: Txt) -> Result<()> {
843 self.0.call(|id, p| p.set_title(id, title))
844 }
845
846 pub fn set_visible(&self, visible: bool) -> Result<()> {
848 self.0.call(|id, p| p.set_visible(id, visible))
849 }
850
851 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 pub fn set_movable(&self, movable: bool) -> Result<()> {
858 self.0.call(|id, p| p.set_movable(id, movable))
859 }
860
861 pub fn set_resizable(&self, resizable: bool) -> Result<()> {
863 self.0.call(|id, p| p.set_resizable(id, resizable))
864 }
865
866 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 pub fn set_cursor(&self, cursor: Option<CursorIcon>) -> Result<()> {
883 self.0.call(|id, p| p.set_cursor(id, cursor))
884 }
885
886 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 pub fn set_taskbar_visible(&self, visible: bool) -> Result<()> {
908 self.0.call(|id, p| p.set_taskbar_visible(id, visible))
909 }
910
911 pub fn bring_to_top(&self) -> Result<()> {
913 self.0.call(|id, p| p.bring_to_top(id))
914 }
915
916 pub fn set_state(&self, state: WindowStateAll) -> Result<()> {
918 self.0.call(|id, p| p.set_state(id, state))
919 }
920
921 pub fn set_video_mode(&self, mode: VideoMode) -> Result<()> {
923 self.0.call(|id, p| p.set_video_mode(id, mode))
924 }
925
926 pub fn set_can_minimize(&self, can: bool) -> Result<()> {
928 self.0.call(|id, p| p.set_can_minimize(id, can))
929 }
930 pub fn set_can_maximize(&self, can: bool) -> Result<()> {
932 self.0.call(|id, p| p.set_can_maximize(id, can))
933 }
934 pub fn set_can_fullscreen(&self, can: bool) -> Result<()> {
936 self.0.call(|id, p| p.set_can_fullscreen(id, can))
937 }
938 pub fn set_can_close(&self, can: bool) -> Result<()> {
940 self.0.call(|id, p| p.set_can_close(id, can))
941 }
942
943 pub fn renderer(&self) -> ViewRenderer {
945 ViewRenderer(ArcEq::downgrade(&self.0))
946 }
947
948 pub fn set_capture_mode(&self, enabled: bool) -> Result<()> {
953 self.0.call(|id, p| p.set_capture_mode(id, enabled))
954 }
955
956 pub fn focus(&self) -> Result<FocusResult> {
960 self.0.call(|id, p| p.focus(id))
961 }
962
963 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 pub fn drag_move(&self) -> Result<()> {
973 self.0.call(|id, p| p.drag_move(id))
974 }
975
976 pub fn drag_resize(&self, direction: ResizeDirection) -> Result<()> {
980 self.0.call(|id, p| p.drag_resize(id, direction))
981 }
982
983 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 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 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 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 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 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 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 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 pub fn close(self) {
1054 drop(self)
1055 }
1056
1057 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 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 pub fn downgrade(&self) -> WeakViewWindow {
1074 WeakViewWindow(ArcEq::downgrade(&self.0))
1075 }
1076}
1077#[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 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#[derive(Clone, Debug)]
1100pub enum ViewWindowOrHeadless {
1101 Window(ViewWindow),
1103 Headless(ViewHeadless),
1105}
1106impl ViewWindowOrHeadless {
1107 pub fn renderer(&self) -> ViewRenderer {
1109 match self {
1110 ViewWindowOrHeadless::Window(w) => w.renderer(),
1111 ViewWindowOrHeadless::Headless(h) => h.renderer(),
1112 }
1113 }
1114
1115 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 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#[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 pub fn cue(&self, mix: AudioMix) -> Result<AudioPlayId> {
1183 self.0.call(|id, p| p.cue_audio(AudioPlayRequest::new(id, mix)))
1184 }
1185
1186 pub fn update(&self, cfg: AudioOutputConfig) -> Result<()> {
1188 self.0.call(|id, p| p.update_audio_output(AudioOutputUpdateRequest::new(id, cfg)))
1189 }
1190
1191 pub fn data(&self) -> &AudioOutputOpenData {
1193 &self.0.data
1194 }
1195
1196 pub fn downgrade(&self) -> WeakViewAudioOutput {
1198 WeakViewAudioOutput(ArcEq::downgrade(&self.0))
1199 }
1200}
1201#[derive(Clone, Debug, PartialEq, Eq)]
1203pub struct WeakViewAudioOutput(WeakEq<ViewAudioOutputData>);
1204impl WeakViewAudioOutput {
1205 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#[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 pub fn generation(&self) -> ViewProcessGen {
1255 self.0.generation
1256 }
1257
1258 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 pub fn renderer(&self) -> ViewRenderer {
1265 ViewRenderer(ArcEq::downgrade(&self.0))
1266 }
1267
1268 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 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 pub fn downgrade(&self) -> WeakViewHeadless {
1285 WeakViewHeadless(ArcEq::downgrade(&self.0))
1286 }
1287}
1288
1289#[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 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#[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 pub fn generation(&self) -> Result<ViewProcessGen> {
1326 self.0.upgrade().map(|c| c.generation).ok_or(ChannelError::disconnected())
1327 }
1328
1329 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 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 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 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 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 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 pub fn delete_font(&self, font_id: FontId) -> Result<()> {
1398 self.call(|id, p| p.delete_font(id, font_id))
1399 }
1400
1401 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 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 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 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 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 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#[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 pub fn dummy() -> Self {
1494 ViewImageHandle(None)
1495 }
1496
1497 pub fn is_dummy(&self) -> bool {
1499 self.0.is_none()
1500 }
1501
1502 pub fn image_id(&self) -> ImageId {
1506 self.0.as_ref().map(|h| h.2).unwrap_or(ImageId::INVALID)
1507 }
1508
1509 pub fn app_id(&self) -> Option<AppId> {
1515 self.0.as_ref().map(|h| h.0.0)
1516 }
1517
1518 pub fn view_process_gen(&self) -> ViewProcessGen {
1524 self.0.as_ref().map(|h| h.1).unwrap_or(ViewProcessGen::INVALID)
1525 }
1526
1527 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#[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 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#[derive(Debug, Clone, PartialEq, Eq)]
1582#[non_exhaustive]
1583pub enum EncodeError {
1584 Encode(Txt),
1586 Dummy,
1591 Loading,
1593 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#[non_exhaustive]
1627pub struct ViewClipboard {}
1628impl ViewClipboard {
1629 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 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 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 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 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 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 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 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#[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 pub fn dummy() -> Self {
1753 ViewAudioHandle(None)
1754 }
1755
1756 pub fn is_dummy(&self) -> bool {
1758 self.0.is_none()
1759 }
1760
1761 pub fn audio_id(&self) -> AudioId {
1765 self.0.as_ref().map(|h| h.2).unwrap_or(AudioId::INVALID)
1766 }
1767
1768 pub fn app_id(&self) -> Option<AppId> {
1774 self.0.as_ref().map(|h| h.0.0)
1775 }
1776
1777 pub fn view_process_gen(&self) -> ViewProcessGen {
1783 self.0.as_ref().map(|h| h.1).unwrap_or(ViewProcessGen::INVALID)
1784 }
1785
1786 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#[derive(Clone, Debug, PartialEq, Eq)]
1819pub struct WeakViewAudioHandle(WeakEq<ViewAudioHandleData>);
1820impl WeakViewAudioHandle {
1821 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 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 fn from(name: &'static str) -> AudioOutputId {
1851 AudioOutputId::named(name)
1852 }
1853 fn from(name: String) -> AudioOutputId {
1855 AudioOutputId::named(name)
1856 }
1857 fn from(name: std::borrow::Cow<'static, str>) -> AudioOutputId {
1859 AudioOutputId::named(name)
1860 }
1861 fn from(name: char) -> AudioOutputId {
1863 AudioOutputId::named(name)
1864 }
1865 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}