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, RwLock};
19use zng_app_context::app_local;
20use zng_layout::unit::PxDensity2d;
21use zng_layout::unit::{DipPoint, DipRect, DipSideOffsets, DipSize, Factor, Px, PxPoint, PxRect, PxSize};
22use zng_task::SignalOnce;
23use zng_txt::Txt;
24use zng_var::ResponderVar;
25use zng_view_api::{
26 self, DeviceEventsFilter, DragDropId, Event, FocusResult, ViewProcessGen,
27 api_extension::{ApiExtensionId, ApiExtensionName, ApiExtensionPayload, ApiExtensionRecvError, ApiExtensions},
28 dialog::{FileDialog, FileDialogResponse, MsgDialog, MsgDialogResponse},
29 drag_drop::{DragDropData, DragDropEffect, DragDropError},
30 font::{FontOptions, IpcFontBytes},
31 image::{ImageMaskMode, ImageRequest, ImageTextureId},
32 ipc::{IpcBytes, IpcBytesReceiver, ViewChannelError},
33 window::{
34 CursorIcon, FocusIndicator, FrameRequest, FrameUpdateRequest, HeadlessOpenData, HeadlessRequest, RenderMode, ResizeDirection,
35 VideoMode, WindowButton, WindowRequest, WindowStateAll,
36 },
37};
38
39pub(crate) use zng_view_api::{
40 Controller, raw_input::InputDeviceId as ApiDeviceId, window::MonitorId as ApiMonitorId, window::WindowId as ApiWindowId,
41};
42use zng_view_api::{
43 clipboard::{ClipboardData, ClipboardError, ClipboardType},
44 font::{FontFaceId, FontId, FontVariationName},
45 image::{ImageId, ImageLoadedData},
46};
47
48use self::raw_device_events::InputDeviceId;
49
50use super::{APP, AppId};
51
52#[expect(non_camel_case_types)]
54pub struct VIEW_PROCESS;
55struct ViewProcessService {
56 process: zng_view_api::Controller,
57 input_device_ids: HashMap<ApiDeviceId, InputDeviceId>,
58 monitor_ids: HashMap<ApiMonitorId, MonitorId>,
59
60 data_generation: ViewProcessGen,
61
62 extensions: ApiExtensions,
63
64 loading_images: Vec<sync::Weak<RwLock<ViewImageData>>>,
65 frame_images: Vec<sync::Weak<RwLock<ViewImageData>>>,
66 encoding_images: Vec<EncodeRequest>,
67
68 pending_frames: usize,
69
70 message_dialogs: Vec<(zng_view_api::dialog::DialogId, ResponderVar<MsgDialogResponse>)>,
71 file_dialogs: Vec<(zng_view_api::dialog::DialogId, ResponderVar<FileDialogResponse>)>,
72
73 ping_count: u16,
74}
75app_local! {
76 static VIEW_PROCESS_SV: Option<ViewProcessService> = None;
77}
78impl VIEW_PROCESS {
79 pub fn is_available(&self) -> bool {
82 APP.is_running() && VIEW_PROCESS_SV.read().is_some()
83 }
84
85 fn read(&self) -> MappedRwLockReadGuard<'_, ViewProcessService> {
86 VIEW_PROCESS_SV.read_map(|e| e.as_ref().expect("VIEW_PROCESS not available"))
87 }
88
89 fn write(&self) -> MappedRwLockWriteGuard<'_, ViewProcessService> {
90 VIEW_PROCESS_SV.write_map(|e| e.as_mut().expect("VIEW_PROCESS not available"))
91 }
92
93 fn try_write(&self) -> Result<MappedRwLockWriteGuard<'_, ViewProcessService>> {
94 let vp = VIEW_PROCESS_SV.write();
95 if let Some(w) = &*vp
96 && w.process.is_connected()
97 {
98 return Ok(MappedRwLockWriteGuard::map(vp, |w| w.as_mut().unwrap()));
99 }
100 Err(ViewChannelError::Disconnected)
101 }
102
103 fn check_app(&self, id: AppId) {
104 let actual = APP.id();
105 if Some(id) != actual {
106 panic!("cannot use view handle from app `{id:?}` in app `{actual:?}`");
107 }
108 }
109
110 fn handle_write(&self, id: AppId) -> MappedRwLockWriteGuard<'_, ViewProcessService> {
111 self.check_app(id);
112 self.write()
113 }
114
115 pub fn is_connected(&self) -> bool {
117 self.read().process.is_connected()
118 }
119
120 pub fn is_headless_with_render(&self) -> bool {
122 self.read().process.headless()
123 }
124
125 pub fn is_same_process(&self) -> bool {
127 self.read().process.same_process()
128 }
129
130 pub fn generation(&self) -> ViewProcessGen {
132 self.read().process.generation()
133 }
134
135 pub fn set_device_events_filter(&self, filter: DeviceEventsFilter) -> Result<()> {
140 self.write().process.set_device_events_filter(filter)
141 }
142
143 pub fn open_window(&self, config: WindowRequest) -> Result<()> {
150 let _s = tracing::debug_span!("VIEW_PROCESS.open_window").entered();
151 self.write().process.open_window(config)
152 }
153
154 pub fn open_headless(&self, config: HeadlessRequest) -> Result<()> {
164 let _s = tracing::debug_span!("VIEW_PROCESS.open_headless").entered();
165 self.write().process.open_headless(config)
166 }
167
168 pub fn add_image(&self, request: ImageRequest<IpcBytes>) -> Result<ViewImage> {
177 let mut app = self.write();
178 let id = app.process.add_image(request)?;
179 let img = ViewImage(Arc::new(RwLock::new(ViewImageData {
180 id: Some(id),
181 app_id: APP.id(),
182 generation: app.process.generation(),
183 size: PxSize::zero(),
184 partial_size: PxSize::zero(),
185 density: None,
186 is_opaque: false,
187 partial_pixels: None,
188 pixels: None,
189 is_mask: false,
190 done_signal: SignalOnce::new(),
191 })));
192 app.loading_images.push(Arc::downgrade(&img.0));
193 Ok(img)
194 }
195
196 pub fn add_image_pro(&self, request: ImageRequest<IpcBytesReceiver>) -> Result<ViewImage> {
207 let mut app = self.write();
208 let id = app.process.add_image_pro(request)?;
209 let img = ViewImage(Arc::new(RwLock::new(ViewImageData {
210 id: Some(id),
211 app_id: APP.id(),
212 generation: app.process.generation(),
213 size: PxSize::zero(),
214 partial_size: PxSize::zero(),
215 density: None,
216 is_opaque: false,
217 partial_pixels: None,
218 pixels: None,
219 is_mask: false,
220 done_signal: SignalOnce::new(),
221 })));
222 app.loading_images.push(Arc::downgrade(&img.0));
223 Ok(img)
224 }
225
226 pub fn clipboard(&self) -> Result<&ViewClipboard> {
228 if VIEW_PROCESS.is_connected() {
229 Ok(&ViewClipboard {})
230 } else {
231 Err(ViewChannelError::Disconnected)
232 }
233 }
234
235 pub fn image_decoders(&self) -> Result<Vec<Txt>> {
239 self.write().process.image_decoders()
240 }
241
242 pub fn image_encoders(&self) -> Result<Vec<Txt>> {
246 self.write().process.image_encoders()
247 }
248
249 pub fn pending_frames(&self) -> usize {
253 self.write().pending_frames
254 }
255
256 pub fn respawn(&self) {
260 self.write().process.respawn()
261 }
262
263 pub fn extension_id(&self, extension_name: impl Into<ApiExtensionName>) -> Result<Option<ApiExtensionId>> {
269 let me = self.read();
270 if me.process.is_connected() {
271 Ok(me.extensions.id(&extension_name.into()))
272 } else {
273 Err(ViewChannelError::Disconnected)
274 }
275 }
276
277 pub fn third_party_licenses(&self) -> Result<Vec<crate::third_party::LicenseUsed>> {
282 self.write().process.third_party_licenses()
283 }
284
285 pub fn app_extension_raw(&self, extension_id: ApiExtensionId, extension_request: ApiExtensionPayload) -> Result<ApiExtensionPayload> {
287 self.write().process.app_extension(extension_id, extension_request)
288 }
289
290 pub fn app_extension<I, O>(&self, extension_id: ApiExtensionId, request: &I) -> Result<std::result::Result<O, ApiExtensionRecvError>>
292 where
293 I: serde::Serialize,
294 O: serde::de::DeserializeOwned,
295 {
296 let payload = ApiExtensionPayload::serialize(&request).unwrap();
297 let response = self.write().process.app_extension(extension_id, payload)?;
298 Ok(response.deserialize::<O>())
299 }
300
301 pub fn handle_disconnect(&self, vp_gen: ViewProcessGen) {
307 self.write().process.handle_disconnect(vp_gen)
308 }
309
310 pub(super) fn start<F>(&self, view_process_exe: PathBuf, view_process_env: HashMap<Txt, Txt>, headless: bool, on_event: F)
312 where
313 F: FnMut(Event) + Send + 'static,
314 {
315 let _s = tracing::debug_span!("VIEW_PROCESS.start").entered();
316
317 let process = zng_view_api::Controller::start(view_process_exe, view_process_env, headless, on_event);
318 *VIEW_PROCESS_SV.write() = Some(ViewProcessService {
319 data_generation: process.generation(),
320 process,
321 input_device_ids: HashMap::default(),
322 monitor_ids: HashMap::default(),
323 loading_images: vec![],
324 encoding_images: vec![],
325 frame_images: vec![],
326 pending_frames: 0,
327 message_dialogs: vec![],
328 file_dialogs: vec![],
329 extensions: ApiExtensions::default(),
330 ping_count: 0,
331 });
332 }
333
334 pub(crate) fn on_window_opened(&self, window_id: WindowId, data: zng_view_api::window::WindowOpenData) -> (ViewWindow, WindowOpenData) {
335 let mut app = self.write();
336 let _ = app.check_generation();
337
338 let win = ViewWindow(Arc::new(ViewWindowData {
339 app_id: APP.id().unwrap(),
340 id: ApiWindowId::from_raw(window_id.get()),
341 generation: app.data_generation,
342 }));
343 drop(app);
344
345 let data = WindowOpenData::new(data, |id| self.monitor_id(id));
346
347 (win, data)
348 }
349 pub(super) fn input_device_id(&self, id: ApiDeviceId) -> InputDeviceId {
351 *self.write().input_device_ids.entry(id).or_insert_with(InputDeviceId::new_unique)
352 }
353
354 pub(super) fn monitor_id(&self, id: ApiMonitorId) -> MonitorId {
356 *self.write().monitor_ids.entry(id).or_insert_with(MonitorId::new_unique)
357 }
358
359 pub(super) fn handle_inited(&self, vp_gen: ViewProcessGen, extensions: ApiExtensions) {
365 let mut me = self.write();
366 me.extensions = extensions;
367 me.process.handle_inited(vp_gen);
368 }
369
370 pub(super) fn handle_suspended(&self) {
371 self.write().process.handle_suspended();
372 }
373
374 pub(crate) fn on_headless_opened(
375 &self,
376 id: WindowId,
377 data: zng_view_api::window::HeadlessOpenData,
378 ) -> (ViewHeadless, HeadlessOpenData) {
379 let mut app = self.write();
380 let _ = app.check_generation();
381
382 let surf = ViewHeadless(Arc::new(ViewWindowData {
383 app_id: APP.id().unwrap(),
384 id: ApiWindowId::from_raw(id.get()),
385 generation: app.data_generation,
386 }));
387
388 (surf, data)
389 }
390
391 fn loading_image_index(&self, id: ImageId) -> Option<usize> {
392 let mut app = self.write();
393
394 app.loading_images.retain(|i| i.strong_count() > 0);
396
397 app.loading_images.iter().position(|i| i.upgrade().unwrap().read().id == Some(id))
398 }
399
400 pub(super) fn on_image_metadata_loaded(
401 &self,
402 id: ImageId,
403 size: PxSize,
404 density: Option<PxDensity2d>,
405 is_mask: bool,
406 ) -> Option<ViewImage> {
407 if let Some(i) = self.loading_image_index(id) {
408 let img = self.read().loading_images[i].upgrade().unwrap();
409 {
410 let mut img = img.write();
411 img.size = size;
412 img.density = density;
413 img.is_mask = is_mask;
414 }
415 Some(ViewImage(img))
416 } else {
417 None
418 }
419 }
420
421 pub(super) fn on_image_partially_loaded(
422 &self,
423 id: ImageId,
424 partial_size: PxSize,
425 density: Option<PxDensity2d>,
426 is_opaque: bool,
427 is_mask: bool,
428 partial_pixels: IpcBytes,
429 ) -> Option<ViewImage> {
430 if let Some(i) = self.loading_image_index(id) {
431 let img = self.read().loading_images[i].upgrade().unwrap();
432 {
433 let mut img = img.write();
434 img.partial_size = partial_size;
435 img.density = density;
436 img.is_opaque = is_opaque;
437 img.partial_pixels = Some(partial_pixels);
438 img.is_mask = is_mask;
439 }
440 Some(ViewImage(img))
441 } else {
442 None
443 }
444 }
445
446 pub(super) fn on_image_loaded(&self, data: ImageLoadedData) -> Option<ViewImage> {
447 if let Some(i) = self.loading_image_index(data.id) {
448 let img = self.write().loading_images.swap_remove(i).upgrade().unwrap();
449 {
450 let mut img = img.write();
451 img.size = data.size;
452 img.partial_size = data.size;
453 img.density = data.density;
454 img.is_opaque = data.is_opaque;
455 img.pixels = Some(Ok(data.pixels));
456 img.partial_pixels = None;
457 img.is_mask = data.is_mask;
458 img.done_signal.set();
459 }
460 Some(ViewImage(img))
461 } else {
462 None
463 }
464 }
465
466 pub(super) fn on_image_error(&self, id: ImageId, error: Txt) -> Option<ViewImage> {
467 if let Some(i) = self.loading_image_index(id) {
468 let img = self.write().loading_images.swap_remove(i).upgrade().unwrap();
469 {
470 let mut img = img.write();
471 img.pixels = Some(Err(error));
472 img.done_signal.set();
473 }
474 Some(ViewImage(img))
475 } else {
476 None
477 }
478 }
479
480 pub(crate) fn on_frame_rendered(&self, _id: WindowId) {
481 let mut vp = self.write();
482 vp.pending_frames = vp.pending_frames.saturating_sub(1);
483 }
484
485 pub(crate) fn on_frame_image(&self, data: ImageLoadedData) -> ViewImage {
486 ViewImage(Arc::new(RwLock::new(ViewImageData {
487 app_id: APP.id(),
488 id: Some(data.id),
489 generation: self.generation(),
490 size: data.size,
491 partial_size: data.size,
492 density: data.density,
493 is_opaque: data.is_opaque,
494 partial_pixels: None,
495 pixels: Some(Ok(data.pixels)),
496 is_mask: data.is_mask,
497 done_signal: SignalOnce::new_set(),
498 })))
499 }
500
501 pub(super) fn on_frame_image_ready(&self, id: ImageId) -> Option<ViewImage> {
502 let mut app = self.write();
503
504 app.frame_images.retain(|i| i.strong_count() > 0);
506
507 let i = app.frame_images.iter().position(|i| i.upgrade().unwrap().read().id == Some(id));
508
509 i.map(|i| ViewImage(app.frame_images.swap_remove(i).upgrade().unwrap()))
510 }
511
512 pub(super) fn on_image_encoded(&self, id: ImageId, format: Txt, data: IpcBytes) {
513 self.on_image_encode_result(id, format, Ok(data));
514 }
515 pub(super) fn on_image_encode_error(&self, id: ImageId, format: Txt, error: Txt) {
516 self.on_image_encode_result(id, format, Err(EncodeError::Encode(error)));
517 }
518 fn on_image_encode_result(&self, id: ImageId, format: Txt, result: std::result::Result<IpcBytes, EncodeError>) {
519 let mut app = self.write();
520 app.encoding_images.retain(move |r| {
521 let done = r.image_id == id && r.format == format;
522 if done {
523 for sender in &r.listeners {
524 let _ = sender.send(result.clone());
525 }
526 }
527 !done
528 })
529 }
530
531 pub(crate) fn on_message_dlg_response(&self, id: zng_view_api::dialog::DialogId, response: MsgDialogResponse) {
532 let mut app = self.write();
533 if let Some(i) = app.message_dialogs.iter().position(|(i, _)| *i == id) {
534 let (_, r) = app.message_dialogs.swap_remove(i);
535 r.respond(response);
536 }
537 }
538
539 pub(crate) fn on_file_dlg_response(&self, id: zng_view_api::dialog::DialogId, response: FileDialogResponse) {
540 let mut app = self.write();
541 if let Some(i) = app.file_dialogs.iter().position(|(i, _)| *i == id) {
542 let (_, r) = app.file_dialogs.swap_remove(i);
543 r.respond(response);
544 }
545 }
546
547 pub(super) fn on_respawned(&self, _gen: ViewProcessGen) {
548 let mut app = self.write();
549 app.pending_frames = 0;
550 for (_, r) in app.message_dialogs.drain(..) {
551 r.respond(MsgDialogResponse::Error(Txt::from_static("respawn")));
552 }
553 }
554
555 pub(crate) fn exit(&self) {
556 *VIEW_PROCESS_SV.write() = None;
557 }
558
559 pub(crate) fn ping(&self) {
560 let mut app = self.write();
561 let count = app.ping_count.wrapping_add(1);
562 if let Ok(c) = app.process.ping(count)
563 && c != count
564 {
565 tracing::error!("incorrect ping response, expected {count}, was {c}");
566 }
567 app.ping_count = count;
568 }
569
570 pub(crate) fn on_pong(&self, count: u16) {
571 let expected = self.read().ping_count;
572 if expected != count {
573 tracing::warn!("unexpected pong event, expected {expected}, was {count}");
575 }
576 }
577}
578impl ViewProcessService {
579 #[must_use = "if `true` all current WinId, DevId and MonId are invalid"]
580 fn check_generation(&mut self) -> bool {
581 let vp_gen = self.process.generation();
582 let invalid = vp_gen != self.data_generation;
583 if invalid {
584 self.data_generation = vp_gen;
585 self.input_device_ids.clear();
586 self.monitor_ids.clear();
587 }
588 invalid
589 }
590}
591
592event_args! {
593 pub struct ViewProcessInitedArgs {
595 pub generation: ViewProcessGen,
597
598 pub is_respawn: bool,
603
604 pub extensions: ApiExtensions,
608
609 ..
610
611 fn delivery_list(&self, list: &mut UpdateDeliveryList) {
613 list.search_all()
614 }
615 }
616
617 pub struct ViewProcessSuspendedArgs {
619
620 ..
621
622 fn delivery_list(&self, list: &mut UpdateDeliveryList) {
624 list.search_all()
625 }
626 }
627}
628
629event! {
630 pub static VIEW_PROCESS_INITED_EVENT: ViewProcessInitedArgs;
632 pub static VIEW_PROCESS_SUSPENDED_EVENT: ViewProcessSuspendedArgs;
637}
638
639#[derive(Debug, Clone)]
641#[non_exhaustive]
642pub struct WindowOpenData {
643 pub state: WindowStateAll,
645
646 pub monitor: Option<MonitorId>,
648
649 pub position: (PxPoint, DipPoint),
653 pub size: DipSize,
655
656 pub scale_factor: Factor,
658
659 pub render_mode: RenderMode,
661
662 pub safe_padding: DipSideOffsets,
668}
669impl WindowOpenData {
670 pub(crate) fn new(data: zng_view_api::window::WindowOpenData, map_monitor: impl FnOnce(ApiMonitorId) -> MonitorId) -> Self {
671 WindowOpenData {
672 state: data.state,
673 monitor: data.monitor.map(map_monitor),
674 position: data.position,
675 size: data.size,
676 scale_factor: data.scale_factor,
677 render_mode: data.render_mode,
678 safe_padding: data.safe_padding,
679 }
680 }
681}
682
683#[derive(Debug, Clone)]
687#[must_use = "the window is closed when all clones of the handle are dropped"]
688pub struct ViewWindow(Arc<ViewWindowData>);
689impl PartialEq for ViewWindow {
690 fn eq(&self, other: &Self) -> bool {
691 Arc::ptr_eq(&self.0, &other.0)
692 }
693}
694impl Eq for ViewWindow {}
695
696impl ViewWindow {
697 pub fn generation(&self) -> ViewProcessGen {
699 self.0.generation
700 }
701
702 pub fn set_title(&self, title: Txt) -> Result<()> {
704 self.0.call(|id, p| p.set_title(id, title))
705 }
706
707 pub fn set_visible(&self, visible: bool) -> Result<()> {
709 self.0.call(|id, p| p.set_visible(id, visible))
710 }
711
712 pub fn set_always_on_top(&self, always_on_top: bool) -> Result<()> {
714 self.0.call(|id, p| p.set_always_on_top(id, always_on_top))
715 }
716
717 pub fn set_movable(&self, movable: bool) -> Result<()> {
719 self.0.call(|id, p| p.set_movable(id, movable))
720 }
721
722 pub fn set_resizable(&self, resizable: bool) -> Result<()> {
724 self.0.call(|id, p| p.set_resizable(id, resizable))
725 }
726
727 pub fn set_icon(&self, icon: Option<&ViewImage>) -> Result<()> {
729 self.0.call(|id, p| {
730 if let Some(icon) = icon {
731 let icon = icon.0.read();
732 if p.generation() == icon.generation {
733 p.set_icon(id, icon.id)
734 } else {
735 Err(ViewChannelError::Disconnected)
736 }
737 } else {
738 p.set_icon(id, None)
739 }
740 })
741 }
742
743 pub fn set_cursor(&self, cursor: Option<CursorIcon>) -> Result<()> {
745 self.0.call(|id, p| p.set_cursor(id, cursor))
746 }
747
748 pub fn set_cursor_image(&self, cursor: Option<&ViewImage>, hotspot: PxPoint) -> Result<()> {
755 self.0.call(|id, p| {
756 if let Some(cur) = cursor {
757 let cur = cur.0.read();
758 if p.generation() == cur.generation {
759 p.set_cursor_image(id, cur.id.map(|img| zng_view_api::window::CursorImage::new(img, hotspot)))
760 } else {
761 Err(ViewChannelError::Disconnected)
762 }
763 } else {
764 p.set_cursor_image(id, None)
765 }
766 })
767 }
768
769 pub fn set_taskbar_visible(&self, visible: bool) -> Result<()> {
771 self.0.call(|id, p| p.set_taskbar_visible(id, visible))
772 }
773
774 pub fn bring_to_top(&self) -> Result<()> {
776 self.0.call(|id, p| p.bring_to_top(id))
777 }
778
779 pub fn set_state(&self, state: WindowStateAll) -> Result<()> {
781 self.0.call(|id, p| p.set_state(id, state))
782 }
783
784 pub fn set_video_mode(&self, mode: VideoMode) -> Result<()> {
786 self.0.call(|id, p| p.set_video_mode(id, mode))
787 }
788
789 pub fn set_enabled_buttons(&self, buttons: WindowButton) -> Result<()> {
791 self.0.call(|id, p| p.set_enabled_buttons(id, buttons))
792 }
793
794 pub fn renderer(&self) -> ViewRenderer {
796 ViewRenderer(Arc::downgrade(&self.0))
797 }
798
799 pub fn set_capture_mode(&self, enabled: bool) -> Result<()> {
804 self.0.call(|id, p| p.set_capture_mode(id, enabled))
805 }
806
807 pub fn focus(&self) -> Result<FocusResult> {
811 self.0.call(|id, p| p.focus(id))
812 }
813
814 pub fn set_focus_indicator(&self, indicator: Option<FocusIndicator>) -> Result<()> {
817 self.0.call(|id, p| p.set_focus_indicator(id, indicator))
818 }
819
820 pub fn drag_move(&self) -> Result<()> {
824 self.0.call(|id, p| p.drag_move(id))
825 }
826
827 pub fn drag_resize(&self, direction: ResizeDirection) -> Result<()> {
831 self.0.call(|id, p| p.drag_resize(id, direction))
832 }
833
834 pub fn start_drag_drop(
840 &self,
841 data: Vec<DragDropData>,
842 allowed_effects: DragDropEffect,
843 ) -> Result<std::result::Result<DragDropId, DragDropError>> {
844 self.0.call(|id, p| p.start_drag_drop(id, data, allowed_effects))
845 }
846
847 pub fn drag_dropped(&self, drop_id: DragDropId, applied: DragDropEffect) -> Result<()> {
849 self.0.call(|id, p| p.drag_dropped(id, drop_id, applied))
850 }
851
852 pub fn open_title_bar_context_menu(&self, position: DipPoint) -> Result<()> {
854 self.0.call(|id, p| p.open_title_bar_context_menu(id, position))
855 }
856
857 pub fn message_dialog(&self, dlg: MsgDialog, responder: ResponderVar<MsgDialogResponse>) -> Result<()> {
862 let dlg_id = self.0.call(|id, p| p.message_dialog(id, dlg))?;
863 VIEW_PROCESS.handle_write(self.0.app_id).message_dialogs.push((dlg_id, responder));
864 Ok(())
865 }
866
867 pub fn file_dialog(&self, dlg: FileDialog, responder: ResponderVar<FileDialogResponse>) -> Result<()> {
872 let dlg_id = self.0.call(|id, p| p.file_dialog(id, dlg))?;
873 VIEW_PROCESS.handle_write(self.0.app_id).file_dialogs.push((dlg_id, responder));
874 Ok(())
875 }
876
877 pub fn access_update(&self, update: zng_view_api::access::AccessTreeUpdate) -> Result<()> {
879 self.0.call(|id, p| p.access_update(id, update))
880 }
881
882 pub fn set_ime_area(&self, area: Option<DipRect>) -> Result<()> {
886 self.0.call(|id, p| p.set_ime_area(id, area))
887 }
888
889 pub fn set_system_shutdown_warn(&self, reason: Txt) -> Result<()> {
900 self.0.call(move |id, p| p.set_system_shutdown_warn(id, reason))
901 }
902
903 pub fn close(self) {
905 drop(self)
906 }
907
908 pub fn window_extension_raw(&self, extension_id: ApiExtensionId, request: ApiExtensionPayload) -> Result<ApiExtensionPayload> {
910 self.0.call(|id, p| p.window_extension(id, extension_id, request))
911 }
912
913 pub fn window_extension<I, O>(&self, extension_id: ApiExtensionId, request: &I) -> Result<std::result::Result<O, ApiExtensionRecvError>>
915 where
916 I: serde::Serialize,
917 O: serde::de::DeserializeOwned,
918 {
919 let r = self.window_extension_raw(extension_id, ApiExtensionPayload::serialize(&request).unwrap())?;
920 Ok(r.deserialize())
921 }
922}
923
924#[derive(Clone, Debug)]
926pub enum ViewWindowOrHeadless {
927 Window(ViewWindow),
929 Headless(ViewHeadless),
931}
932impl ViewWindowOrHeadless {
933 pub fn renderer(&self) -> ViewRenderer {
935 match self {
936 ViewWindowOrHeadless::Window(w) => w.renderer(),
937 ViewWindowOrHeadless::Headless(h) => h.renderer(),
938 }
939 }
940
941 pub fn window_extension_raw(&self, extension_id: ApiExtensionId, request: ApiExtensionPayload) -> Result<ApiExtensionPayload> {
943 match self {
944 ViewWindowOrHeadless::Window(w) => w.window_extension_raw(extension_id, request),
945 ViewWindowOrHeadless::Headless(h) => h.window_extension_raw(extension_id, request),
946 }
947 }
948
949 pub fn window_extension<I, O>(&self, extension_id: ApiExtensionId, request: &I) -> Result<std::result::Result<O, ApiExtensionRecvError>>
951 where
952 I: serde::Serialize,
953 O: serde::de::DeserializeOwned,
954 {
955 match self {
956 ViewWindowOrHeadless::Window(w) => w.window_extension(extension_id, request),
957 ViewWindowOrHeadless::Headless(h) => h.window_extension(extension_id, request),
958 }
959 }
960}
961impl From<ViewWindow> for ViewWindowOrHeadless {
962 fn from(w: ViewWindow) -> Self {
963 ViewWindowOrHeadless::Window(w)
964 }
965}
966impl From<ViewHeadless> for ViewWindowOrHeadless {
967 fn from(w: ViewHeadless) -> Self {
968 ViewWindowOrHeadless::Headless(w)
969 }
970}
971
972#[derive(Debug)]
973struct ViewWindowData {
974 app_id: AppId,
975 id: ApiWindowId,
976 generation: ViewProcessGen,
977}
978impl ViewWindowData {
979 fn call<R>(&self, f: impl FnOnce(ApiWindowId, &mut Controller) -> Result<R>) -> Result<R> {
980 let mut app = VIEW_PROCESS.handle_write(self.app_id);
981 if app.check_generation() {
982 Err(ViewChannelError::Disconnected)
983 } else {
984 f(self.id, &mut app.process)
985 }
986 }
987}
988impl Drop for ViewWindowData {
989 fn drop(&mut self) {
990 if VIEW_PROCESS.is_available() {
991 let mut app = VIEW_PROCESS.handle_write(self.app_id);
992 if self.generation == app.process.generation() {
993 let _ = app.process.close(self.id);
994 }
995 }
996 }
997}
998type Result<T> = std::result::Result<T, ViewChannelError>;
999
1000#[derive(Clone, Debug)]
1004#[must_use = "the view is disposed when all clones of the handle are dropped"]
1005pub struct ViewHeadless(Arc<ViewWindowData>);
1006impl PartialEq for ViewHeadless {
1007 fn eq(&self, other: &Self) -> bool {
1008 Arc::ptr_eq(&self.0, &other.0)
1009 }
1010}
1011impl Eq for ViewHeadless {}
1012impl ViewHeadless {
1013 pub fn set_size(&self, size: DipSize, scale_factor: Factor) -> Result<()> {
1015 self.0.call(|id, p| p.set_headless_size(id, size, scale_factor))
1016 }
1017
1018 pub fn renderer(&self) -> ViewRenderer {
1020 ViewRenderer(Arc::downgrade(&self.0))
1021 }
1022
1023 pub fn window_extension_raw(&self, extension_id: ApiExtensionId, request: ApiExtensionPayload) -> Result<ApiExtensionPayload> {
1025 self.0.call(|id, p| p.window_extension(id, extension_id, request))
1026 }
1027
1028 pub fn window_extension<I, O>(&self, extension_id: ApiExtensionId, request: &I) -> Result<std::result::Result<O, ApiExtensionRecvError>>
1030 where
1031 I: serde::Serialize,
1032 O: serde::de::DeserializeOwned,
1033 {
1034 let r = self.window_extension_raw(extension_id, ApiExtensionPayload::serialize(&request).unwrap())?;
1035 Ok(r.deserialize())
1036 }
1037}
1038
1039#[derive(Clone, Debug)]
1044pub struct ViewRenderer(sync::Weak<ViewWindowData>);
1045impl PartialEq for ViewRenderer {
1046 fn eq(&self, other: &Self) -> bool {
1047 if let Some(s) = self.0.upgrade()
1048 && let Some(o) = other.0.upgrade()
1049 {
1050 Arc::ptr_eq(&s, &o)
1051 } else {
1052 false
1053 }
1054 }
1055}
1056impl Eq for ViewRenderer {}
1057
1058impl ViewRenderer {
1059 fn call<R>(&self, f: impl FnOnce(ApiWindowId, &mut Controller) -> Result<R>) -> Result<R> {
1060 if let Some(c) = self.0.upgrade() {
1061 c.call(f)
1062 } else {
1063 Err(ViewChannelError::Disconnected)
1064 }
1065 }
1066
1067 pub fn generation(&self) -> Result<ViewProcessGen> {
1069 self.0.upgrade().map(|c| c.generation).ok_or(ViewChannelError::Disconnected)
1070 }
1071
1072 pub fn use_image(&self, image: &ViewImage) -> Result<ImageTextureId> {
1076 self.call(|id, p| {
1077 let image = image.0.read();
1078 if p.generation() == image.generation {
1079 p.use_image(id, image.id.unwrap_or(ImageId::INVALID))
1080 } else {
1081 Err(ViewChannelError::Disconnected)
1082 }
1083 })
1084 }
1085
1086 pub fn update_image_use(&mut self, tex_id: ImageTextureId, image: &ViewImage) -> Result<()> {
1088 self.call(|id, p| {
1089 let image = image.0.read();
1090 if p.generation() == image.generation {
1091 p.update_image_use(id, tex_id, image.id.unwrap_or(ImageId::INVALID))
1092 } else {
1093 Err(ViewChannelError::Disconnected)
1094 }
1095 })
1096 }
1097
1098 pub fn delete_image_use(&mut self, tex_id: ImageTextureId) -> Result<()> {
1100 self.call(|id, p| p.delete_image_use(id, tex_id))
1101 }
1102
1103 pub fn add_font_face(&self, bytes: IpcFontBytes, index: u32) -> Result<FontFaceId> {
1107 self.call(|id, p| p.add_font_face(id, bytes, index))
1108 }
1109
1110 pub fn delete_font_face(&self, font_face_id: FontFaceId) -> Result<()> {
1112 self.call(|id, p| p.delete_font_face(id, font_face_id))
1113 }
1114
1115 pub fn add_font(
1119 &self,
1120 font_face_id: FontFaceId,
1121 glyph_size: Px,
1122 options: FontOptions,
1123 variations: Vec<(FontVariationName, f32)>,
1124 ) -> Result<FontId> {
1125 self.call(|id, p| p.add_font(id, font_face_id, glyph_size, options, variations))
1126 }
1127
1128 pub fn delete_font(&self, font_id: FontId) -> Result<()> {
1130 self.call(|id, p| p.delete_font(id, font_id))
1131 }
1132
1133 pub fn frame_image(&self, mask: Option<ImageMaskMode>) -> Result<ViewImage> {
1135 if let Some(c) = self.0.upgrade() {
1136 let id = c.call(|id, p| p.frame_image(id, mask))?;
1137 Ok(Self::add_frame_image(c.app_id, id))
1138 } else {
1139 Err(ViewChannelError::Disconnected)
1140 }
1141 }
1142
1143 pub fn frame_image_rect(&self, rect: PxRect, mask: Option<ImageMaskMode>) -> Result<ViewImage> {
1145 if let Some(c) = self.0.upgrade() {
1146 let id = c.call(|id, p| p.frame_image_rect(id, rect, mask))?;
1147 Ok(Self::add_frame_image(c.app_id, id))
1148 } else {
1149 Err(ViewChannelError::Disconnected)
1150 }
1151 }
1152
1153 fn add_frame_image(app_id: AppId, id: ImageId) -> ViewImage {
1154 if id == ImageId::INVALID {
1155 ViewImage::dummy(None)
1156 } else {
1157 let mut app = VIEW_PROCESS.handle_write(app_id);
1158 let img = ViewImage(Arc::new(RwLock::new(ViewImageData {
1159 app_id: Some(app_id),
1160 id: Some(id),
1161 generation: app.process.generation(),
1162 size: PxSize::zero(),
1163 partial_size: PxSize::zero(),
1164 density: None,
1165 is_opaque: false,
1166 partial_pixels: None,
1167 pixels: None,
1168 is_mask: false,
1169 done_signal: SignalOnce::new(),
1170 })));
1171
1172 app.loading_images.push(Arc::downgrade(&img.0));
1173 app.frame_images.push(Arc::downgrade(&img.0));
1174
1175 img
1176 }
1177 }
1178
1179 pub fn render(&self, frame: FrameRequest) -> Result<()> {
1181 let _s = tracing::debug_span!("ViewRenderer.render").entered();
1182
1183 if let Some(w) = self.0.upgrade() {
1184 w.call(|id, p| p.render(id, frame))?;
1185 VIEW_PROCESS.handle_write(w.app_id).pending_frames += 1;
1186 Ok(())
1187 } else {
1188 Err(ViewChannelError::Disconnected)
1189 }
1190 }
1191
1192 pub fn render_update(&self, frame: FrameUpdateRequest) -> Result<()> {
1194 let _s = tracing::debug_span!("ViewRenderer.render_update").entered();
1195
1196 if let Some(w) = self.0.upgrade() {
1197 w.call(|id, p| p.render_update(id, frame))?;
1198 VIEW_PROCESS.handle_write(w.app_id).pending_frames += 1;
1199 Ok(())
1200 } else {
1201 Err(ViewChannelError::Disconnected)
1202 }
1203 }
1204
1205 pub fn render_extension_raw(&self, extension_id: ApiExtensionId, request: ApiExtensionPayload) -> Result<ApiExtensionPayload> {
1207 if let Some(w) = self.0.upgrade() {
1208 w.call(|id, p| p.render_extension(id, extension_id, request))
1209 } else {
1210 Err(ViewChannelError::Disconnected)
1211 }
1212 }
1213
1214 pub fn render_extension<I, O>(&self, extension_id: ApiExtensionId, request: &I) -> Result<std::result::Result<O, ApiExtensionRecvError>>
1216 where
1217 I: serde::Serialize,
1218 O: serde::de::DeserializeOwned,
1219 {
1220 let r = self.render_extension_raw(extension_id, ApiExtensionPayload::serialize(&request).unwrap())?;
1221 Ok(r.deserialize())
1222 }
1223}
1224
1225#[must_use = "the image is disposed when all clones of the handle are dropped"]
1229#[derive(Clone)]
1230pub struct ViewImage(Arc<RwLock<ViewImageData>>);
1231impl PartialEq for ViewImage {
1232 fn eq(&self, other: &Self) -> bool {
1233 Arc::ptr_eq(&self.0, &other.0)
1234 }
1235}
1236impl Eq for ViewImage {}
1237impl std::hash::Hash for ViewImage {
1238 fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
1239 let ptr = Arc::as_ptr(&self.0) as usize;
1240 ptr.hash(state)
1241 }
1242}
1243impl fmt::Debug for ViewImage {
1244 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1245 f.debug_struct("ViewImage")
1246 .field("loaded", &self.is_loaded())
1247 .field("error", &self.error())
1248 .field("size", &self.size())
1249 .field("density", &self.density())
1250 .field("is_opaque", &self.is_opaque())
1251 .field("is_mask", &self.is_mask())
1252 .field("generation", &self.generation())
1253 .finish_non_exhaustive()
1254 }
1255}
1256
1257struct ViewImageData {
1258 app_id: Option<AppId>,
1259 id: Option<ImageId>,
1260 generation: ViewProcessGen,
1261
1262 size: PxSize,
1263 partial_size: PxSize,
1264 density: Option<PxDensity2d>,
1265 is_opaque: bool,
1266
1267 partial_pixels: Option<IpcBytes>,
1268 pixels: Option<std::result::Result<IpcBytes, Txt>>,
1269 is_mask: bool,
1270
1271 done_signal: SignalOnce,
1272}
1273impl Drop for ViewImageData {
1274 fn drop(&mut self) {
1275 if let Some(id) = self.id {
1276 let app_id = self.app_id.unwrap();
1277 if let Some(app) = APP.id() {
1278 if app_id != app {
1279 tracing::error!("image from app `{:?}` dropped in app `{:?}`", app_id, app);
1280 }
1281
1282 if VIEW_PROCESS.is_available() && VIEW_PROCESS.generation() == self.generation {
1283 let _ = VIEW_PROCESS.write().process.forget_image(id);
1284 }
1285 }
1286 }
1287 }
1288}
1289
1290impl ViewImage {
1291 pub fn id(&self) -> Option<ImageId> {
1293 self.0.read().id
1294 }
1295
1296 pub fn is_dummy(&self) -> bool {
1298 self.0.read().id.is_none()
1299 }
1300
1301 pub fn is_loaded(&self) -> bool {
1303 self.0.read().pixels.as_ref().map(|r| r.is_ok()).unwrap_or(false)
1304 }
1305
1306 pub fn is_partially_loaded(&self) -> bool {
1308 self.0.read().partial_pixels.is_some()
1309 }
1310
1311 pub fn is_error(&self) -> bool {
1315 self.0.read().pixels.as_ref().map(|r| r.is_err()).unwrap_or(false)
1316 }
1317
1318 pub fn error(&self) -> Option<Txt> {
1320 self.0.read().pixels.as_ref().and_then(|s| s.as_ref().err().cloned())
1321 }
1322
1323 pub fn size(&self) -> PxSize {
1325 self.0.read().size
1326 }
1327
1328 pub fn partial_size(&self) -> PxSize {
1334 self.0.read().partial_size
1335 }
1336
1337 pub fn density(&self) -> Option<PxDensity2d> {
1340 self.0.read().density
1341 }
1342
1343 pub fn is_opaque(&self) -> bool {
1345 self.0.read().is_opaque
1346 }
1347
1348 pub fn is_mask(&self) -> bool {
1350 self.0.read().is_mask
1351 }
1352
1353 pub fn partial_pixels(&self) -> Option<Vec<u8>> {
1360 self.0.read().partial_pixels.as_ref().map(|r| r[..].to_vec())
1361 }
1362
1363 pub fn pixels(&self) -> Option<IpcBytes> {
1374 self.0.read().pixels.as_ref().and_then(|r| r.as_ref().ok()).cloned()
1375 }
1376
1377 pub fn app_id(&self) -> Option<AppId> {
1379 self.0.read().app_id
1380 }
1381
1382 pub fn generation(&self) -> ViewProcessGen {
1384 self.0.read().generation
1385 }
1386
1387 pub fn downgrade(&self) -> WeakViewImage {
1389 WeakViewImage(Arc::downgrade(&self.0))
1390 }
1391
1392 pub fn dummy(error: Option<Txt>) -> Self {
1394 ViewImage(Arc::new(RwLock::new(ViewImageData {
1395 app_id: None,
1396 id: None,
1397 generation: ViewProcessGen::INVALID,
1398 size: PxSize::zero(),
1399 partial_size: PxSize::zero(),
1400 density: None,
1401 is_opaque: true,
1402 partial_pixels: None,
1403 pixels: if let Some(e) = error {
1404 Some(Err(e))
1405 } else {
1406 Some(Ok(IpcBytes::from_slice(&[])))
1407 },
1408 is_mask: false,
1409 done_signal: SignalOnce::new_set(),
1410 })))
1411 }
1412
1413 pub fn awaiter(&self) -> SignalOnce {
1415 self.0.read().done_signal.clone()
1416 }
1417
1418 pub async fn encode(&self, format: Txt) -> std::result::Result<IpcBytes, EncodeError> {
1424 self.awaiter().await;
1425
1426 if let Some(e) = self.error() {
1427 return Err(EncodeError::Encode(e));
1428 }
1429
1430 let receiver = {
1431 let img = self.0.read();
1432 if let Some(id) = img.id {
1433 let mut app = VIEW_PROCESS.handle_write(img.app_id.unwrap());
1434
1435 app.process.encode_image(id, format.clone())?;
1436
1437 let (sender, receiver) = flume::bounded(1);
1438 if let Some(entry) = app.encoding_images.iter_mut().find(|r| r.image_id == id && r.format == format) {
1439 entry.listeners.push(sender);
1440 } else {
1441 app.encoding_images.push(EncodeRequest {
1442 image_id: id,
1443 format,
1444 listeners: vec![sender],
1445 });
1446 }
1447 receiver
1448 } else {
1449 return Err(EncodeError::Dummy);
1450 }
1451 };
1452
1453 receiver.recv_async().await?
1454 }
1455}
1456
1457#[derive(Debug, Clone, PartialEq, Eq)]
1459#[non_exhaustive]
1460pub enum EncodeError {
1461 Encode(Txt),
1463 Dummy,
1468 Disconnected,
1470}
1471impl From<Txt> for EncodeError {
1472 fn from(e: Txt) -> Self {
1473 EncodeError::Encode(e)
1474 }
1475}
1476impl From<ViewChannelError> for EncodeError {
1477 fn from(_: ViewChannelError) -> Self {
1478 EncodeError::Disconnected
1479 }
1480}
1481impl From<flume::RecvError> for EncodeError {
1482 fn from(_: flume::RecvError) -> Self {
1483 EncodeError::Disconnected
1484 }
1485}
1486impl fmt::Display for EncodeError {
1487 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1488 match self {
1489 EncodeError::Encode(e) => write!(f, "{e}"),
1490 EncodeError::Dummy => write!(f, "cannot encode dummy image"),
1491 EncodeError::Disconnected => write!(f, "{}", ViewChannelError::Disconnected),
1492 }
1493 }
1494}
1495impl std::error::Error for EncodeError {}
1496
1497#[derive(Clone)]
1503pub struct WeakViewImage(sync::Weak<RwLock<ViewImageData>>);
1504impl WeakViewImage {
1505 pub fn upgrade(&self) -> Option<ViewImage> {
1509 self.0.upgrade().map(ViewImage)
1510 }
1511}
1512
1513struct EncodeRequest {
1514 image_id: ImageId,
1515 format: Txt,
1516 listeners: Vec<flume::Sender<std::result::Result<IpcBytes, EncodeError>>>,
1517}
1518
1519type ClipboardResult<T> = std::result::Result<T, ClipboardError>;
1520
1521#[non_exhaustive]
1523pub struct ViewClipboard {}
1524impl ViewClipboard {
1525 pub fn read_text(&self) -> Result<ClipboardResult<Txt>> {
1529 match VIEW_PROCESS.try_write()?.process.read_clipboard(ClipboardType::Text)? {
1530 Ok(ClipboardData::Text(t)) => Ok(Ok(t)),
1531 Err(e) => Ok(Err(e)),
1532 _ => Ok(Err(ClipboardError::Other(Txt::from_static("view-process returned incorrect type")))),
1533 }
1534 }
1535
1536 pub fn write_text(&self, txt: Txt) -> Result<ClipboardResult<()>> {
1540 VIEW_PROCESS.try_write()?.process.write_clipboard(ClipboardData::Text(txt))
1541 }
1542
1543 pub fn read_image(&self) -> Result<ClipboardResult<ViewImage>> {
1547 let mut app = VIEW_PROCESS.try_write()?;
1548 match app.process.read_clipboard(ClipboardType::Image)? {
1549 Ok(ClipboardData::Image(id)) => {
1550 if id == ImageId::INVALID {
1551 Ok(Err(ClipboardError::Other(Txt::from_static("view-process returned invalid image"))))
1552 } else {
1553 let img = ViewImage(Arc::new(RwLock::new(ViewImageData {
1554 id: Some(id),
1555 app_id: APP.id(),
1556 generation: app.process.generation(),
1557 size: PxSize::zero(),
1558 partial_size: PxSize::zero(),
1559 density: None,
1560 is_opaque: false,
1561 partial_pixels: None,
1562 pixels: None,
1563 is_mask: false,
1564 done_signal: SignalOnce::new(),
1565 })));
1566 app.loading_images.push(Arc::downgrade(&img.0));
1567 Ok(Ok(img))
1568 }
1569 }
1570 Err(e) => Ok(Err(e)),
1571 _ => Ok(Err(ClipboardError::Other(Txt::from_static("view-process returned incorrect type")))),
1572 }
1573 }
1574
1575 pub fn write_image(&self, img: &ViewImage) -> Result<ClipboardResult<()>> {
1579 if img.is_loaded()
1580 && let Some(id) = img.id()
1581 {
1582 return VIEW_PROCESS.try_write()?.process.write_clipboard(ClipboardData::Image(id));
1583 }
1584 Ok(Err(ClipboardError::Other(Txt::from_static("image not loaded"))))
1585 }
1586
1587 pub fn read_file_list(&self) -> Result<ClipboardResult<Vec<PathBuf>>> {
1591 match VIEW_PROCESS.try_write()?.process.read_clipboard(ClipboardType::FileList)? {
1592 Ok(ClipboardData::FileList(f)) => Ok(Ok(f)),
1593 Err(e) => Ok(Err(e)),
1594 _ => Ok(Err(ClipboardError::Other(Txt::from_static("view-process returned incorrect type")))),
1595 }
1596 }
1597
1598 pub fn write_file_list(&self, list: Vec<PathBuf>) -> Result<ClipboardResult<()>> {
1602 VIEW_PROCESS.try_write()?.process.write_clipboard(ClipboardData::FileList(list))
1603 }
1604
1605 pub fn read_extension(&self, data_type: Txt) -> Result<ClipboardResult<IpcBytes>> {
1609 match VIEW_PROCESS
1610 .try_write()?
1611 .process
1612 .read_clipboard(ClipboardType::Extension(data_type.clone()))?
1613 {
1614 Ok(ClipboardData::Extension { data_type: rt, data }) if rt == data_type => Ok(Ok(data)),
1615 Err(e) => Ok(Err(e)),
1616 _ => Ok(Err(ClipboardError::Other(Txt::from_static("view-process returned incorrect type")))),
1617 }
1618 }
1619
1620 pub fn write_extension(&self, data_type: Txt, data: IpcBytes) -> Result<ClipboardResult<()>> {
1624 VIEW_PROCESS
1625 .try_write()?
1626 .process
1627 .write_clipboard(ClipboardData::Extension { data_type, data })
1628 }
1629}