1use crate::schema::{
4 AssetKind, ColorScheme, DesignSystem, FileDelivery, FileDialogKind, FileDialogStatus, FileOp,
5 IoStatus, Key, MAX_MODIFIER_WORDS, MaterialRole, MessageDuration, Modifier, MotionRole,
6 NotificationImportance, NotificationPermission, NotificationPresentation, Paint, PropertyKind,
7 SEMANTICS_STATE_MASK, Selection, SemanticsRole, ShapeRole, SpaceRole, TYPE_ROLE_COUNT, Theme,
8 WidgetKind, modifier_extra_words,
9};
10use core::fmt;
11
12const TAG_ENVELOPE: u16 = 0;
13const TAG_CREATE: u16 = 1;
14const TAG_SET_PROP: u16 = 2;
15const TAG_SET_MODIFIER: u16 = 3;
16const TAG_INSERT: u16 = 4;
17const TAG_MOVE: u16 = 5;
18const TAG_REMOVE: u16 = 6;
19const TAG_SET_TEXT: u16 = 7;
20const TAG_APPEND_TEXT: u16 = 8;
21const TAG_SET_THEME: u16 = 9;
22const TAG_REGISTER_ASSET: u16 = 10;
23const TAG_RELEASE_ASSET: u16 = 11;
24const TAG_SHOW_MESSAGE: u16 = 12;
25const TAG_SET_WINDOW: u16 = 13;
26const TAG_POST_NOTIFICATION: u16 = 14;
27const TAG_WITHDRAW_NOTIFICATION: u16 = 15;
28const TAG_REQUEST_NOTIFICATION_PERMISSION: u16 = 16;
29const TAG_EDIT_CODE: u16 = 17;
30const TAG_START_ANIMATION: u16 = 18;
31const TAG_CONTROL_ANIMATION: u16 = 19;
32const TAG_SET_SELECTION: u16 = 20;
34const TAG_REVEAL_RANGE: u16 = 21;
35const TAG_SET_FOLDED: u16 = 22;
36const TAG_SET_WINDOW_THEME: u16 = 23;
38const TAG_FOCUS_WINDOW: u16 = 24;
39const TAG_REQUEST_FOCUS: u16 = 25;
40const TAG_SHOW_FILE_DIALOG: u16 = 26;
42const SHOW_FILE_DIALOG_PAYLOAD_LEN: usize = 4 + 4 + 2 + 2 + 4 * 8;
45const TAG_SET_OVERLAY_TOKENS: u16 = 27;
47const TAG_SET_SCROLL_OFFSET: u16 = 30;
49const OVERLAY_TOKENS_PAYLOAD_LEN: usize = 24;
51const TAG_SET_CLIPBOARD_TEXT: u16 = 29;
54const TAG_READ_CLIPBOARD: u16 = 31;
57const TAG_STORAGE_CALL: u16 = 32;
60const STORAGE_CALL_PAYLOAD_LEN: usize = 4 + 2 + 2 + 3 * 8;
63const TAG_FILE_CALL: u16 = 33;
67const FILE_CALL_PAYLOAD_LEN: usize = 4 + 2 + 2 + 4 + 8 + 8;
70const TAG_RELEASE_FILE_HANDLE: u16 = 34;
71const TAG_SCROLL_TO: u16 = 35;
75const TAG_SCROLL_INTO_VIEW: u16 = 36;
76const SCROLL_TO_LEN: usize = 32;
78const SCROLL_INTO_VIEW_LEN: usize = 52;
80const ANIMATION_HEADER_LEN: usize = 52;
82const KEYFRAME_HEAD_LEN: usize = 24;
84const TRANSFORM_FUNCTION_LEN: usize = 28;
86const KEYFRAME_FROM_PRESENTED: u16 = 1;
87const ENVELOPE_LEN: usize = 12;
88pub const THEME_PAYLOAD_LEN: usize = 8 + 4 * TYPE_ROLE_COUNT + 8;
91const MIN_RECORD_LEN: usize = 4;
94
95const VALUE_NONE: u16 = 0;
96const VALUE_STRING: u16 = 1;
97const VALUE_BOOL: u16 = 2;
98const VALUE_I64: u16 = 3;
99const VALUE_F32: u16 = 4;
100const VALUE_BYTES: u16 = 5;
103
104#[derive(Clone, Debug, PartialEq)]
105pub enum PropertyValue<'a> {
106 None,
107 String(&'a str),
108 Bool(bool),
109 Integer(i64),
110 Float(f32),
111 Bytes(&'a [u8]),
112}
113
114#[derive(Clone, Debug, PartialEq)]
115pub enum Mutation<'a> {
116 Create {
117 node_id: u32,
118 widget: WidgetKind,
119 },
120 SetProp {
121 node_id: u32,
122 property: PropertyKind,
123 value: PropertyValue<'a>,
124 },
125 SetModifier {
126 node_id: u32,
127 index: u16,
128 modifier: Modifier,
129 },
130 Insert {
131 parent_id: u32,
132 node_id: u32,
133 index: u32,
134 },
135 Move {
136 parent_id: u32,
137 node_id: u32,
138 index: u32,
139 },
140 Remove {
141 node_id: u32,
142 },
143 SetText {
144 node_id: u32,
145 text: &'a str,
146 selection: Option<Selection>,
147 },
148 AppendText {
150 node_id: u32,
151 text: &'a str,
152 },
153 SetTheme(Theme),
155 SetWindow(crate::schema::Window),
163 RegisterAsset {
167 asset_id: u32,
168 kind: AssetKind,
169 bytes: &'a [u8],
170 },
171 ReleaseAsset {
173 asset_id: u32,
174 },
175 ShowMessage {
183 handler_id: u64,
184 text: &'a str,
185 action: &'a str,
186 duration: MessageDuration,
187 },
188 PostNotification {
196 key: &'a str,
197 title: &'a str,
198 body: &'a str,
199 channel: &'a str,
200 action_1: &'a str,
201 action_2: &'a str,
202 importance: NotificationImportance,
203 presentation: NotificationPresentation,
204 },
205 WithdrawNotification {
207 key: &'a str,
208 },
209 RequestNotificationPermission,
212 EditCode {
221 node_id: u32,
222 request_id: u32,
223 base_version: u32,
224 start_line: u32,
225 start_column: u32,
226 end_line: u32,
227 end_column: u32,
228 text: &'a str,
229 },
230 StartAnimation(crate::schema::Animation<'a>),
233 ControlAnimation {
237 node_id: u32,
238 animation_id: u32,
239 property: crate::schema::AnimatedProperty,
240 slot: u8,
241 op: crate::schema::AnimationControl,
242 at_time_nanos: u64,
244 },
245 SetSelection {
250 node_id: u32,
251 base_version: u32,
252 anchor_line: u32,
253 anchor_column: u32,
254 active_line: u32,
255 active_column: u32,
256 },
257 RevealRange {
260 node_id: u32,
261 base_version: u32,
262 start_line: u32,
263 start_column: u32,
264 end_line: u32,
265 end_column: u32,
266 align: crate::schema::RevealAlign,
267 },
268 SetFolded {
270 node_id: u32,
271 start_line: u32,
272 collapsed: bool,
273 },
274 SetWindowTheme {
277 node_id: u32,
278 theme: Theme,
279 },
280 FocusWindow {
283 node_id: u32,
284 },
285 RequestFocus {
287 node_id: u32,
288 },
289 ShowFileDialog {
297 request: u32,
298 window: u32,
299 kind: FileDialogKind,
300 default_filter: u16,
301 title: &'a str,
302 directory: &'a str,
303 name: &'a str,
304 filters: &'a str,
305 },
306 SetOverlayTokens(crate::schema::OverlayTokens),
308 SetClipboardText {
312 text: &'a str,
313 },
314 ReadClipboard {
317 request: u32,
318 },
319 SetScrollOffset {
323 node_id: u32,
324 offset_px: f64,
325 },
326 StorageCall {
333 request: u32,
334 op: crate::schema::StorageOp,
335 store: &'a str,
336 key: &'a str,
337 data: &'a [u8],
338 },
339 FileCall {
345 request: u32,
346 op: FileOp,
347 create: bool,
348 handle: u32,
349 name: &'a str,
350 data: &'a [u8],
351 },
352 ReleaseFileHandle {
355 handle: u32,
356 },
357 ScrollTo {
362 node_id: u32,
363 item: u32,
364 flags: u32,
365 x_px: f64,
366 y_px: f64,
367 },
368 ScrollIntoView {
372 node_id: u32,
373 target: u32,
374 target_kind: u16,
375 align_x: u16,
376 align_y: u16,
377 flags: u16,
378 rect: crate::schema::Rect,
379 },
380}
381
382#[derive(Clone, Debug, Eq, PartialEq)]
383pub enum ProtocolError {
384 Truncated,
385 InvalidEnvelope,
386 InvalidRecordLength,
387 InvalidTag(u16),
388 InvalidWidget(u16),
389 InvalidProperty(u16),
390 InvalidValueKind(u16),
391 InvalidModifier(u16),
392 InvalidTheme(u16),
393 InvalidAssetKind(u16),
394 InvalidMessageDuration(u16),
395 InvalidNotification(u16),
397 InvalidStringRange,
398 InvalidUtf8,
399 LengthOverflow,
400 MeasureRefused(i32),
403 InvalidAnimation,
408 MisplacedWindow,
412}
413
414#[derive(Clone, Debug, PartialEq)]
415pub struct HostEvent<'a> {
416 pub node_id: u32,
417 pub handler_id: u64,
418 pub payload: crate::schema::EventPayload<'a>,
419}
420
421const EVENT_CLICK: u16 = 1;
422const EVENT_TEXT_CHANGED: u16 = 2;
423const EVENT_TEXT_SUBMITTED: u16 = 3;
424const EVENT_FOCUS_LOST: u16 = 4;
425const EVENT_PROTOCOL_ERROR: u16 = 5;
426const EVENT_KEY_DOWN: u16 = 6;
427const EVENT_RANGE_REQUESTED: u16 = 7;
428const EVENT_POINTER: u16 = 8;
431const POINTER_RECORD_LEN: usize = 72;
432const EVENT_VALUE_CHANGED: u16 = 16;
433const EVENT_WINDOW_SIZE_CHANGED: u16 = 17;
434const EVENT_RESYNC: u16 = 18;
435const EVENT_LIFECYCLE_START: u16 = 19;
436const EVENT_LIFECYCLE_STOP: u16 = 20;
437const EVENT_DESIGN_SYSTEM_RESOLVED: u16 = 21;
438const EVENT_FILES_ENTERED: u16 = 22;
439const EVENT_FILES_DROPPED: u16 = 23;
440const EVENT_NOTIFICATION_ACTIVATED: u16 = 24;
441const EVENT_NOTIFICATION_PERMISSION_CHANGED: u16 = 25;
442const EVENT_CODE_CHANGED: u16 = 26;
443const EVENT_CODE_EDIT_REJECTED: u16 = 27;
444const EVENT_CODE_HOVERED: u16 = 28;
445const EVENT_CODE_SAVE_REQUESTED: u16 = 29;
446const EVENT_DECORATION_ACTIVATED: u16 = 30;
447const EVENT_ANIMATION: u16 = 31;
448const EVENT_REDUCED_MOTION_CHANGED: u16 = 32;
449const EVENT_ZOOM_CHANGED: u16 = 33;
451const EVENT_SELECTION_CHANGED: u16 = 34;
453const EVENT_VISIBLE_RANGE_CHANGED: u16 = 35;
454const EVENT_EDITOR_FOCUS_CHANGED: u16 = 36;
455const EVENT_FOLD_CHANGED: u16 = 37;
456const EVENT_COMPOSITION_CHANGED: u16 = 38;
457const EVENT_WINDOW_CLOSE_REQUESTED: u16 = 39;
459const EVENT_WINDOW_FOCUS_CHANGED: u16 = 40;
460const EVENT_WINDOW_MOVED: u16 = 41;
461const EVENT_WINDOW_DRAG: u16 = 42;
462const EVENT_WINDOW_DRAG_OVER: u16 = 43;
463const EVENT_WINDOW_UNAVAILABLE: u16 = 44;
464const EVENT_WINDOW_CLOSED: u16 = 45;
465const EVENT_WINDOW_REQUESTED: u16 = 46;
466const EVENT_APPLICATION_REOPEN: u16 = 47;
467const EVENT_FOCUS_GAINED: u16 = 48;
469const EVENT_KEY_UP: u16 = 49;
470const EVENT_FILE_DIALOG_RESULT: u16 = 50;
471const EVENT_CODE_METRICS: u16 = 54;
474const EVENT_CODE_SCROLLED: u16 = 55;
475const FILE_DIALOG_RESULT_LEN: usize = 36;
477const EVENT_CLIPBOARD_TEXT: u16 = 56;
479const CLIPBOARD_TEXT_LEN: usize = 36;
481const EVENT_IO_ANSWER: u16 = 57;
482const IO_ANSWER_LEN: usize = 32;
484const EVENT_FILE_HANDLES: u16 = 58;
485const FILE_HANDLES_LEN: usize = 52;
487const EVENT_STORAGE_LOCATION: u16 = 59;
488const EVENT_SCROLL_CHANGED: u16 = 60;
489const SCROLL_CHANGED_LEN: usize = 72;
491
492const WINDOW_MODE_SHIFT: u32 = 8;
495const WINDOW_POLICY_SHIFT: u32 = 12;
496
497pub const WINDOW_DRAG_MOVE: u32 = 1;
499pub const WINDOW_DRAG_END: u32 = 2;
500pub const WINDOW_DRAG_CANCEL: u32 = 3;
501pub const WINDOW_DRAG_OVER_ENTER: u32 = 1;
503pub const WINDOW_DRAG_OVER_MOVE: u32 = 2;
504pub const WINDOW_DRAG_OVER_LEAVE: u32 = 3;
505pub const WINDOW_DRAG_OVER_DROP: u32 = 4;
506
507const MODIFIER_SHIFT: u8 = 1 << 0;
508const MODIFIER_CTRL: u8 = 1 << 1;
509const MODIFIER_ALT: u8 = 1 << 2;
510const MODIFIER_META: u8 = 1 << 3;
511
512fn pointer_tag<T: TryFrom<u16, Error = ()>>(
515 bytes: &[u8],
516 offset: usize,
517) -> Result<T, ProtocolError> {
518 let raw = read_u32(bytes, offset)?;
519 u16::try_from(raw)
520 .ok()
521 .and_then(|tag| T::try_from(tag).ok())
522 .ok_or(ProtocolError::InvalidValueKind(
523 raw.min(u32::from(u16::MAX)) as u16,
524 ))
525}
526
527fn decode_pointer(bytes: &[u8]) -> Result<crate::schema::EventPayload<'_>, ProtocolError> {
529 Ok(crate::schema::EventPayload::Pointer {
530 kind: pointer_tag(bytes, 16)?,
531 pointer_id: read_u32(bytes, 20)?,
532 pointer_type: pointer_tag(bytes, 24)?,
533 x: f32::from_bits(read_u32(bytes, 28)?),
534 y: f32::from_bits(read_u32(bytes, 32)?),
535 buttons: read_u32(bytes, 36)?,
536 button: read_u32(bytes, 40)?,
537 modifiers: read_u32(bytes, 44)?,
538 click_count: read_u32(bytes, 48)?,
539 delta_x: f32::from_bits(read_u32(bytes, 52)?),
540 delta_y: f32::from_bits(read_u32(bytes, 56)?),
541 delta_mode: pointer_tag(bytes, 60)?,
542 timestamp_nanos: read_u64(bytes, 64)?,
543 })
544}
545
546pub fn decode_event(bytes: &[u8]) -> Result<HostEvent<'_>, ProtocolError> {
548 if bytes.len() < 16 {
549 return Err(ProtocolError::Truncated);
550 }
551 let tag = read_u16(bytes, 0)?;
552 let record_len = usize::from(read_u16(bytes, 2)?);
553 if record_len < 16 || record_len % 4 != 0 || record_len > bytes.len() {
554 return Err(ProtocolError::InvalidRecordLength);
555 }
556 let node_id = read_u32(bytes, 4)?;
557 let handler_id = read_u64(bytes, 8)?;
558 let payload = match tag {
559 EVENT_CLICK if record_len == 16 => crate::schema::EventPayload::Clicked,
560 EVENT_TEXT_CHANGED if record_len == 24 => {
561 crate::schema::EventPayload::TextChanged(read_string(bytes, 16, record_len)?)
562 }
563 EVENT_TEXT_SUBMITTED if record_len == 24 => {
564 crate::schema::EventPayload::TextSubmitted(read_string(bytes, 16, record_len)?)
565 }
566 EVENT_FOCUS_LOST if record_len == 16 => crate::schema::EventPayload::FocusLost,
567 EVENT_FOCUS_GAINED if record_len == 16 => crate::schema::EventPayload::FocusGained,
568 EVENT_PROTOCOL_ERROR if record_len == 28 => crate::schema::EventPayload::ProtocolError {
569 code: read_u32(bytes, 16)?,
570 message: read_string(bytes, 20, record_len)?,
571 },
572 EVENT_KEY_DOWN if record_len == 32 => {
573 let raw_key = read_u16(bytes, 16)?;
574 let modifiers = *bytes.get(18).ok_or(ProtocolError::Truncated)?;
575 let repeat = *bytes.get(19).ok_or(ProtocolError::Truncated)? != 0;
576 crate::schema::EventPayload::KeyDown {
577 key: Key::try_from(raw_key)
578 .map_err(|()| ProtocolError::InvalidValueKind(raw_key))?,
579 shift_key: modifiers & MODIFIER_SHIFT != 0,
580 ctrl_key: modifiers & MODIFIER_CTRL != 0,
581 alt_key: modifiers & MODIFIER_ALT != 0,
582 meta_key: modifiers & MODIFIER_META != 0,
583 repeat,
584 code: read_u32(bytes, 20)?,
585 text: read_string(bytes, 24, record_len)?,
586 }
587 }
588 EVENT_KEY_UP if record_len == 32 => {
590 let raw_key = read_u16(bytes, 16)?;
591 let modifiers = *bytes.get(18).ok_or(ProtocolError::Truncated)?;
592 let repeat = *bytes.get(19).ok_or(ProtocolError::Truncated)? != 0;
593 crate::schema::EventPayload::KeyUp {
594 key: Key::try_from(raw_key)
595 .map_err(|()| ProtocolError::InvalidValueKind(raw_key))?,
596 shift_key: modifiers & MODIFIER_SHIFT != 0,
597 ctrl_key: modifiers & MODIFIER_CTRL != 0,
598 alt_key: modifiers & MODIFIER_ALT != 0,
599 meta_key: modifiers & MODIFIER_META != 0,
600 repeat,
601 code: read_u32(bytes, 20)?,
602 text: read_string(bytes, 24, record_len)?,
603 }
604 }
605 EVENT_RANGE_REQUESTED if record_len == 24 => crate::schema::EventPayload::RangeRequested {
606 start: read_u32(bytes, 16)?,
607 count: read_u32(bytes, 20)?,
608 },
609 EVENT_VALUE_CHANGED if record_len == 24 => {
610 crate::schema::EventPayload::ValueChanged(f64::from_bits(read_u64(bytes, 16)?))
611 }
612 EVENT_WINDOW_SIZE_CHANGED if record_len == 44 => {
613 let raw_class = read_u32(bytes, 24)?;
614 let class = u16::try_from(raw_class)
615 .ok()
616 .and_then(|value| crate::schema::WindowSizeClass::try_from(value).ok())
617 .ok_or(ProtocolError::InvalidValueKind(raw_class as u16))?;
618 let raw_height_class = read_u32(bytes, 28)?;
619 let height_class = u16::try_from(raw_height_class)
620 .ok()
621 .and_then(|value| crate::schema::WindowHeightClass::try_from(value).ok())
622 .ok_or(ProtocolError::InvalidValueKind(raw_height_class as u16))?;
623 crate::schema::EventPayload::WindowSizeChanged {
624 width_dp: f32::from_bits(read_u32(bytes, 16)?),
625 height_dp: f32::from_bits(read_u32(bytes, 20)?),
626 class,
627 height_class,
628 safe_area_top: f32::from_bits(read_u32(bytes, 32)?),
629 safe_area_leading: f32::from_bits(read_u32(bytes, 36)?),
630 safe_area_trailing: f32::from_bits(read_u32(bytes, 40)?),
631 }
632 }
633 EVENT_DESIGN_SYSTEM_RESOLVED if record_len == 20 => {
634 let raw = read_u32(bytes, 16)?;
635 let system = u16::try_from(raw)
636 .ok()
637 .and_then(|tag| crate::schema::DesignSystem::try_from(tag).ok())
638 .ok_or(ProtocolError::InvalidValueKind(raw as u16))?;
639 crate::schema::EventPayload::DesignSystemResolved(system)
640 }
641 EVENT_FILES_ENTERED if record_len == 16 => crate::schema::EventPayload::FilesEntered,
642 EVENT_FILES_DROPPED if record_len == 24 => {
643 crate::schema::EventPayload::FilesDropped(read_string(bytes, 16, record_len)?)
644 }
645 EVENT_NOTIFICATION_ACTIVATED if record_len == 28 => {
646 let action = read_u32(bytes, 16)?;
647 if action > 2 {
650 return Err(ProtocolError::InvalidValueKind(
651 action.min(u32::from(u16::MAX)) as u16,
652 ));
653 }
654 crate::schema::EventPayload::NotificationActivated {
655 action,
656 key: read_string(bytes, 20, record_len)?,
657 }
658 }
659 EVENT_NOTIFICATION_PERMISSION_CHANGED if record_len == 20 => {
660 let raw = read_u32(bytes, 16)?;
661 let state = u16::try_from(raw)
662 .ok()
663 .and_then(|tag| NotificationPermission::try_from(tag).ok())
664 .ok_or(ProtocolError::InvalidValueKind(
665 raw.min(u32::from(u16::MAX)) as u16,
666 ))?;
667 crate::schema::EventPayload::NotificationPermissionChanged(state)
668 }
669 EVENT_ANIMATION if record_len == 40 => {
670 let raw_kind = u16::from(read_u8(bytes, 20)?);
671 let raw_property = u16::from(read_u8(bytes, 21)?);
672 crate::schema::EventPayload::AnimationEvent {
673 animation_id: read_u32(bytes, 16)?,
674 kind: crate::schema::AnimationEventKind::try_from(raw_kind)
675 .map_err(|()| ProtocolError::InvalidValueKind(raw_kind))?,
676 property: crate::schema::AnimatedProperty::try_from(raw_property)
677 .map_err(|()| ProtocolError::InvalidValueKind(raw_property))?,
678 slot: read_u8(bytes, 22)?,
679 iteration: read_u32(bytes, 24)?,
680 elapsed_ms: f32::from_bits(read_u32(bytes, 28)?),
681 time_nanos: read_u64(bytes, 32)?,
682 }
683 }
684 EVENT_POINTER if record_len == POINTER_RECORD_LEN => decode_pointer(bytes)?,
685 EVENT_REDUCED_MOTION_CHANGED if record_len == 20 => {
686 let raw = read_u32(bytes, 16)?;
687 let state = u16::try_from(raw)
688 .ok()
689 .and_then(|tag| crate::schema::ReducedMotion::try_from(tag).ok())
690 .ok_or(ProtocolError::InvalidValueKind(
691 raw.min(u32::from(u16::MAX)) as u16,
692 ))?;
693 crate::schema::EventPayload::ReducedMotionChanged(state)
694 }
695 EVENT_CODE_CHANGED if record_len == 44 => crate::schema::EventPayload::CodeChanged {
696 version: read_u32(bytes, 16)?,
697 start_line: read_u32(bytes, 20)?,
698 start_column: read_u32(bytes, 24)?,
699 end_line: read_u32(bytes, 28)?,
700 end_column: read_u32(bytes, 32)?,
701 text: read_string(bytes, 36, record_len)?,
702 },
703 EVENT_CODE_EDIT_REJECTED if record_len == 44 => {
704 crate::schema::EventPayload::CodeEditRejected {
705 request_id: read_u32(bytes, 16)?,
706 base_version: read_u32(bytes, 20)?,
707 current_version: read_u32(bytes, 24)?,
708 start_line: read_u32(bytes, 28)?,
709 start_column: read_u32(bytes, 32)?,
710 end_line: read_u32(bytes, 36)?,
711 end_column: read_u32(bytes, 40)?,
712 }
713 }
714 EVENT_CODE_HOVERED if record_len == 36 => {
715 let raw_phase = read_u32(bytes, 32)?;
716 let phase = u16::try_from(raw_phase)
717 .ok()
718 .and_then(|tag| crate::schema::HoverPhase::try_from(tag).ok())
719 .ok_or(ProtocolError::InvalidValueKind(raw_phase as u16))?;
720 crate::schema::EventPayload::CodeHovered {
721 decoration: read_u64(bytes, 16)?,
722 line: read_u32(bytes, 24)?,
723 column: read_u32(bytes, 28)?,
724 phase,
725 }
726 }
727 EVENT_CODE_SAVE_REQUESTED if record_len == 24 => {
728 crate::schema::EventPayload::CodeSaveRequested {
729 version: read_u32(bytes, 16)?,
730 }
731 }
732 EVENT_DECORATION_ACTIVATED if record_len == 24 => {
733 crate::schema::EventPayload::DecorationActivated {
734 decoration: read_u64(bytes, 16)?,
735 }
736 }
737 EVENT_CODE_CHANGED..=EVENT_DECORATION_ACTIVATED => {
738 return Err(ProtocolError::InvalidRecordLength);
739 }
740 EVENT_SELECTION_CHANGED if record_len == 36 => {
741 crate::schema::EventPayload::SelectionChanged {
742 version: read_u32(bytes, 16)?,
743 anchor_line: read_u32(bytes, 20)?,
744 anchor_column: read_u32(bytes, 24)?,
745 active_line: read_u32(bytes, 28)?,
746 active_column: read_u32(bytes, 32)?,
747 }
748 }
749 EVENT_VISIBLE_RANGE_CHANGED if record_len == 28 => {
750 crate::schema::EventPayload::VisibleRangeChanged {
751 version: read_u32(bytes, 16)?,
752 first_line: read_u32(bytes, 20)?,
753 last_line: read_u32(bytes, 24)?,
754 }
755 }
756 EVENT_EDITOR_FOCUS_CHANGED if record_len == 24 => {
757 crate::schema::EventPayload::EditorFocusChanged {
758 focused: read_flag(bytes, 16)?,
759 }
760 }
761 EVENT_FOLD_CHANGED if record_len == 24 => crate::schema::EventPayload::FoldChanged {
762 start_line: read_u32(bytes, 16)?,
763 collapsed: read_flag(bytes, 20)?,
764 },
765 EVENT_COMPOSITION_CHANGED if record_len == 24 => {
766 crate::schema::EventPayload::CompositionChanged {
767 composing: read_flag(bytes, 16)?,
768 }
769 }
770 EVENT_SELECTION_CHANGED..=EVENT_COMPOSITION_CHANGED => {
771 return Err(ProtocolError::InvalidRecordLength);
772 }
773 EVENT_WINDOW_CLOSE_REQUESTED if record_len == 16 => {
774 crate::schema::EventPayload::WindowCloseRequested
775 }
776 EVENT_WINDOW_FOCUS_CHANGED if record_len == 20 => {
777 crate::schema::EventPayload::WindowFocusChanged {
778 focused: read_flag(bytes, 16)?,
779 }
780 }
781 EVENT_WINDOW_MOVED if record_len == 32 => crate::schema::EventPayload::WindowMoved {
782 x: f32::from_bits(read_u32(bytes, 16)?),
783 y: f32::from_bits(read_u32(bytes, 20)?),
784 width: f32::from_bits(read_u32(bytes, 24)?),
785 height: f32::from_bits(read_u32(bytes, 28)?),
786 },
787 EVENT_WINDOW_DRAG if record_len == 32 => {
788 let phase = read_u32(bytes, 16)?;
789 if !(WINDOW_DRAG_MOVE..=WINDOW_DRAG_CANCEL).contains(&phase) {
790 return Err(ProtocolError::InvalidValueKind(
791 phase.min(u32::from(u16::MAX)) as u16,
792 ));
793 }
794 crate::schema::EventPayload::WindowDrag {
795 phase,
796 screen_x: f32::from_bits(read_u32(bytes, 20)?),
797 screen_y: f32::from_bits(read_u32(bytes, 24)?),
798 over_window: read_u32(bytes, 28)?,
799 }
800 }
801 EVENT_WINDOW_DRAG_OVER if record_len == 32 => {
802 let kind = read_u32(bytes, 16)?;
803 if !(WINDOW_DRAG_OVER_ENTER..=WINDOW_DRAG_OVER_DROP).contains(&kind) {
804 return Err(ProtocolError::InvalidValueKind(
805 kind.min(u32::from(u16::MAX)) as u16,
806 ));
807 }
808 crate::schema::EventPayload::WindowDragOver {
809 kind,
810 window: read_u32(bytes, 20)?,
811 x: f32::from_bits(read_u32(bytes, 24)?),
812 y: f32::from_bits(read_u32(bytes, 28)?),
813 }
814 }
815 EVENT_WINDOW_UNAVAILABLE if record_len == 16 => {
816 crate::schema::EventPayload::WindowUnavailable
817 }
818 EVENT_WINDOW_CLOSED if record_len == 16 => crate::schema::EventPayload::WindowClosed,
819 EVENT_WINDOW_REQUESTED if record_len == 28 => {
820 crate::schema::EventPayload::WindowRequested {
821 request: read_u32(bytes, 16)?,
822 key: read_string(bytes, 20, record_len)?,
823 }
824 }
825 EVENT_APPLICATION_REOPEN if record_len == 16 => {
826 crate::schema::EventPayload::ApplicationReopen
827 }
828 EVENT_FILE_DIALOG_RESULT if record_len == FILE_DIALOG_RESULT_LEN => {
829 let raw_status = read_u32(bytes, 20)?;
830 let status = u16::try_from(raw_status)
831 .ok()
832 .and_then(|tag| FileDialogStatus::try_from(tag).ok())
833 .ok_or(ProtocolError::InvalidValueKind(
834 raw_status.min(u32::from(u16::MAX)) as u16,
835 ))?;
836 crate::schema::EventPayload::FileDialogResult {
837 request: read_u32(bytes, 16)?,
838 status,
839 filter: read_u32(bytes, 24)?,
840 paths: read_string(bytes, 28, record_len)?,
841 }
842 }
843 EVENT_FILE_DIALOG_RESULT => return Err(ProtocolError::InvalidRecordLength),
844 EVENT_CLIPBOARD_TEXT if record_len == CLIPBOARD_TEXT_LEN => {
845 crate::schema::EventPayload::ClipboardText {
846 request: read_u32(bytes, 16)?,
847 status: read_u32(bytes, 20)?,
848 len: read_u32(bytes, 24)?,
849 text: read_string(bytes, 28, record_len)?,
850 }
851 }
852 EVENT_CLIPBOARD_TEXT => return Err(ProtocolError::InvalidRecordLength),
853 EVENT_CODE_METRICS if record_len == 24 => crate::schema::EventPayload::CodeMetrics {
854 line_height_px: f64::from_bits(read_u64(bytes, 16)?),
855 },
856 EVENT_CODE_SCROLLED if record_len == 24 => crate::schema::EventPayload::CodeScrolled {
857 offset_px: f64::from_bits(read_u64(bytes, 16)?),
858 },
859 EVENT_CODE_METRICS | EVENT_CODE_SCROLLED => {
860 return Err(ProtocolError::InvalidRecordLength);
861 }
862 EVENT_IO_ANSWER if record_len == IO_ANSWER_LEN => {
863 let raw_status = read_u32(bytes, 20)?;
864 let status = u16::try_from(raw_status)
865 .ok()
866 .and_then(|tag| IoStatus::try_from(tag).ok())
867 .ok_or(ProtocolError::InvalidValueKind(
868 raw_status.min(u32::from(u16::MAX)) as u16,
869 ))?;
870 crate::schema::EventPayload::IoAnswer {
871 request: read_u32(bytes, 16)?,
872 status,
873 data: read_bytes(bytes, 24, record_len)?,
874 }
875 }
876 EVENT_IO_ANSWER => return Err(ProtocolError::InvalidRecordLength),
877 EVENT_FILE_HANDLES if record_len == FILE_HANDLES_LEN => {
878 let raw_status = read_u32(bytes, 20)?;
879 let status = u16::try_from(raw_status)
880 .ok()
881 .and_then(|tag| FileDialogStatus::try_from(tag).ok())
882 .ok_or(ProtocolError::InvalidValueKind(
883 raw_status.min(u32::from(u16::MAX)) as u16,
884 ))?;
885 let raw_delivery = read_u32(bytes, 28)?;
886 let delivery = u16::try_from(raw_delivery)
887 .ok()
888 .and_then(|tag| FileDelivery::try_from(tag).ok())
889 .ok_or(ProtocolError::InvalidValueKind(
890 raw_delivery.min(u32::from(u16::MAX)) as u16,
891 ))?;
892 let names = read_string(bytes, 36, record_len)?;
893 let kinds = read_string(bytes, 44, record_len)?;
894 if let Some(unknown) = kinds.bytes().find(|kind| *kind != b'f' && *kind != b'd') {
897 return Err(ProtocolError::InvalidValueKind(u16::from(unknown)));
898 }
899 let count = if names.is_empty() {
900 0
901 } else {
902 names.split('\0').count()
903 };
904 if count != kinds.len() || names.split('\0').any(|name| count != 0 && name.is_empty()) {
905 return Err(ProtocolError::InvalidStringRange);
906 }
907 crate::schema::EventPayload::FileHandles {
908 request: read_u32(bytes, 16)?,
909 status,
910 filter: read_u32(bytes, 24)?,
911 delivery,
912 first_handle: read_u32(bytes, 32)?,
913 names,
914 kinds,
915 }
916 }
917 EVENT_FILE_HANDLES => return Err(ProtocolError::InvalidRecordLength),
918 EVENT_STORAGE_LOCATION if record_len == 24 => {
919 crate::schema::EventPayload::StorageLocation(read_string(bytes, 16, record_len)?)
920 }
921 EVENT_STORAGE_LOCATION => return Err(ProtocolError::InvalidRecordLength),
922 EVENT_SCROLL_CHANGED if record_len == SCROLL_CHANGED_LEN => {
923 use crate::schema::scroll_changed_flags as flag;
924 let flags = read_u32(bytes, 20)?;
925 if flags & !flag::KNOWN != 0 {
926 return Err(ProtocolError::InvalidValueKind(
929 (flags & !flag::KNOWN).trailing_zeros() as u16,
930 ));
931 }
932 crate::schema::EventPayload::ScrollChanged {
933 item: read_u32(bytes, 16)?,
934 settled: flags & flag::SETTLED != 0,
935 can_scroll_backward: flags & flag::CAN_SCROLL_BACKWARD != 0,
936 can_scroll_forward: flags & flag::CAN_SCROLL_FORWARD != 0,
937 x_px: f64::from_bits(read_u64(bytes, 24)?),
938 y_px: f64::from_bits(read_u64(bytes, 32)?),
939 max_x_px: f64::from_bits(read_u64(bytes, 40)?),
940 max_y_px: f64::from_bits(read_u64(bytes, 48)?),
941 viewport_width_px: f64::from_bits(read_u64(bytes, 56)?),
942 viewport_height_px: f64::from_bits(read_u64(bytes, 64)?),
943 }
944 }
945 EVENT_SCROLL_CHANGED => return Err(ProtocolError::InvalidRecordLength),
946 EVENT_WINDOW_CLOSE_REQUESTED..=EVENT_APPLICATION_REOPEN => {
947 return Err(ProtocolError::InvalidRecordLength);
948 }
949 EVENT_ZOOM_CHANGED if record_len == 28 => {
950 let level = read_u32(bytes, 24)? as i32;
951 if !(i32::from(crate::zoom::MIN_ZOOM_LEVEL)..=i32::from(crate::zoom::MAX_ZOOM_LEVEL))
952 .contains(&level)
953 {
954 return Err(ProtocolError::InvalidValueKind(level as u16));
955 }
956 crate::schema::EventPayload::ZoomChanged {
957 k: f32::from_bits(read_u32(bytes, 16)?),
958 os: f32::from_bits(read_u32(bytes, 20)?),
959 level,
960 }
961 }
962 EVENT_RESYNC if record_len == 16 => crate::schema::EventPayload::Resync,
963 EVENT_LIFECYCLE_START if record_len == 16 => crate::schema::EventPayload::LifecycleStart,
964 EVENT_LIFECYCLE_STOP if record_len == 16 => crate::schema::EventPayload::LifecycleStop,
965 EVENT_CLICK..=EVENT_RANGE_REQUESTED
966 | EVENT_VALUE_CHANGED..=EVENT_NOTIFICATION_PERMISSION_CHANGED
967 | EVENT_ANIMATION
968 | EVENT_POINTER
969 | EVENT_REDUCED_MOTION_CHANGED
970 | EVENT_ZOOM_CHANGED
971 | EVENT_FOCUS_GAINED
972 | EVENT_KEY_UP => {
973 return Err(ProtocolError::InvalidRecordLength);
974 }
975 other => return Err(ProtocolError::InvalidTag(other)),
976 };
977 Ok(HostEvent {
978 node_id,
979 handler_id,
980 payload,
981 })
982}
983
984pub fn encode_event(event: &HostEvent<'_>, output: &mut Vec<u8>) -> Result<(), ProtocolError> {
986 output.clear();
987 if let crate::schema::EventPayload::KeyDown {
988 key,
989 shift_key,
990 ctrl_key,
991 alt_key,
992 meta_key,
993 repeat,
994 code,
995 text,
996 } = event.payload
997 {
998 output.extend_from_slice(&EVENT_KEY_DOWN.to_le_bytes());
999 output.extend_from_slice(&32_u16.to_le_bytes());
1000 output.extend_from_slice(&event.node_id.to_le_bytes());
1001 output.extend_from_slice(&event.handler_id.to_le_bytes());
1002 output.extend_from_slice(&(key as u16).to_le_bytes());
1003 let modifiers = (u8::from(shift_key) * MODIFIER_SHIFT)
1004 | (u8::from(ctrl_key) * MODIFIER_CTRL)
1005 | (u8::from(alt_key) * MODIFIER_ALT)
1006 | (u8::from(meta_key) * MODIFIER_META);
1007 output.push(modifiers);
1008 output.push(u8::from(repeat));
1009 output.extend_from_slice(&code.to_le_bytes());
1010 let len = u32::try_from(text.len()).map_err(|_| ProtocolError::LengthOverflow)?;
1011 output.extend_from_slice(&32_u32.to_le_bytes());
1012 output.extend_from_slice(&len.to_le_bytes());
1013 output.extend_from_slice(text.as_bytes());
1014 return Ok(());
1015 }
1016 if let crate::schema::EventPayload::KeyUp {
1017 key,
1018 shift_key,
1019 ctrl_key,
1020 alt_key,
1021 meta_key,
1022 repeat,
1023 code,
1024 text,
1025 } = event.payload
1026 {
1027 output.extend_from_slice(&EVENT_KEY_UP.to_le_bytes());
1028 output.extend_from_slice(&32_u16.to_le_bytes());
1029 output.extend_from_slice(&event.node_id.to_le_bytes());
1030 output.extend_from_slice(&event.handler_id.to_le_bytes());
1031 output.extend_from_slice(&(key as u16).to_le_bytes());
1032 let modifiers = (u8::from(shift_key) * MODIFIER_SHIFT)
1033 | (u8::from(ctrl_key) * MODIFIER_CTRL)
1034 | (u8::from(alt_key) * MODIFIER_ALT)
1035 | (u8::from(meta_key) * MODIFIER_META);
1036 output.push(modifiers);
1037 output.push(u8::from(repeat));
1038 output.extend_from_slice(&code.to_le_bytes());
1039 let len = u32::try_from(text.len()).map_err(|_| ProtocolError::LengthOverflow)?;
1040 output.extend_from_slice(&32_u32.to_le_bytes());
1041 output.extend_from_slice(&len.to_le_bytes());
1042 output.extend_from_slice(text.as_bytes());
1043 return Ok(());
1044 }
1045 if let crate::schema::EventPayload::ValueChanged(value) = event.payload {
1046 output.extend_from_slice(&EVENT_VALUE_CHANGED.to_le_bytes());
1047 output.extend_from_slice(&24_u16.to_le_bytes());
1048 output.extend_from_slice(&event.node_id.to_le_bytes());
1049 output.extend_from_slice(&event.handler_id.to_le_bytes());
1050 output.extend_from_slice(&value.to_bits().to_le_bytes());
1051 return Ok(());
1052 }
1053 if let crate::schema::EventPayload::WindowSizeChanged {
1054 width_dp,
1055 height_dp,
1056 class,
1057 height_class,
1058 safe_area_top,
1059 safe_area_leading,
1060 safe_area_trailing,
1061 } = event.payload
1062 {
1063 output.extend_from_slice(&EVENT_WINDOW_SIZE_CHANGED.to_le_bytes());
1064 output.extend_from_slice(&44_u16.to_le_bytes());
1065 output.extend_from_slice(&event.node_id.to_le_bytes());
1066 output.extend_from_slice(&event.handler_id.to_le_bytes());
1067 output.extend_from_slice(&width_dp.to_bits().to_le_bytes());
1068 output.extend_from_slice(&height_dp.to_bits().to_le_bytes());
1069 output.extend_from_slice(&u32::from(u16::from(class)).to_le_bytes());
1070 output.extend_from_slice(&u32::from(u16::from(height_class)).to_le_bytes());
1071 output.extend_from_slice(&safe_area_top.to_bits().to_le_bytes());
1072 output.extend_from_slice(&safe_area_leading.to_bits().to_le_bytes());
1073 output.extend_from_slice(&safe_area_trailing.to_bits().to_le_bytes());
1074 return Ok(());
1075 }
1076 if let crate::schema::EventPayload::DesignSystemResolved(system) = event.payload {
1077 output.extend_from_slice(&EVENT_DESIGN_SYSTEM_RESOLVED.to_le_bytes());
1078 output.extend_from_slice(&20_u16.to_le_bytes());
1079 output.extend_from_slice(&event.node_id.to_le_bytes());
1080 output.extend_from_slice(&event.handler_id.to_le_bytes());
1081 output.extend_from_slice(&u32::from(u16::from(system)).to_le_bytes());
1084 return Ok(());
1085 }
1086 if let crate::schema::EventPayload::NotificationPermissionChanged(state) = event.payload {
1087 output.extend_from_slice(&EVENT_NOTIFICATION_PERMISSION_CHANGED.to_le_bytes());
1088 output.extend_from_slice(&20_u16.to_le_bytes());
1089 output.extend_from_slice(&event.node_id.to_le_bytes());
1090 output.extend_from_slice(&event.handler_id.to_le_bytes());
1091 output.extend_from_slice(&u32::from(u16::from(state)).to_le_bytes());
1092 return Ok(());
1093 }
1094 if let crate::schema::EventPayload::AnimationEvent {
1095 animation_id,
1096 kind,
1097 property,
1098 slot,
1099 iteration,
1100 elapsed_ms,
1101 time_nanos,
1102 } = event.payload
1103 {
1104 output.extend_from_slice(&EVENT_ANIMATION.to_le_bytes());
1105 output.extend_from_slice(&40_u16.to_le_bytes());
1106 output.extend_from_slice(&event.node_id.to_le_bytes());
1107 output.extend_from_slice(&event.handler_id.to_le_bytes());
1108 output.extend_from_slice(&animation_id.to_le_bytes());
1109 output.push(kind as u8);
1110 output.push(property as u8);
1111 output.push(slot);
1112 output.push(0);
1113 output.extend_from_slice(&iteration.to_le_bytes());
1114 output.extend_from_slice(&elapsed_ms.to_bits().to_le_bytes());
1115 output.extend_from_slice(&time_nanos.to_le_bytes());
1116 return Ok(());
1117 }
1118 if let crate::schema::EventPayload::Pointer {
1119 kind,
1120 pointer_id,
1121 pointer_type,
1122 x,
1123 y,
1124 buttons,
1125 button,
1126 modifiers,
1127 click_count,
1128 delta_x,
1129 delta_y,
1130 delta_mode,
1131 timestamp_nanos,
1132 } = event.payload
1133 {
1134 output.extend_from_slice(&EVENT_POINTER.to_le_bytes());
1135 output.extend_from_slice(&(POINTER_RECORD_LEN as u16).to_le_bytes());
1136 output.extend_from_slice(&event.node_id.to_le_bytes());
1137 output.extend_from_slice(&event.handler_id.to_le_bytes());
1138 output.extend_from_slice(&u32::from(u16::from(kind)).to_le_bytes());
1139 output.extend_from_slice(&pointer_id.to_le_bytes());
1140 output.extend_from_slice(&u32::from(u16::from(pointer_type)).to_le_bytes());
1141 output.extend_from_slice(&x.to_bits().to_le_bytes());
1142 output.extend_from_slice(&y.to_bits().to_le_bytes());
1143 output.extend_from_slice(&buttons.to_le_bytes());
1144 output.extend_from_slice(&button.to_le_bytes());
1145 output.extend_from_slice(&modifiers.to_le_bytes());
1146 output.extend_from_slice(&click_count.to_le_bytes());
1147 output.extend_from_slice(&delta_x.to_bits().to_le_bytes());
1148 output.extend_from_slice(&delta_y.to_bits().to_le_bytes());
1149 output.extend_from_slice(&u32::from(u16::from(delta_mode)).to_le_bytes());
1150 output.extend_from_slice(×tamp_nanos.to_le_bytes());
1151 return Ok(());
1152 }
1153 if let crate::schema::EventPayload::ReducedMotionChanged(state) = event.payload {
1154 output.extend_from_slice(&EVENT_REDUCED_MOTION_CHANGED.to_le_bytes());
1155 output.extend_from_slice(&20_u16.to_le_bytes());
1156 output.extend_from_slice(&event.node_id.to_le_bytes());
1157 output.extend_from_slice(&event.handler_id.to_le_bytes());
1158 output.extend_from_slice(&u32::from(u16::from(state)).to_le_bytes());
1159 return Ok(());
1160 }
1161 match event.payload {
1162 crate::schema::EventPayload::CodeChanged {
1163 version,
1164 start_line,
1165 start_column,
1166 end_line,
1167 end_column,
1168 text,
1169 } => {
1170 put_event_header(output, EVENT_CODE_CHANGED, 44, event);
1171 for value in [version, start_line, start_column, end_line, end_column] {
1172 output.extend_from_slice(&value.to_le_bytes());
1173 }
1174 let len = u32::try_from(text.len()).map_err(|_| ProtocolError::LengthOverflow)?;
1175 output.extend_from_slice(&44_u32.to_le_bytes());
1176 output.extend_from_slice(&len.to_le_bytes());
1177 output.extend_from_slice(text.as_bytes());
1178 return Ok(());
1179 }
1180 crate::schema::EventPayload::CodeEditRejected {
1181 request_id,
1182 base_version,
1183 current_version,
1184 start_line,
1185 start_column,
1186 end_line,
1187 end_column,
1188 } => {
1189 put_event_header(output, EVENT_CODE_EDIT_REJECTED, 44, event);
1190 for value in [
1191 request_id,
1192 base_version,
1193 current_version,
1194 start_line,
1195 start_column,
1196 end_line,
1197 end_column,
1198 ] {
1199 output.extend_from_slice(&value.to_le_bytes());
1200 }
1201 return Ok(());
1202 }
1203 crate::schema::EventPayload::CodeHovered {
1204 decoration,
1205 line,
1206 column,
1207 phase,
1208 } => {
1209 put_event_header(output, EVENT_CODE_HOVERED, 36, event);
1210 output.extend_from_slice(&decoration.to_le_bytes());
1211 output.extend_from_slice(&line.to_le_bytes());
1212 output.extend_from_slice(&column.to_le_bytes());
1213 output.extend_from_slice(&u32::from(u16::from(phase)).to_le_bytes());
1214 return Ok(());
1215 }
1216 crate::schema::EventPayload::CodeSaveRequested { version } => {
1217 put_event_header(output, EVENT_CODE_SAVE_REQUESTED, 24, event);
1218 output.extend_from_slice(&version.to_le_bytes());
1219 output.extend_from_slice(&0_u32.to_le_bytes());
1221 return Ok(());
1222 }
1223 crate::schema::EventPayload::DecorationActivated { decoration } => {
1224 put_event_header(output, EVENT_DECORATION_ACTIVATED, 24, event);
1225 output.extend_from_slice(&decoration.to_le_bytes());
1226 return Ok(());
1227 }
1228 crate::schema::EventPayload::SelectionChanged {
1229 version,
1230 anchor_line,
1231 anchor_column,
1232 active_line,
1233 active_column,
1234 } => {
1235 put_event_header(output, EVENT_SELECTION_CHANGED, 36, event);
1236 for value in [
1237 version,
1238 anchor_line,
1239 anchor_column,
1240 active_line,
1241 active_column,
1242 ] {
1243 output.extend_from_slice(&value.to_le_bytes());
1244 }
1245 return Ok(());
1246 }
1247 crate::schema::EventPayload::VisibleRangeChanged {
1248 version,
1249 first_line,
1250 last_line,
1251 } => {
1252 put_event_header(output, EVENT_VISIBLE_RANGE_CHANGED, 28, event);
1253 for value in [version, first_line, last_line] {
1254 output.extend_from_slice(&value.to_le_bytes());
1255 }
1256 return Ok(());
1257 }
1258 crate::schema::EventPayload::EditorFocusChanged { focused } => {
1259 put_event_header(output, EVENT_EDITOR_FOCUS_CHANGED, 24, event);
1260 output.extend_from_slice(&u32::from(focused).to_le_bytes());
1261 output.extend_from_slice(&0_u32.to_le_bytes());
1262 return Ok(());
1263 }
1264 crate::schema::EventPayload::FoldChanged {
1265 start_line,
1266 collapsed,
1267 } => {
1268 put_event_header(output, EVENT_FOLD_CHANGED, 24, event);
1269 output.extend_from_slice(&start_line.to_le_bytes());
1270 output.extend_from_slice(&u32::from(collapsed).to_le_bytes());
1271 return Ok(());
1272 }
1273 crate::schema::EventPayload::CompositionChanged { composing } => {
1274 put_event_header(output, EVENT_COMPOSITION_CHANGED, 24, event);
1275 output.extend_from_slice(&u32::from(composing).to_le_bytes());
1276 output.extend_from_slice(&0_u32.to_le_bytes());
1277 return Ok(());
1278 }
1279 _ => {}
1280 }
1281 match event.payload {
1282 crate::schema::EventPayload::WindowFocusChanged { focused } => {
1283 put_event_header(output, EVENT_WINDOW_FOCUS_CHANGED, 20, event);
1284 output.extend_from_slice(&u32::from(focused).to_le_bytes());
1285 return Ok(());
1286 }
1287 crate::schema::EventPayload::WindowMoved {
1288 x,
1289 y,
1290 width,
1291 height,
1292 } => {
1293 put_event_header(output, EVENT_WINDOW_MOVED, 32, event);
1294 for value in [x, y, width, height] {
1295 output.extend_from_slice(&value.to_bits().to_le_bytes());
1296 }
1297 return Ok(());
1298 }
1299 crate::schema::EventPayload::WindowDrag {
1300 phase,
1301 screen_x,
1302 screen_y,
1303 over_window,
1304 } => {
1305 put_event_header(output, EVENT_WINDOW_DRAG, 32, event);
1306 output.extend_from_slice(&phase.to_le_bytes());
1307 output.extend_from_slice(&screen_x.to_bits().to_le_bytes());
1308 output.extend_from_slice(&screen_y.to_bits().to_le_bytes());
1309 output.extend_from_slice(&over_window.to_le_bytes());
1310 return Ok(());
1311 }
1312 crate::schema::EventPayload::WindowDragOver { kind, window, x, y } => {
1313 put_event_header(output, EVENT_WINDOW_DRAG_OVER, 32, event);
1314 output.extend_from_slice(&kind.to_le_bytes());
1315 output.extend_from_slice(&window.to_le_bytes());
1316 output.extend_from_slice(&x.to_bits().to_le_bytes());
1317 output.extend_from_slice(&y.to_bits().to_le_bytes());
1318 return Ok(());
1319 }
1320 _ => {}
1321 }
1322 if let crate::schema::EventPayload::FileDialogResult {
1323 request,
1324 status,
1325 filter,
1326 paths,
1327 } = event.payload
1328 {
1329 put_event_header(
1330 output,
1331 EVENT_FILE_DIALOG_RESULT,
1332 FILE_DIALOG_RESULT_LEN as u16,
1333 event,
1334 );
1335 output.extend_from_slice(&request.to_le_bytes());
1336 output.extend_from_slice(&u32::from(status as u16).to_le_bytes());
1337 output.extend_from_slice(&filter.to_le_bytes());
1338 let len = u32::try_from(paths.len()).map_err(|_| ProtocolError::LengthOverflow)?;
1339 output.extend_from_slice(&(FILE_DIALOG_RESULT_LEN as u32).to_le_bytes());
1340 output.extend_from_slice(&len.to_le_bytes());
1341 output.extend_from_slice(paths.as_bytes());
1342 return Ok(());
1343 }
1344 if let crate::schema::EventPayload::ClipboardText {
1345 request,
1346 status,
1347 len,
1348 text,
1349 } = event.payload
1350 {
1351 put_event_header(
1352 output,
1353 EVENT_CLIPBOARD_TEXT,
1354 CLIPBOARD_TEXT_LEN as u16,
1355 event,
1356 );
1357 output.extend_from_slice(&request.to_le_bytes());
1358 output.extend_from_slice(&status.to_le_bytes());
1359 output.extend_from_slice(&len.to_le_bytes());
1360 let text_len = u32::try_from(text.len()).map_err(|_| ProtocolError::LengthOverflow)?;
1361 output.extend_from_slice(&(CLIPBOARD_TEXT_LEN as u32).to_le_bytes());
1362 output.extend_from_slice(&text_len.to_le_bytes());
1363 output.extend_from_slice(text.as_bytes());
1364 return Ok(());
1365 }
1366 match event.payload {
1367 crate::schema::EventPayload::CodeMetrics { line_height_px } => {
1368 put_event_header(output, EVENT_CODE_METRICS, 24, event);
1369 output.extend_from_slice(&line_height_px.to_bits().to_le_bytes());
1370 return Ok(());
1371 }
1372 crate::schema::EventPayload::CodeScrolled { offset_px } => {
1373 put_event_header(output, EVENT_CODE_SCROLLED, 24, event);
1374 output.extend_from_slice(&offset_px.to_bits().to_le_bytes());
1375 return Ok(());
1376 }
1377 _ => {}
1378 }
1379 if let crate::schema::EventPayload::IoAnswer {
1380 request,
1381 status,
1382 data,
1383 } = event.payload
1384 {
1385 put_event_header(output, EVENT_IO_ANSWER, IO_ANSWER_LEN as u16, event);
1386 output.extend_from_slice(&request.to_le_bytes());
1387 output.extend_from_slice(&u32::from(u16::from(status)).to_le_bytes());
1388 let data_len = u32::try_from(data.len()).map_err(|_| ProtocolError::LengthOverflow)?;
1389 output.extend_from_slice(&(IO_ANSWER_LEN as u32).to_le_bytes());
1390 output.extend_from_slice(&data_len.to_le_bytes());
1391 output.extend_from_slice(data);
1392 return Ok(());
1393 }
1394 if let crate::schema::EventPayload::FileHandles {
1395 request,
1396 status,
1397 filter,
1398 delivery,
1399 first_handle,
1400 names,
1401 kinds,
1402 } = event.payload
1403 {
1404 put_event_header(output, EVENT_FILE_HANDLES, FILE_HANDLES_LEN as u16, event);
1405 output.extend_from_slice(&request.to_le_bytes());
1406 output.extend_from_slice(&u32::from(status as u16).to_le_bytes());
1407 output.extend_from_slice(&filter.to_le_bytes());
1408 output.extend_from_slice(&u32::from(delivery as u16).to_le_bytes());
1409 output.extend_from_slice(&first_handle.to_le_bytes());
1410 let names_len = u32::try_from(names.len()).map_err(|_| ProtocolError::LengthOverflow)?;
1411 let kinds_len = u32::try_from(kinds.len()).map_err(|_| ProtocolError::LengthOverflow)?;
1412 let kinds_at = (FILE_HANDLES_LEN as u32)
1413 .checked_add(names_len)
1414 .ok_or(ProtocolError::LengthOverflow)?;
1415 output.extend_from_slice(&(FILE_HANDLES_LEN as u32).to_le_bytes());
1416 output.extend_from_slice(&names_len.to_le_bytes());
1417 output.extend_from_slice(&kinds_at.to_le_bytes());
1418 output.extend_from_slice(&kinds_len.to_le_bytes());
1419 output.extend_from_slice(names.as_bytes());
1420 output.extend_from_slice(kinds.as_bytes());
1421 return Ok(());
1422 }
1423 if let crate::schema::EventPayload::ScrollChanged {
1424 item,
1425 settled,
1426 can_scroll_backward,
1427 can_scroll_forward,
1428 x_px,
1429 y_px,
1430 max_x_px,
1431 max_y_px,
1432 viewport_width_px,
1433 viewport_height_px,
1434 } = event.payload
1435 {
1436 use crate::schema::scroll_changed_flags as flag;
1437 put_event_header(
1438 output,
1439 EVENT_SCROLL_CHANGED,
1440 SCROLL_CHANGED_LEN as u16,
1441 event,
1442 );
1443 let mut flags = 0_u32;
1444 if settled {
1445 flags |= flag::SETTLED;
1446 }
1447 if can_scroll_backward {
1448 flags |= flag::CAN_SCROLL_BACKWARD;
1449 }
1450 if can_scroll_forward {
1451 flags |= flag::CAN_SCROLL_FORWARD;
1452 }
1453 output.extend_from_slice(&item.to_le_bytes());
1454 output.extend_from_slice(&flags.to_le_bytes());
1455 for value in [
1456 x_px,
1457 y_px,
1458 max_x_px,
1459 max_y_px,
1460 viewport_width_px,
1461 viewport_height_px,
1462 ] {
1463 output.extend_from_slice(&value.to_bits().to_le_bytes());
1464 }
1465 return Ok(());
1466 }
1467 if let crate::schema::EventPayload::ZoomChanged { k, os, level } = event.payload {
1468 output.extend_from_slice(&EVENT_ZOOM_CHANGED.to_le_bytes());
1469 output.extend_from_slice(&28_u16.to_le_bytes());
1470 output.extend_from_slice(&event.node_id.to_le_bytes());
1471 output.extend_from_slice(&event.handler_id.to_le_bytes());
1472 output.extend_from_slice(&k.to_bits().to_le_bytes());
1473 output.extend_from_slice(&os.to_bits().to_le_bytes());
1474 output.extend_from_slice(&level.to_le_bytes());
1475 return Ok(());
1476 }
1477 if let crate::schema::EventPayload::RangeRequested { start, count } = event.payload {
1478 output.extend_from_slice(&EVENT_RANGE_REQUESTED.to_le_bytes());
1479 output.extend_from_slice(&24_u16.to_le_bytes());
1480 output.extend_from_slice(&event.node_id.to_le_bytes());
1481 output.extend_from_slice(&event.handler_id.to_le_bytes());
1482 output.extend_from_slice(&start.to_le_bytes());
1483 output.extend_from_slice(&count.to_le_bytes());
1484 return Ok(());
1485 }
1486 let (tag, record_len, text, error_code) = match &event.payload {
1487 crate::schema::EventPayload::Clicked => (EVENT_CLICK, 16_u16, None, None),
1488 crate::schema::EventPayload::TextChanged(value) => {
1489 (EVENT_TEXT_CHANGED, 24, Some(*value), None)
1490 }
1491 crate::schema::EventPayload::TextSubmitted(value) => {
1492 (EVENT_TEXT_SUBMITTED, 24, Some(*value), None)
1493 }
1494 crate::schema::EventPayload::FocusLost => (EVENT_FOCUS_LOST, 16, None, None),
1495 crate::schema::EventPayload::FocusGained => (EVENT_FOCUS_GAINED, 16, None, None),
1496 crate::schema::EventPayload::FilesEntered => (EVENT_FILES_ENTERED, 16, None, None),
1497 crate::schema::EventPayload::FilesDropped(paths) => {
1498 (EVENT_FILES_DROPPED, 24, Some(*paths), None)
1499 }
1500 crate::schema::EventPayload::StorageLocation(path) => {
1501 (EVENT_STORAGE_LOCATION, 24, Some(*path), None)
1502 }
1503 crate::schema::EventPayload::Resync => (EVENT_RESYNC, 16, None, None),
1504 crate::schema::EventPayload::WindowCloseRequested => {
1505 (EVENT_WINDOW_CLOSE_REQUESTED, 16, None, None)
1506 }
1507 crate::schema::EventPayload::WindowUnavailable => {
1508 (EVENT_WINDOW_UNAVAILABLE, 16, None, None)
1509 }
1510 crate::schema::EventPayload::WindowClosed => (EVENT_WINDOW_CLOSED, 16, None, None),
1511 crate::schema::EventPayload::ApplicationReopen => {
1512 (EVENT_APPLICATION_REOPEN, 16, None, None)
1513 }
1514 crate::schema::EventPayload::WindowRequested { key, request } => {
1516 (EVENT_WINDOW_REQUESTED, 28, Some(*key), Some(*request))
1517 }
1518 crate::schema::EventPayload::LifecycleStart => (EVENT_LIFECYCLE_START, 16, None, None),
1519 crate::schema::EventPayload::LifecycleStop => (EVENT_LIFECYCLE_STOP, 16, None, None),
1520 crate::schema::EventPayload::ProtocolError { code, message } => {
1521 (EVENT_PROTOCOL_ERROR, 28, Some(*message), Some(*code))
1522 }
1523 crate::schema::EventPayload::NotificationActivated { action, key } => {
1525 (EVENT_NOTIFICATION_ACTIVATED, 28, Some(*key), Some(*action))
1526 }
1527 crate::schema::EventPayload::KeyDown { .. }
1528 | crate::schema::EventPayload::KeyUp { .. }
1529 | crate::schema::EventPayload::RangeRequested { .. }
1530 | crate::schema::EventPayload::ValueChanged(_)
1531 | crate::schema::EventPayload::WindowSizeChanged { .. }
1532 | crate::schema::EventPayload::DesignSystemResolved(_)
1533 | crate::schema::EventPayload::NotificationPermissionChanged(_)
1534 | crate::schema::EventPayload::CodeChanged { .. }
1535 | crate::schema::EventPayload::CodeEditRejected { .. }
1536 | crate::schema::EventPayload::CodeHovered { .. }
1537 | crate::schema::EventPayload::CodeSaveRequested { .. }
1538 | crate::schema::EventPayload::DecorationActivated { .. }
1539 | crate::schema::EventPayload::SelectionChanged { .. }
1540 | crate::schema::EventPayload::VisibleRangeChanged { .. }
1541 | crate::schema::EventPayload::EditorFocusChanged { .. }
1542 | crate::schema::EventPayload::FoldChanged { .. }
1543 | crate::schema::EventPayload::CompositionChanged { .. }
1544 | crate::schema::EventPayload::WindowFocusChanged { .. }
1545 | crate::schema::EventPayload::WindowMoved { .. }
1546 | crate::schema::EventPayload::WindowDrag { .. }
1547 | crate::schema::EventPayload::WindowDragOver { .. }
1548 | crate::schema::EventPayload::CodeMetrics { .. }
1549 | crate::schema::EventPayload::CodeScrolled { .. }
1550 | crate::schema::EventPayload::FileDialogResult { .. }
1551 | crate::schema::EventPayload::ClipboardText { .. }
1552 | crate::schema::EventPayload::IoAnswer { .. }
1553 | crate::schema::EventPayload::FileHandles { .. }
1554 | crate::schema::EventPayload::ScrollChanged { .. } => unreachable!(),
1555 crate::schema::EventPayload::AnimationEvent { .. }
1556 | crate::schema::EventPayload::Pointer { .. }
1557 | crate::schema::EventPayload::ReducedMotionChanged(_)
1558 | crate::schema::EventPayload::ZoomChanged { .. } => unreachable!(),
1559 };
1560 output.extend_from_slice(&tag.to_le_bytes());
1561 output.extend_from_slice(&record_len.to_le_bytes());
1562 output.extend_from_slice(&event.node_id.to_le_bytes());
1563 output.extend_from_slice(&event.handler_id.to_le_bytes());
1564 if let Some(code) = error_code {
1565 output.extend_from_slice(&code.to_le_bytes());
1566 }
1567 if let Some(text) = text {
1568 let offset = u32::from(record_len);
1569 let len = u32::try_from(text.len()).map_err(|_| ProtocolError::LengthOverflow)?;
1570 output.extend_from_slice(&offset.to_le_bytes());
1571 output.extend_from_slice(&len.to_le_bytes());
1572 output.extend_from_slice(text.as_bytes());
1573 }
1574 Ok(())
1575}
1576
1577fn put_event_header(output: &mut Vec<u8>, tag: u16, record_len: u16, event: &HostEvent<'_>) {
1580 output.extend_from_slice(&tag.to_le_bytes());
1581 output.extend_from_slice(&record_len.to_le_bytes());
1582 output.extend_from_slice(&event.node_id.to_le_bytes());
1583 output.extend_from_slice(&event.handler_id.to_le_bytes());
1584}
1585
1586impl fmt::Display for ProtocolError {
1587 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
1588 write!(formatter, "protocol error: {self:?}")
1589 }
1590}
1591
1592impl std::error::Error for ProtocolError {}
1593
1594pub struct BatchEncoder {
1596 records: Vec<u8>,
1597 strings: Vec<u8>,
1598 string_fixups: Vec<usize>,
1599 arena: Vec<u8>,
1600 record_count: u32,
1601}
1602
1603impl Default for BatchEncoder {
1604 fn default() -> Self {
1605 Self::with_capacity(4096, 1024, 64)
1606 }
1607}
1608
1609impl BatchEncoder {
1610 pub fn with_capacity(record_bytes: usize, string_bytes: usize, string_count: usize) -> Self {
1611 let mut encoder = Self {
1612 records: Vec::with_capacity(record_bytes),
1613 strings: Vec::with_capacity(string_bytes),
1614 string_fixups: Vec::with_capacity(string_count),
1615 arena: Vec::with_capacity(record_bytes + string_bytes),
1616 record_count: 0,
1617 };
1618 encoder.clear();
1619 encoder
1620 }
1621
1622 pub fn clear(&mut self) {
1623 self.records.clear();
1624 self.strings.clear();
1625 self.string_fixups.clear();
1626 self.arena.clear();
1627 self.record_count = 0;
1628 self.begin_record(TAG_ENVELOPE, 8);
1629 self.put_u32(0);
1630 self.put_u32(0);
1631 }
1632
1633 pub fn encode(&mut self, mutation: &Mutation<'_>) -> Result<(), ProtocolError> {
1634 match mutation {
1635 Mutation::Create { node_id, widget } => {
1636 self.begin_record(TAG_CREATE, 8);
1637 self.put_u32(*node_id);
1638 self.put_u16(*widget as u16);
1639 self.put_u16(0);
1640 }
1641 Mutation::SetProp {
1642 node_id,
1643 property,
1644 value,
1645 } => {
1646 self.begin_record(TAG_SET_PROP, 20);
1647 self.put_u32(*node_id);
1648 self.put_u16(*property as u16);
1649 match value {
1650 PropertyValue::None => {
1651 self.put_u16(VALUE_NONE);
1652 self.put_u64(0);
1653 self.put_u32(0);
1654 }
1655 PropertyValue::String(value) => {
1656 self.put_u16(VALUE_STRING);
1657 self.put_string_ref(value)?;
1658 self.put_u32(0);
1659 }
1660 PropertyValue::Bool(value) => {
1661 self.put_u16(VALUE_BOOL);
1662 self.put_u64(u64::from(*value));
1663 self.put_u32(0);
1664 }
1665 PropertyValue::Integer(value) => {
1666 self.put_u16(VALUE_I64);
1667 self.put_u64(*value as u64);
1668 self.put_u32(0);
1669 }
1670 PropertyValue::Float(value) => {
1671 self.put_u16(VALUE_F32);
1672 self.put_u64(u64::from(value.to_bits()));
1673 self.put_u32(0);
1674 }
1675 PropertyValue::Bytes(value) => {
1676 self.put_u16(VALUE_BYTES);
1677 self.put_bytes_ref(value)?;
1678 self.put_u32(0);
1679 }
1680 }
1681 }
1682 Mutation::SetModifier {
1683 node_id,
1684 index,
1685 modifier,
1686 } => {
1687 let (tag, words) = modifier_fields(modifier);
1688 let extra = modifier_extra_words(tag).ok_or(ProtocolError::InvalidModifier(tag))?;
1692 self.begin_record(TAG_SET_MODIFIER, 24 + 8 * u16::from(extra));
1693 self.put_u32(*node_id);
1694 self.put_u16(*index);
1695 self.put_u16(tag);
1696 for word in &words[..2 + usize::from(extra)] {
1697 self.put_u64(*word);
1698 }
1699 }
1700 Mutation::Insert {
1701 parent_id,
1702 node_id,
1703 index,
1704 } => {
1705 self.begin_record(TAG_INSERT, 12);
1706 self.put_u32(*parent_id);
1707 self.put_u32(*node_id);
1708 self.put_u32(*index);
1709 }
1710 Mutation::Move {
1711 parent_id,
1712 node_id,
1713 index,
1714 } => {
1715 self.begin_record(TAG_MOVE, 12);
1716 self.put_u32(*parent_id);
1717 self.put_u32(*node_id);
1718 self.put_u32(*index);
1719 }
1720 Mutation::Remove { node_id } => {
1721 self.begin_record(TAG_REMOVE, 4);
1722 self.put_u32(*node_id);
1723 }
1724 Mutation::SetText {
1725 node_id,
1726 text,
1727 selection,
1728 } => {
1729 self.begin_record(TAG_SET_TEXT, 20);
1730 self.put_u32(*node_id);
1731 self.put_string_ref(text)?;
1732 let (start, end) =
1733 selection.map_or((u32::MAX, u32::MAX), |value| (value.start, value.end));
1734 self.put_u32(start);
1735 self.put_u32(end);
1736 }
1737 Mutation::AppendText { node_id, text } => {
1738 self.begin_record(TAG_APPEND_TEXT, 12);
1739 self.put_u32(*node_id);
1740 self.put_string_ref(text)?;
1741 }
1742 Mutation::RegisterAsset {
1743 asset_id,
1744 kind,
1745 bytes,
1746 } => {
1747 self.begin_record(TAG_REGISTER_ASSET, 16);
1748 self.put_u32(*asset_id);
1749 self.put_u16(*kind as u16);
1750 self.put_u16(0);
1751 self.put_bytes_ref(bytes)?;
1752 }
1753 Mutation::ReleaseAsset { asset_id } => {
1754 self.begin_record(TAG_RELEASE_ASSET, 4);
1755 self.put_u32(*asset_id);
1756 }
1757 Mutation::ShowMessage {
1758 handler_id,
1759 text,
1760 action,
1761 duration,
1762 } => {
1763 self.begin_record(TAG_SHOW_MESSAGE, 28);
1764 self.put_u64(*handler_id);
1765 self.put_string_ref(text)?;
1766 self.put_string_ref(action)?;
1767 self.put_u16(*duration as u16);
1768 self.put_u16(0);
1769 }
1770 Mutation::EditCode {
1771 node_id,
1772 request_id,
1773 base_version,
1774 start_line,
1775 start_column,
1776 end_line,
1777 end_column,
1778 text,
1779 } => {
1780 self.begin_record(TAG_EDIT_CODE, 36);
1781 self.put_u32(*node_id);
1782 self.put_u32(*request_id);
1783 self.put_u32(*base_version);
1784 self.put_u32(*start_line);
1785 self.put_u32(*start_column);
1786 self.put_u32(*end_line);
1787 self.put_u32(*end_column);
1788 self.put_string_ref(text)?;
1789 }
1790 Mutation::SetSelection {
1791 node_id,
1792 base_version,
1793 anchor_line,
1794 anchor_column,
1795 active_line,
1796 active_column,
1797 } => {
1798 self.begin_record(TAG_SET_SELECTION, 24);
1799 self.put_u32(*node_id);
1800 self.put_u32(*base_version);
1801 self.put_u32(*anchor_line);
1802 self.put_u32(*anchor_column);
1803 self.put_u32(*active_line);
1804 self.put_u32(*active_column);
1805 }
1806 Mutation::RevealRange {
1807 node_id,
1808 base_version,
1809 start_line,
1810 start_column,
1811 end_line,
1812 end_column,
1813 align,
1814 } => {
1815 self.begin_record(TAG_REVEAL_RANGE, 28);
1816 self.put_u32(*node_id);
1817 self.put_u32(*base_version);
1818 self.put_u32(*start_line);
1819 self.put_u32(*start_column);
1820 self.put_u32(*end_line);
1821 self.put_u32(*end_column);
1822 self.put_u32(u32::from(*align as u16));
1823 }
1824 Mutation::SetFolded {
1825 node_id,
1826 start_line,
1827 collapsed,
1828 } => {
1829 self.begin_record(TAG_SET_FOLDED, 12);
1830 self.put_u32(*node_id);
1831 self.put_u32(*start_line);
1832 self.put_u32(u32::from(*collapsed));
1833 }
1834 Mutation::SetTheme(theme) => {
1835 self.begin_record(TAG_SET_THEME, THEME_PAYLOAD_LEN as u16);
1836 self.put_theme(theme)?;
1837 }
1838 Mutation::SetWindowTheme { node_id, theme } => {
1839 self.begin_record(TAG_SET_WINDOW_THEME, 4 + THEME_PAYLOAD_LEN as u16);
1840 self.put_u32(*node_id);
1841 self.put_theme(theme)?;
1842 }
1843 Mutation::FocusWindow { node_id } => {
1844 self.begin_record(TAG_FOCUS_WINDOW, 4);
1845 self.put_u32(*node_id);
1846 }
1847 Mutation::RequestFocus { node_id } => {
1848 self.begin_record(TAG_REQUEST_FOCUS, 4);
1849 self.put_u32(*node_id);
1850 }
1851 Mutation::SetScrollOffset { node_id, offset_px } => {
1852 self.begin_record(TAG_SET_SCROLL_OFFSET, 12);
1853 self.put_u32(*node_id);
1854 self.put_u64(offset_px.to_bits());
1855 }
1856 Mutation::ShowFileDialog {
1857 request,
1858 window,
1859 kind,
1860 default_filter,
1861 title,
1862 directory,
1863 name,
1864 filters,
1865 } => {
1866 self.begin_record(TAG_SHOW_FILE_DIALOG, SHOW_FILE_DIALOG_PAYLOAD_LEN as u16);
1867 self.put_u32(*request);
1868 self.put_u32(*window);
1869 self.put_u16(*kind as u16);
1870 self.put_u16(*default_filter);
1871 self.put_string_ref(title)?;
1872 self.put_string_ref(directory)?;
1873 self.put_string_ref(name)?;
1874 self.put_string_ref(filters)?;
1875 }
1876 Mutation::SetOverlayTokens(overlay) => {
1877 self.begin_record(TAG_SET_OVERLAY_TOKENS, OVERLAY_TOKENS_PAYLOAD_LEN as u16);
1878 self.put_u16(overlay.sheet_corner_radius);
1879 self.put_u16(overlay.dialog_corner_radius);
1880 self.put_u16(overlay.popup_corner_radius);
1881 self.put_u16(overlay.sheet_elevation);
1882 self.put_u16(overlay.popup_elevation);
1883 self.put_u16(overlay.dialog_elevation);
1884 self.put_u16(overlay.sheet_padding);
1885 self.put_u16(overlay.dialog_padding);
1886 self.put_u16(overlay.popup_padding);
1887 self.put_u16(0);
1888 self.put_u32(overlay.scrim);
1889 }
1890 Mutation::SetClipboardText { text } => {
1891 self.begin_record(TAG_SET_CLIPBOARD_TEXT, 8);
1892 self.put_string_ref(text)?;
1893 }
1894 Mutation::ReadClipboard { request } => {
1895 self.begin_record(TAG_READ_CLIPBOARD, 4);
1896 self.put_u32(*request);
1897 }
1898 Mutation::StorageCall {
1899 request,
1900 op,
1901 store,
1902 key,
1903 data,
1904 } => {
1905 self.begin_record(TAG_STORAGE_CALL, STORAGE_CALL_PAYLOAD_LEN as u16);
1906 self.put_u32(*request);
1907 self.put_u16(u16::from(*op));
1908 self.put_u16(0);
1909 self.put_string_ref(store)?;
1910 self.put_string_ref(key)?;
1911 self.put_bytes_ref(data)?;
1912 }
1913 Mutation::FileCall {
1914 request,
1915 op,
1916 create,
1917 handle,
1918 name,
1919 data,
1920 } => {
1921 self.begin_record(TAG_FILE_CALL, FILE_CALL_PAYLOAD_LEN as u16);
1922 self.put_u32(*request);
1923 self.put_u16(*op as u16);
1924 self.put_u16(u16::from(*create));
1925 self.put_u32(*handle);
1926 self.put_string_ref(name)?;
1927 self.put_bytes_ref(data)?;
1928 }
1929 Mutation::ReleaseFileHandle { handle } => {
1930 self.begin_record(TAG_RELEASE_FILE_HANDLE, 4);
1931 self.put_u32(*handle);
1932 }
1933 Mutation::ScrollTo {
1934 node_id,
1935 item,
1936 flags,
1937 x_px,
1938 y_px,
1939 } => {
1940 self.begin_record(TAG_SCROLL_TO, (SCROLL_TO_LEN - 4) as u16);
1941 self.put_u32(*node_id);
1942 self.put_u32(*item);
1943 self.put_u32(*flags);
1944 self.put_u64(x_px.to_bits());
1945 self.put_u64(y_px.to_bits());
1946 }
1947 Mutation::ScrollIntoView {
1948 node_id,
1949 target,
1950 target_kind,
1951 align_x,
1952 align_y,
1953 flags,
1954 rect,
1955 } => {
1956 self.begin_record(TAG_SCROLL_INTO_VIEW, (SCROLL_INTO_VIEW_LEN - 4) as u16);
1957 self.put_u32(*node_id);
1958 self.put_u32(*target);
1959 self.put_u16(*target_kind);
1960 self.put_u16(*align_x);
1961 self.put_u16(*align_y);
1962 self.put_u16(*flags);
1963 self.put_u64(rect.x_px.to_bits());
1964 self.put_u64(rect.y_px.to_bits());
1965 self.put_u64(rect.width_px.to_bits());
1966 self.put_u64(rect.height_px.to_bits());
1967 }
1968 Mutation::SetWindow(window) => {
1969 self.begin_record(TAG_SET_WINDOW, 28);
1970 self.put_u16(window.chrome as u16);
1971 self.put_u16(window.title_bar as u16);
1972 self.put_u16(window.width);
1973 self.put_u16(window.height);
1974 self.put_u16(window.min_width);
1975 self.put_u16(window.min_height);
1976 self.put_u16(
1981 u16::from(window.resizable)
1982 | (u16::from(window.report_size) << 1)
1983 | ((window.mode as u16) << WINDOW_MODE_SHIFT)
1984 | ((window.last_window_closed as u16) << WINDOW_POLICY_SHIFT),
1985 );
1986 self.put_u16(match window.zoom_level {
1991 None => 0,
1992 Some(level) => 1 | (u16::from(crate::zoom::clamp_level(level) as u8) << 8),
1993 });
1994 self.put_string_ref(window.title)?;
1995 self.put_u32(window.icon);
1996 }
1997 Mutation::PostNotification {
1998 key,
1999 title,
2000 body,
2001 channel,
2002 action_1,
2003 action_2,
2004 importance,
2005 presentation,
2006 } => {
2007 self.begin_record(TAG_POST_NOTIFICATION, 52);
2008 self.put_string_ref(key)?;
2009 self.put_string_ref(title)?;
2010 self.put_string_ref(body)?;
2011 self.put_string_ref(channel)?;
2012 self.put_string_ref(action_1)?;
2013 self.put_string_ref(action_2)?;
2014 self.put_u16(*importance as u16);
2015 self.put_u16(*presentation as u16);
2016 }
2017 Mutation::WithdrawNotification { key } => {
2018 self.begin_record(TAG_WITHDRAW_NOTIFICATION, 8);
2019 self.put_string_ref(key)?;
2020 }
2021 Mutation::RequestNotificationPermission => {
2022 self.begin_record(TAG_REQUEST_NOTIFICATION_PERMISSION, 0);
2023 }
2024 Mutation::StartAnimation(animation) => self.put_animation(animation)?,
2025 Mutation::ControlAnimation {
2026 node_id,
2027 animation_id,
2028 property,
2029 slot,
2030 op,
2031 at_time_nanos,
2032 } => {
2033 self.begin_record(TAG_CONTROL_ANIMATION, 20);
2034 self.put_u32(*node_id);
2035 self.put_u32(*animation_id);
2036 self.records.push(*property as u8);
2037 self.records.push(*slot);
2038 self.records.push(*op as u8);
2039 self.records.push(0);
2040 self.put_u64(*at_time_nanos);
2041 }
2042 }
2043 self.record_count = self
2044 .record_count
2045 .checked_add(1)
2046 .ok_or(ProtocolError::LengthOverflow)?;
2047 Ok(())
2048 }
2049
2050 pub fn finish(&mut self) -> Result<&[u8], ProtocolError> {
2051 let records_len =
2052 u32::try_from(self.records.len()).map_err(|_| ProtocolError::LengthOverflow)?;
2053 self.records[4..8].copy_from_slice(&records_len.to_le_bytes());
2054 self.records[8..12].copy_from_slice(&self.record_count.to_le_bytes());
2055 let total = self
2056 .records
2057 .len()
2058 .checked_add(self.strings.len())
2059 .ok_or(ProtocolError::LengthOverflow)?;
2060 self.arena.clear();
2061 if self.arena.capacity() < total {
2062 self.arena.reserve(total);
2063 }
2064 self.arena.extend_from_slice(&self.records);
2065 self.arena.extend_from_slice(&self.strings);
2066 for &position in &self.string_fixups {
2067 let relative = read_u32(&self.arena, position)?;
2068 let absolute = records_len
2069 .checked_add(relative)
2070 .ok_or(ProtocolError::LengthOverflow)?;
2071 self.arena[position..position + 4].copy_from_slice(&absolute.to_le_bytes());
2072 }
2073 Ok(&self.arena)
2074 }
2075
2076 pub fn arena(&self) -> (*const u8, usize) {
2083 (self.arena.as_ptr(), self.arena.capacity())
2084 }
2085
2086 pub fn capacity(&self) -> usize {
2087 self.records.capacity() + self.strings.capacity() + self.arena.capacity()
2088 }
2089
2090 fn put_animation(
2096 &mut self,
2097 animation: &crate::schema::Animation<'_>,
2098 ) -> Result<(), ProtocolError> {
2099 use crate::schema::{KeyframeValue, Timing};
2100 let keyframes_len: usize = animation
2101 .keyframes
2102 .iter()
2103 .map(|keyframe| {
2104 KEYFRAME_HEAD_LEN
2105 + match &keyframe.value {
2106 KeyframeValue::Alpha(_) => 4,
2107 KeyframeValue::Paint(_) => 8,
2108 KeyframeValue::Transform(functions) => {
2109 4 + TRANSFORM_FUNCTION_LEN * functions.len()
2110 }
2111 }
2112 })
2113 .sum();
2114 let payload = u16::try_from(ANIMATION_HEADER_LEN - 4 + keyframes_len)
2115 .ok()
2116 .filter(|payload| *payload <= u16::MAX - 4)
2117 .ok_or(ProtocolError::LengthOverflow)?;
2118 let keyframe_count =
2119 u16::try_from(animation.keyframes.len()).map_err(|_| ProtocolError::LengthOverflow)?;
2120 self.begin_record(TAG_START_ANIMATION, payload);
2121 self.put_u32(animation.node_id);
2122 self.put_u32(animation.animation_id);
2123 self.records.push(animation.property as u8);
2124 self.records.push(animation.slot);
2125 self.records.push(animation.direction as u8);
2126 self.records.push(animation.fill as u8);
2127 self.records.push(animation.play_state as u8);
2128 self.records
2129 .push(animation.interpolation.map_or(0, |value| value as u8));
2130 self.put_u16(keyframe_count);
2131 self.put_u64(animation.start_time_nanos);
2132 self.put_u32(animation.delay_ms.to_bits());
2133 self.put_u32(animation.duration_ms.to_bits());
2134 self.put_u32(animation.iterations.to_bits());
2135 self.put_u32(animation.origin_x.to_bits());
2136 self.put_u32(animation.origin_y.to_bits());
2137 self.records.push(animation.events.0);
2138 self.records.extend_from_slice(&[0, 0, 0]);
2139 for keyframe in animation.keyframes.iter() {
2140 self.put_u32(keyframe.offset.to_bits());
2141 let (kind, position, numbers) = match keyframe.timing {
2142 Timing::Linear => (1_u8, 0_u8, [0_u32; 4]),
2143 Timing::CubicBezier { x1, y1, x2, y2 } => (
2144 2,
2145 0,
2146 [x1.to_bits(), y1.to_bits(), x2.to_bits(), y2.to_bits()],
2147 ),
2148 Timing::Steps { count, position } => (3, position as u8, [count, 0, 0, 0]),
2149 };
2150 self.records.push(kind);
2151 self.records.push(position);
2152 self.put_u16(if keyframe.from_presented {
2153 KEYFRAME_FROM_PRESENTED
2154 } else {
2155 0
2156 });
2157 for number in numbers {
2158 self.put_u32(number);
2159 }
2160 match &keyframe.value {
2161 KeyframeValue::Alpha(value) => self.put_u32(value.to_bits()),
2162 KeyframeValue::Paint(paint) => self.put_u64(paint.to_bits()),
2163 KeyframeValue::Transform(functions) => {
2164 let count = u16::try_from(functions.len())
2165 .map_err(|_| ProtocolError::LengthOverflow)?;
2166 self.put_u16(count);
2167 self.put_u16(0);
2168 for function in functions.iter() {
2169 self.put_u32(u32::from(u16::from(function.kind)));
2170 for value in function.values {
2171 self.put_u32(value.to_bits());
2172 }
2173 }
2174 }
2175 }
2176 }
2177 Ok(())
2178 }
2179
2180 fn put_theme(&mut self, theme: &Theme) -> Result<(), ProtocolError> {
2182 self.put_u16(theme.design_system as u16);
2183 self.put_u16(theme.fallback as u16);
2184 self.put_u16(theme.color_scheme as u16);
2185 self.put_u16(u16::from(theme.adaptive));
2186 for asset in theme.fonts {
2190 self.put_u32(asset);
2191 }
2192 self.put_palette_ref(theme.palette)
2197 }
2198
2199 fn begin_record(&mut self, tag: u16, payload_len: u16) {
2200 self.put_u16(tag);
2201 self.put_u16(payload_len + 4);
2202 }
2203
2204 fn put_palette_ref(
2207 &mut self,
2208 palette: Option<&crate::palette::Palette>,
2209 ) -> Result<(), ProtocolError> {
2210 let start = self.strings.len();
2211 let offset = u32::try_from(start).map_err(|_| ProtocolError::LengthOverflow)?;
2212 if let Some(palette) = palette {
2213 palette.write_into(&mut self.strings);
2214 }
2215 let len =
2216 u32::try_from(self.strings.len() - start).map_err(|_| ProtocolError::LengthOverflow)?;
2217 self.string_fixups.push(self.records.len());
2218 self.put_u32(offset);
2219 self.put_u32(len);
2220 Ok(())
2221 }
2222
2223 fn put_string_ref(&mut self, value: &str) -> Result<(), ProtocolError> {
2224 self.put_bytes_ref(value.as_bytes())
2225 }
2226
2227 fn put_bytes_ref(&mut self, value: &[u8]) -> Result<(), ProtocolError> {
2230 let offset =
2231 u32::try_from(self.strings.len()).map_err(|_| ProtocolError::LengthOverflow)?;
2232 let len = u32::try_from(value.len()).map_err(|_| ProtocolError::LengthOverflow)?;
2233 self.string_fixups.push(self.records.len());
2234 self.put_u32(offset);
2235 self.put_u32(len);
2236 self.strings.extend_from_slice(value);
2237 Ok(())
2238 }
2239
2240 fn put_u16(&mut self, value: u16) {
2241 self.records.extend_from_slice(&value.to_le_bytes());
2242 }
2243
2244 fn put_u32(&mut self, value: u32) {
2245 self.records.extend_from_slice(&value.to_le_bytes());
2246 }
2247
2248 fn put_u64(&mut self, value: u64) {
2249 self.records.extend_from_slice(&value.to_le_bytes());
2250 }
2251}
2252
2253fn decode_theme(bytes: &[u8], payload: usize, records_len: usize) -> Result<Theme, ProtocolError> {
2256 let design_system = read_u16(bytes, payload)?;
2257 let fallback = read_u16(bytes, payload + 2)?;
2258 let color_scheme = read_u16(bytes, payload + 4)?;
2259 let adaptive = read_u16(bytes, payload + 6)?;
2260 if adaptive > 1 {
2261 return Err(ProtocolError::InvalidTheme(adaptive));
2262 }
2263 let mut fonts = [0u32; TYPE_ROLE_COUNT];
2264 for (index, slot) in fonts.iter_mut().enumerate() {
2265 *slot = read_u32(bytes, payload + 8 + 4 * index)?;
2266 }
2267 let palette_bytes = read_bytes(bytes, payload + 8 + 4 * TYPE_ROLE_COUNT, records_len)?;
2272 let palette = if palette_bytes.is_empty() {
2273 None
2274 } else {
2275 let (palette, _problems) = crate::palette::decode_palette(palette_bytes);
2276 Some(&*Box::leak(Box::new(palette)))
2277 };
2278 Ok(Theme {
2279 design_system: DesignSystem::try_from(design_system)
2280 .map_err(|()| ProtocolError::InvalidTheme(design_system))?,
2281 fallback: DesignSystem::try_from(fallback)
2282 .map_err(|()| ProtocolError::InvalidTheme(fallback))?,
2283 color_scheme: ColorScheme::try_from(color_scheme)
2284 .map_err(|()| ProtocolError::InvalidTheme(color_scheme))?,
2285 adaptive: adaptive == 1,
2286 fonts,
2287 palette,
2288 overlay: None,
2289 })
2290}
2291
2292pub fn decode_batch(bytes: &[u8]) -> Result<Vec<Mutation<'_>>, ProtocolError> {
2293 if bytes.len() < ENVELOPE_LEN
2294 || read_u16(bytes, 0)? != TAG_ENVELOPE
2295 || read_u16(bytes, 2)? != 12
2296 {
2297 return Err(ProtocolError::InvalidEnvelope);
2298 }
2299 let records_len =
2300 usize::try_from(read_u32(bytes, 4)?).map_err(|_| ProtocolError::LengthOverflow)?;
2301 let count = usize::try_from(read_u32(bytes, 8)?).map_err(|_| ProtocolError::LengthOverflow)?;
2302 if records_len < ENVELOPE_LEN || records_len > bytes.len() || records_len % 4 != 0 {
2303 return Err(ProtocolError::InvalidEnvelope);
2304 }
2305 let capacity = count.min((records_len - ENVELOPE_LEN) / MIN_RECORD_LEN);
2310 let mut output = Vec::with_capacity(capacity);
2311 let mut position = ENVELOPE_LEN;
2312 while position < records_len {
2313 let tag = read_u16(bytes, position)?;
2314 let len = usize::from(read_u16(bytes, position + 2)?);
2315 if len < 4 || len % 4 != 0 || position + len > records_len {
2316 return Err(ProtocolError::InvalidRecordLength);
2317 }
2318 let payload = position + 4;
2319 let mutation = match tag {
2320 TAG_CREATE if len == 12 => Mutation::Create {
2321 node_id: read_u32(bytes, payload)?,
2322 widget: WidgetKind::try_from(read_u16(bytes, payload + 4)?).map_err(|()| {
2323 ProtocolError::InvalidWidget(read_u16(bytes, payload + 4).unwrap_or(0))
2324 })?,
2325 },
2326 TAG_SET_PROP if len == 24 => {
2327 let property_raw = read_u16(bytes, payload + 4)?;
2328 let value_kind = read_u16(bytes, payload + 6)?;
2329 let value = match value_kind {
2330 VALUE_NONE => PropertyValue::None,
2331 VALUE_STRING => {
2332 PropertyValue::String(read_string(bytes, payload + 8, records_len)?)
2333 }
2334 VALUE_BOOL => PropertyValue::Bool(read_u64(bytes, payload + 8)? != 0),
2335 VALUE_I64 => PropertyValue::Integer(read_u64(bytes, payload + 8)? as i64),
2336 VALUE_F32 => {
2337 PropertyValue::Float(f32::from_bits(read_u64(bytes, payload + 8)? as u32))
2338 }
2339 VALUE_BYTES => {
2340 PropertyValue::Bytes(read_bytes(bytes, payload + 8, records_len)?)
2341 }
2342 other => return Err(ProtocolError::InvalidValueKind(other)),
2343 };
2344 Mutation::SetProp {
2345 node_id: read_u32(bytes, payload)?,
2346 property: PropertyKind::try_from(property_raw)
2347 .map_err(|()| ProtocolError::InvalidProperty(property_raw))?,
2348 value,
2349 }
2350 }
2351 TAG_SET_MODIFIER if len >= 28 => {
2352 let modifier_tag = read_u16(bytes, payload + 6)?;
2356 let extra = usize::from(
2357 modifier_extra_words(modifier_tag)
2358 .ok_or(ProtocolError::InvalidModifier(modifier_tag))?,
2359 );
2360 if len != 28 + 8 * extra {
2361 return Err(ProtocolError::InvalidRecordLength);
2362 }
2363 let mut words = [0_u64; MAX_MODIFIER_WORDS];
2364 for (word_index, word) in words.iter_mut().take(2 + extra).enumerate() {
2365 *word = read_u64(bytes, payload + 8 + 8 * word_index)?;
2366 }
2367 Mutation::SetModifier {
2368 node_id: read_u32(bytes, payload)?,
2369 index: read_u16(bytes, payload + 4)?,
2370 modifier: decode_modifier(modifier_tag, &words)?,
2371 }
2372 }
2373 TAG_INSERT if len == 16 => Mutation::Insert {
2374 parent_id: read_u32(bytes, payload)?,
2375 node_id: read_u32(bytes, payload + 4)?,
2376 index: read_u32(bytes, payload + 8)?,
2377 },
2378 TAG_MOVE if len == 16 => Mutation::Move {
2379 parent_id: read_u32(bytes, payload)?,
2380 node_id: read_u32(bytes, payload + 4)?,
2381 index: read_u32(bytes, payload + 8)?,
2382 },
2383 TAG_REMOVE if len == 8 => Mutation::Remove {
2384 node_id: read_u32(bytes, payload)?,
2385 },
2386 TAG_SET_TEXT if len == 24 => {
2387 let start = read_u32(bytes, payload + 12)?;
2388 let end = read_u32(bytes, payload + 16)?;
2389 Mutation::SetText {
2390 node_id: read_u32(bytes, payload)?,
2391 text: read_string(bytes, payload + 4, records_len)?,
2392 selection: (start != u32::MAX || end != u32::MAX)
2393 .then_some(Selection { start, end }),
2394 }
2395 }
2396 TAG_APPEND_TEXT if len == 16 => Mutation::AppendText {
2397 node_id: read_u32(bytes, payload)?,
2398 text: read_string(bytes, payload + 4, records_len)?,
2399 },
2400 TAG_SET_THEME if len == 4 + THEME_PAYLOAD_LEN => {
2401 Mutation::SetTheme(decode_theme(bytes, payload, records_len)?)
2402 }
2403 TAG_SET_WINDOW_THEME if len == 8 + THEME_PAYLOAD_LEN => Mutation::SetWindowTheme {
2404 node_id: read_u32(bytes, payload)?,
2405 theme: decode_theme(bytes, payload + 4, records_len)?,
2406 },
2407 TAG_FOCUS_WINDOW if len == 8 => Mutation::FocusWindow {
2408 node_id: read_u32(bytes, payload)?,
2409 },
2410 TAG_REQUEST_FOCUS if len == 8 => Mutation::RequestFocus {
2411 node_id: read_u32(bytes, payload)?,
2412 },
2413 TAG_SHOW_FILE_DIALOG if len == 4 + SHOW_FILE_DIALOG_PAYLOAD_LEN => {
2414 let raw_kind = read_u16(bytes, payload + 8)?;
2415 Mutation::ShowFileDialog {
2416 request: read_u32(bytes, payload)?,
2417 window: read_u32(bytes, payload + 4)?,
2418 kind: FileDialogKind::try_from(raw_kind)
2419 .map_err(|()| ProtocolError::InvalidValueKind(raw_kind))?,
2420 default_filter: read_u16(bytes, payload + 10)?,
2421 title: read_string(bytes, payload + 12, records_len)?,
2422 directory: read_string(bytes, payload + 20, records_len)?,
2423 name: read_string(bytes, payload + 28, records_len)?,
2424 filters: read_string(bytes, payload + 36, records_len)?,
2425 }
2426 }
2427 TAG_SHOW_FILE_DIALOG => return Err(ProtocolError::InvalidRecordLength),
2428 TAG_SET_OVERLAY_TOKENS if len == 4 + OVERLAY_TOKENS_PAYLOAD_LEN => {
2429 let sheet_corner_radius = read_u16(bytes, payload)?;
2430 let dialog_corner_radius = read_u16(bytes, payload + 2)?;
2431 let popup_corner_radius = read_u16(bytes, payload + 4)?;
2432 let sheet_elevation = read_u16(bytes, payload + 6)?;
2433 let popup_elevation = read_u16(bytes, payload + 8)?;
2434 let dialog_elevation = read_u16(bytes, payload + 10)?;
2435 let sheet_padding = read_u16(bytes, payload + 12)?;
2436 let dialog_padding = read_u16(bytes, payload + 14)?;
2437 let popup_padding = read_u16(bytes, payload + 16)?;
2438 let scrim = read_u32(bytes, payload + 20)?;
2439 Mutation::SetOverlayTokens(crate::schema::OverlayTokens {
2440 sheet_corner_radius,
2441 dialog_corner_radius,
2442 popup_corner_radius,
2443 sheet_elevation,
2444 popup_elevation,
2445 dialog_elevation,
2446 sheet_padding,
2447 dialog_padding,
2448 popup_padding,
2449 scrim,
2450 })
2451 }
2452 TAG_SET_OVERLAY_TOKENS => return Err(ProtocolError::InvalidRecordLength),
2453 TAG_SET_CLIPBOARD_TEXT if len == 12 => Mutation::SetClipboardText {
2454 text: read_string(bytes, payload, records_len)?,
2455 },
2456 TAG_READ_CLIPBOARD if len == 8 => Mutation::ReadClipboard {
2457 request: read_u32(bytes, payload)?,
2458 },
2459 TAG_STORAGE_CALL if len == 4 + STORAGE_CALL_PAYLOAD_LEN => {
2460 let raw_op = read_u16(bytes, payload + 4)?;
2461 Mutation::StorageCall {
2462 request: read_u32(bytes, payload)?,
2463 op: crate::schema::StorageOp::try_from(raw_op)
2464 .map_err(|()| ProtocolError::InvalidValueKind(raw_op))?,
2465 store: read_string(bytes, payload + 8, records_len)?,
2466 key: read_string(bytes, payload + 16, records_len)?,
2467 data: read_bytes(bytes, payload + 24, records_len)?,
2468 }
2469 }
2470 TAG_FILE_CALL if len == 4 + FILE_CALL_PAYLOAD_LEN => {
2471 let raw_op = read_u16(bytes, payload + 4)?;
2472 let raw_create = read_u16(bytes, payload + 6)?;
2473 if raw_create > 1 {
2474 return Err(ProtocolError::InvalidValueKind(raw_create));
2475 }
2476 Mutation::FileCall {
2477 request: read_u32(bytes, payload)?,
2478 op: FileOp::try_from(raw_op)
2479 .map_err(|()| ProtocolError::InvalidValueKind(raw_op))?,
2480 create: raw_create == 1,
2481 handle: read_u32(bytes, payload + 8)?,
2482 name: read_string(bytes, payload + 12, records_len)?,
2483 data: read_bytes(bytes, payload + 20, records_len)?,
2484 }
2485 }
2486 TAG_RELEASE_FILE_HANDLE if len == 8 => Mutation::ReleaseFileHandle {
2487 handle: read_u32(bytes, payload)?,
2488 },
2489 TAG_SCROLL_TO if len == SCROLL_TO_LEN => Mutation::ScrollTo {
2490 node_id: read_u32(bytes, payload)?,
2491 item: read_u32(bytes, payload + 4)?,
2492 flags: read_u32(bytes, payload + 8)?,
2493 x_px: f64::from_bits(read_u64(bytes, payload + 12)?),
2494 y_px: f64::from_bits(read_u64(bytes, payload + 20)?),
2495 },
2496 TAG_SCROLL_INTO_VIEW if len == SCROLL_INTO_VIEW_LEN => Mutation::ScrollIntoView {
2497 node_id: read_u32(bytes, payload)?,
2498 target: read_u32(bytes, payload + 4)?,
2499 target_kind: read_u16(bytes, payload + 8)?,
2500 align_x: read_u16(bytes, payload + 10)?,
2501 align_y: read_u16(bytes, payload + 12)?,
2502 flags: read_u16(bytes, payload + 14)?,
2503 rect: crate::schema::Rect {
2504 x_px: f64::from_bits(read_u64(bytes, payload + 16)?),
2505 y_px: f64::from_bits(read_u64(bytes, payload + 24)?),
2506 width_px: f64::from_bits(read_u64(bytes, payload + 32)?),
2507 height_px: f64::from_bits(read_u64(bytes, payload + 40)?),
2508 },
2509 },
2510 TAG_SET_WINDOW if len == 32 => {
2511 let chrome = read_u16(bytes, payload)?;
2512 let title_bar = read_u16(bytes, payload + 2)?;
2513 let flags = read_u16(bytes, payload + 12)?;
2517 if flags & 0x00FC != 0 {
2518 return Err(ProtocolError::InvalidValueKind(flags));
2519 }
2520 let resizable = flags & 1;
2521 let report_size = flags & 0x0002 != 0;
2522 let mode_tag = (flags >> WINDOW_MODE_SHIFT) & 0xF;
2523 let policy_tag = flags >> WINDOW_POLICY_SHIFT;
2524 let zoom = read_u16(bytes, payload + 14)?;
2525 let zoom_level = match zoom & 0xFF {
2526 0 => None,
2527 1 => {
2528 let level = (zoom >> 8) as u8 as i8;
2529 if !(crate::zoom::MIN_ZOOM_LEVEL..=crate::zoom::MAX_ZOOM_LEVEL)
2530 .contains(&level)
2531 {
2532 return Err(ProtocolError::InvalidValueKind(zoom));
2533 }
2534 Some(level)
2535 }
2536 _ => return Err(ProtocolError::InvalidValueKind(zoom)),
2537 };
2538 Mutation::SetWindow(crate::schema::Window {
2539 chrome: crate::schema::Chrome::try_from(chrome)
2540 .map_err(|()| ProtocolError::InvalidValueKind(chrome))?,
2541 title_bar: crate::schema::TitleBar::try_from(title_bar)
2542 .map_err(|()| ProtocolError::InvalidValueKind(title_bar))?,
2543 width: read_u16(bytes, payload + 4)?,
2544 height: read_u16(bytes, payload + 6)?,
2545 min_width: read_u16(bytes, payload + 8)?,
2546 min_height: read_u16(bytes, payload + 10)?,
2547 resizable: resizable == 1,
2548 zoom_level,
2549 report_size,
2550 mode: crate::schema::WindowMode::try_from(mode_tag)
2551 .map_err(|()| ProtocolError::InvalidValueKind(flags))?,
2552 last_window_closed: crate::schema::LastWindowClosed::try_from(policy_tag)
2553 .map_err(|()| ProtocolError::InvalidValueKind(flags))?,
2554 icon: read_u32(bytes, payload + 24)?,
2558 title: Box::leak(
2559 read_string(bytes, payload + 16, records_len)?
2560 .to_owned()
2561 .into_boxed_str(),
2562 ),
2563 })
2564 }
2565 TAG_REGISTER_ASSET if len == 20 => {
2566 let raw_kind = read_u16(bytes, payload + 4)?;
2567 Mutation::RegisterAsset {
2568 asset_id: read_u32(bytes, payload)?,
2569 kind: AssetKind::try_from(raw_kind)
2570 .map_err(|()| ProtocolError::InvalidAssetKind(raw_kind))?,
2571 bytes: read_bytes(bytes, payload + 8, records_len)?,
2572 }
2573 }
2574 TAG_RELEASE_ASSET if len == 8 => Mutation::ReleaseAsset {
2575 asset_id: read_u32(bytes, payload)?,
2576 },
2577 TAG_SHOW_MESSAGE if len == 32 => {
2578 let raw_duration = read_u16(bytes, payload + 24)?;
2579 Mutation::ShowMessage {
2580 handler_id: read_u64(bytes, payload)?,
2581 text: read_string(bytes, payload + 8, records_len)?,
2582 action: read_string(bytes, payload + 16, records_len)?,
2583 duration: MessageDuration::try_from(raw_duration)
2584 .map_err(|()| ProtocolError::InvalidMessageDuration(raw_duration))?,
2585 }
2586 }
2587 TAG_EDIT_CODE if len == 40 => Mutation::EditCode {
2588 node_id: read_u32(bytes, payload)?,
2589 request_id: read_u32(bytes, payload + 4)?,
2590 base_version: read_u32(bytes, payload + 8)?,
2591 start_line: read_u32(bytes, payload + 12)?,
2592 start_column: read_u32(bytes, payload + 16)?,
2593 end_line: read_u32(bytes, payload + 20)?,
2594 end_column: read_u32(bytes, payload + 24)?,
2595 text: read_string(bytes, payload + 28, records_len)?,
2596 },
2597 TAG_POST_NOTIFICATION if len == 56 => {
2598 let raw_importance = read_u16(bytes, payload + 48)?;
2599 let raw_presentation = read_u16(bytes, payload + 50)?;
2600 Mutation::PostNotification {
2601 key: read_string(bytes, payload, records_len)?,
2602 title: read_string(bytes, payload + 8, records_len)?,
2603 body: read_string(bytes, payload + 16, records_len)?,
2604 channel: read_string(bytes, payload + 24, records_len)?,
2605 action_1: read_string(bytes, payload + 32, records_len)?,
2606 action_2: read_string(bytes, payload + 40, records_len)?,
2607 importance: NotificationImportance::try_from(raw_importance)
2608 .map_err(|()| ProtocolError::InvalidNotification(raw_importance))?,
2609 presentation: NotificationPresentation::try_from(raw_presentation)
2610 .map_err(|()| ProtocolError::InvalidNotification(raw_presentation))?,
2611 }
2612 }
2613 TAG_WITHDRAW_NOTIFICATION if len == 12 => Mutation::WithdrawNotification {
2614 key: read_string(bytes, payload, records_len)?,
2615 },
2616 TAG_REQUEST_NOTIFICATION_PERMISSION if len == 4 => {
2617 Mutation::RequestNotificationPermission
2618 }
2619 TAG_START_ANIMATION if len >= ANIMATION_HEADER_LEN => {
2620 Mutation::StartAnimation(
2623 decode_animation(&bytes[position..position + len]).map_err(|error| {
2624 if error == ProtocolError::Truncated {
2625 ProtocolError::InvalidAnimation
2626 } else {
2627 error
2628 }
2629 })?,
2630 )
2631 }
2632 TAG_CONTROL_ANIMATION if len == 24 => {
2633 let raw_property = u16::from(read_u8(bytes, payload + 8)?);
2634 let raw_op = u16::from(read_u8(bytes, payload + 10)?);
2635 Mutation::ControlAnimation {
2636 node_id: read_u32(bytes, payload)?,
2637 animation_id: read_u32(bytes, payload + 4)?,
2638 property: crate::schema::AnimatedProperty::try_from(raw_property)
2639 .map_err(|()| ProtocolError::InvalidAnimation)?,
2640 slot: read_u8(bytes, payload + 9)?,
2641 op: crate::schema::AnimationControl::try_from(raw_op)
2642 .map_err(|()| ProtocolError::InvalidAnimation)?,
2643 at_time_nanos: read_u64(bytes, payload + 12)?,
2644 }
2645 }
2646 TAG_SET_SELECTION if len == 28 => Mutation::SetSelection {
2647 node_id: read_u32(bytes, payload)?,
2648 base_version: read_u32(bytes, payload + 4)?,
2649 anchor_line: read_u32(bytes, payload + 8)?,
2650 anchor_column: read_u32(bytes, payload + 12)?,
2651 active_line: read_u32(bytes, payload + 16)?,
2652 active_column: read_u32(bytes, payload + 20)?,
2653 },
2654 TAG_REVEAL_RANGE if len == 32 => {
2655 let raw_align = read_u32(bytes, payload + 24)?;
2656 Mutation::RevealRange {
2657 node_id: read_u32(bytes, payload)?,
2658 base_version: read_u32(bytes, payload + 4)?,
2659 start_line: read_u32(bytes, payload + 8)?,
2660 start_column: read_u32(bytes, payload + 12)?,
2661 end_line: read_u32(bytes, payload + 16)?,
2662 end_column: read_u32(bytes, payload + 20)?,
2663 align: u16::try_from(raw_align)
2664 .ok()
2665 .and_then(|tag| crate::schema::RevealAlign::try_from(tag).ok())
2666 .ok_or(ProtocolError::InvalidValueKind(
2667 raw_align.min(u32::from(u16::MAX)) as u16,
2668 ))?,
2669 }
2670 }
2671 TAG_SET_FOLDED if len == 16 => Mutation::SetFolded {
2672 node_id: read_u32(bytes, payload)?,
2673 start_line: read_u32(bytes, payload + 4)?,
2674 collapsed: read_flag(bytes, payload + 8)?,
2675 },
2676 TAG_SET_SCROLL_OFFSET if len == 16 => Mutation::SetScrollOffset {
2677 node_id: read_u32(bytes, payload)?,
2678 offset_px: f64::from_bits(read_u64(bytes, payload + 4)?),
2679 },
2680 TAG_SET_SCROLL_OFFSET => return Err(ProtocolError::InvalidRecordLength),
2681 TAG_CREATE..=TAG_EDIT_CODE
2682 | TAG_START_ANIMATION
2683 | TAG_CONTROL_ANIMATION
2684 | TAG_SET_SELECTION..=TAG_SET_FOLDED
2685 | TAG_SET_WINDOW_THEME..=TAG_SET_CLIPBOARD_TEXT
2686 | TAG_READ_CLIPBOARD
2687 | TAG_STORAGE_CALL
2688 | TAG_FILE_CALL
2689 | TAG_RELEASE_FILE_HANDLE
2690 | TAG_SCROLL_TO
2691 | TAG_SCROLL_INTO_VIEW => {
2692 return Err(ProtocolError::InvalidRecordLength);
2693 }
2694 other => return Err(ProtocolError::InvalidTag(other)),
2695 };
2696 output.push(mutation);
2697 position += len;
2698 }
2699 if output.len() != count {
2700 return Err(ProtocolError::InvalidEnvelope);
2701 }
2702 Ok(output)
2703}
2704
2705fn decode_animation(record: &[u8]) -> Result<crate::schema::Animation<'static>, ProtocolError> {
2709 use crate::schema::{
2710 AnimatedProperty, Animation, AnimationEvents, ColorInterpolation, FillMode, Keyframe,
2711 KeyframeValue, PlayState, PlaybackDirection, StepPosition, Timing, TransformFunction,
2712 TransformFunctionKind,
2713 };
2714 let bad = ProtocolError::InvalidAnimation;
2715 fn byte_enum<T: TryFrom<u16, Error = ()>>(
2716 record: &[u8],
2717 at: usize,
2718 ) -> Result<T, ProtocolError> {
2719 T::try_from(u16::from(read_u8(record, at)?)).map_err(|()| ProtocolError::InvalidAnimation)
2720 }
2721 let float = |at: usize| read_u32(record, at).map(f32::from_bits);
2722 let property: AnimatedProperty = byte_enum(record, 12)?;
2723 let direction: PlaybackDirection = byte_enum(record, 14)?;
2724 let fill: FillMode = byte_enum(record, 15)?;
2725 let play_state: PlayState = byte_enum(record, 16)?;
2726 let colour = matches!(
2727 property,
2728 AnimatedProperty::Color | AnimatedProperty::Background | AnimatedProperty::BorderColor
2729 );
2730 let interpolation = match (colour, read_u8(record, 17)?) {
2731 (false, 0) => None,
2732 (true, raw) => Some(
2733 ColorInterpolation::try_from(u16::from(raw))
2734 .map_err(|()| ProtocolError::InvalidAnimation)?,
2735 ),
2736 (false, _) => return Err(bad),
2737 };
2738 let keyframe_count = usize::from(read_u16(record, 18)?);
2739 let delay_ms = float(28)?;
2740 let duration_ms = float(32)?;
2741 let iterations = float(36)?;
2742 let origin_x = float(40)?;
2743 let origin_y = float(44)?;
2744 let events = read_u8(record, 48)?;
2745 if keyframe_count < 2
2746 || !delay_ms.is_finite()
2747 || !(duration_ms.is_finite() && duration_ms >= 0.0)
2748 || iterations.is_nan()
2749 || iterations < 0.0
2750 || !(origin_x.is_finite() && origin_y.is_finite())
2751 || events & !AnimationEvents::ALL.0 != 0
2752 {
2753 return Err(bad);
2754 }
2755 let mut keyframes = Vec::with_capacity(keyframe_count);
2756 let mut at = ANIMATION_HEADER_LEN;
2757 for index in 0..keyframe_count {
2758 let offset = float(at)?;
2759 let raw_timing = read_u8(record, at + 4)?;
2760 let raw_position = read_u8(record, at + 5)?;
2761 let flags = read_u16(record, at + 6)?;
2762 let timing = match raw_timing {
2763 1 => Timing::Linear,
2764 2 => {
2765 let (x1, y1, x2, y2) = (
2766 float(at + 8)?,
2767 float(at + 12)?,
2768 float(at + 16)?,
2769 float(at + 20)?,
2770 );
2771 if ![x1, y1, x2, y2].iter().all(|value| value.is_finite())
2772 || !(0.0..=1.0).contains(&x1)
2773 || !(0.0..=1.0).contains(&x2)
2774 {
2775 return Err(bad);
2776 }
2777 Timing::CubicBezier { x1, y1, x2, y2 }
2778 }
2779 3 => {
2780 let count = read_u32(record, at + 8)?;
2781 let position =
2782 StepPosition::try_from(u16::from(raw_position)).map_err(|()| bad.clone())?;
2783 let least = if position == StepPosition::JumpNone {
2784 2
2785 } else {
2786 1
2787 };
2788 if count < least {
2789 return Err(bad);
2790 }
2791 Timing::Steps { count, position }
2792 }
2793 _ => return Err(bad),
2794 };
2795 if flags & !KEYFRAME_FROM_PRESENTED != 0
2796 || (index > 0 && flags & KEYFRAME_FROM_PRESENTED != 0)
2797 || !(0.0..=1.0).contains(&offset)
2798 {
2799 return Err(bad);
2800 }
2801 let value_at = at + KEYFRAME_HEAD_LEN;
2802 let (value, value_len) = match property {
2803 AnimatedProperty::Alpha => {
2804 let value = float(value_at)?;
2805 if !value.is_finite() {
2806 return Err(bad);
2807 }
2808 (KeyframeValue::Alpha(value), 4)
2809 }
2810 AnimatedProperty::Color
2811 | AnimatedProperty::Background
2812 | AnimatedProperty::BorderColor => {
2813 let paint = Paint::from_bits(read_u64(record, value_at)?)
2815 .filter(|paint| !matches!(paint, Paint::Asset(_)))
2816 .ok_or(bad.clone())?;
2817 (KeyframeValue::Paint(paint), 8)
2818 }
2819 AnimatedProperty::Transform => {
2820 let count = usize::from(read_u16(record, value_at)?);
2821 let mut functions = Vec::with_capacity(count);
2822 for function in 0..count {
2823 let function_at = value_at + 4 + TRANSFORM_FUNCTION_LEN * function;
2824 let raw_kind = read_u32(record, function_at)?;
2825 let kind = u16::try_from(raw_kind)
2826 .ok()
2827 .and_then(|kind| TransformFunctionKind::try_from(kind).ok())
2828 .ok_or(bad.clone())?;
2829 let mut values = [0.0_f32; 6];
2830 for (slot, value) in values.iter_mut().enumerate() {
2831 *value = float(function_at + 4 + 4 * slot)?;
2832 }
2833 if !values.iter().all(|value| value.is_finite()) {
2834 return Err(bad);
2835 }
2836 functions.push(TransformFunction { kind, values });
2837 }
2838 (
2839 KeyframeValue::Transform(std::borrow::Cow::Owned(functions)),
2840 4 + TRANSFORM_FUNCTION_LEN * count,
2841 )
2842 }
2843 };
2844 keyframes.push(Keyframe {
2845 offset,
2846 timing,
2847 from_presented: flags & KEYFRAME_FROM_PRESENTED != 0,
2848 value,
2849 });
2850 at = value_at + value_len;
2851 }
2852 if at != record.len() {
2853 return Err(bad);
2854 }
2855 let first = keyframes.first().map(|keyframe| keyframe.offset);
2859 let last = keyframes.last().map(|keyframe| keyframe.offset);
2860 if first != Some(0.0)
2861 || last != Some(1.0)
2862 || keyframes
2863 .windows(2)
2864 .any(|pair| pair[1].offset < pair[0].offset)
2865 {
2866 return Err(bad);
2867 }
2868 if let Some(KeyframeValue::Transform(shape)) = keyframes.first().map(|keyframe| &keyframe.value)
2869 {
2870 let kinds: Vec<_> = shape.iter().map(|function| function.kind).collect();
2871 let lined_up = keyframes.iter().all(|keyframe| match &keyframe.value {
2872 KeyframeValue::Transform(functions) => functions
2873 .iter()
2874 .map(|function| function.kind)
2875 .eq(kinds.iter().copied()),
2876 _ => false,
2877 });
2878 if !lined_up {
2879 return Err(bad);
2880 }
2881 }
2882 Ok(Animation {
2883 node_id: read_u32(record, 4)?,
2884 animation_id: read_u32(record, 8)?,
2885 property,
2886 slot: read_u8(record, 13)?,
2887 direction,
2888 fill,
2889 play_state,
2890 interpolation,
2891 start_time_nanos: read_u64(record, 20)?,
2892 delay_ms,
2893 duration_ms,
2894 iterations,
2895 origin_x,
2896 origin_y,
2897 events: AnimationEvents(events),
2898 keyframes: std::borrow::Cow::Owned(keyframes),
2899 })
2900}
2901
2902const fn pack_floats(low: f32, high: f32) -> u64 {
2904 (low.to_bits() as u64) | ((high.to_bits() as u64) << 32)
2905}
2906
2907const fn unpack_low(word: u64) -> f32 {
2908 f32::from_bits(word as u32)
2909}
2910
2911const fn unpack_high(word: u64) -> f32 {
2912 f32::from_bits((word >> 32) as u32)
2913}
2914
2915const fn two_words(first: u64, second: u64) -> [u64; MAX_MODIFIER_WORDS] {
2918 [first, second, 0, 0, 0, 0, 0, 0, 0, 0]
2919}
2920
2921fn float_word(value: f32) -> u64 {
2922 u64::from(value.to_bits())
2923}
2924
2925fn modifier_fields(modifier: &Modifier) -> (u16, [u64; MAX_MODIFIER_WORDS]) {
2928 match modifier {
2929 Modifier::Empty => (0, two_words(0, 0)),
2930 Modifier::Padding(value) => (1, two_words(float_word(*value), 0)),
2931 Modifier::FillMaxWidth => (2, two_words(0, 0)),
2932 Modifier::MatchParentSize => (31, two_words(0, 0)),
2933 Modifier::FillMaxHeight => (3, two_words(0, 0)),
2934 Modifier::Width(value) => (4, two_words(float_word(*value), 0)),
2935 Modifier::Height(value) => (5, two_words(float_word(*value), 0)),
2936 Modifier::Size { width, height } => (6, two_words(float_word(*width), float_word(*height))),
2937 Modifier::Background(paint) => (7, two_words(paint.to_bits(), 0)),
2938 Modifier::Clickable { handler_id } => (8, two_words(*handler_id, 0)),
2939 Modifier::PaddingRole(role) => (9, two_words(*role as u64, 0)),
2940 Modifier::PaddingEach {
2941 start,
2942 top,
2943 end,
2944 bottom,
2945 } => (
2946 10,
2947 two_words(pack_floats(*start, *top), pack_floats(*end, *bottom)),
2948 ),
2949 Modifier::Weight(value) => (11, two_words(float_word(*value), 0)),
2950 Modifier::Shape {
2951 top_start,
2952 top_end,
2953 bottom_end,
2954 bottom_start,
2955 } => (
2956 12,
2957 two_words(
2958 pack_floats(*top_start, *top_end),
2959 pack_floats(*bottom_end, *bottom_start),
2960 ),
2961 ),
2962 Modifier::ShapeRole(role) => (13, two_words(*role as u64, 0)),
2963 Modifier::Border { width, paint } => (14, two_words(float_word(*width), paint.to_bits())),
2964 Modifier::Elevation(dp) => (15, two_words(float_word(*dp), 0)),
2965 Modifier::ObserveSize { token, every_dp } => {
2966 (16, two_words(u64::from(*token), u64::from(*every_dp)))
2967 }
2968 Modifier::Motion(role) => (17, two_words(*role as u64, 0)),
2969 Modifier::Material(role) => (18, two_words(*role as u64, 0)),
2970 Modifier::Offset { x, y } => (19, two_words(float_word(*x), float_word(*y))),
2971 Modifier::RequiredSize { width, height } => {
2972 (20, two_words(float_word(*width), float_word(*height)))
2973 }
2974 Modifier::BorderEach {
2975 top,
2976 right,
2977 bottom,
2978 left,
2979 top_paint,
2980 right_paint,
2981 bottom_paint,
2982 left_paint,
2983 } => (
2984 21,
2985 [
2986 pack_floats(*top, *right),
2987 pack_floats(*bottom, *left),
2988 top_paint.to_bits(),
2989 right_paint.to_bits(),
2990 bottom_paint.to_bits(),
2991 left_paint.to_bits(),
2992 0,
2993 0,
2994 0,
2995 0,
2996 ],
2997 ),
2998 Modifier::CornerEach {
2999 top_left,
3000 top_right,
3001 bottom_right,
3002 bottom_left,
3003 } => (
3004 22,
3005 two_words(
3006 pack_floats(*top_left, *top_right),
3007 pack_floats(*bottom_right, *bottom_left),
3008 ),
3009 ),
3010 Modifier::Shadow {
3011 x,
3012 y,
3013 blur,
3014 spread,
3015 paint,
3016 } => (
3017 23,
3018 [
3019 pack_floats(*x, *y),
3020 pack_floats(*blur, *spread),
3021 paint.to_bits(),
3022 0,
3023 0,
3024 0,
3025 0,
3026 0,
3027 0,
3028 0,
3029 ],
3030 ),
3031 Modifier::WindowDrag(enabled) => (32, two_words(u64::from(*enabled), 0)),
3032 Modifier::Clip(enabled) => (24, two_words(u64::from(*enabled), 0)),
3033 Modifier::Alpha(value) => (25, two_words(float_word(*value), 0)),
3034 Modifier::PressFeedback { scale, tint_alpha } => {
3035 (33, two_words(pack_floats(*scale, *tint_alpha), 0))
3036 }
3037 Modifier::Semantics { role, state } => (34, two_words(*role as u64, u64::from(*state))),
3038 Modifier::CornerCurve { smoothing } => (35, two_words(float_word(*smoothing), 0)),
3039 Modifier::Transform {
3040 a,
3041 b,
3042 c,
3043 d,
3044 e,
3045 f,
3046 origin_x,
3047 origin_y,
3048 } => (
3049 26,
3050 [
3051 pack_floats(*a, *b),
3052 pack_floats(*c, *d),
3053 pack_floats(*e, *f),
3054 pack_floats(*origin_x, *origin_y),
3055 0,
3056 0,
3057 0,
3058 0,
3059 0,
3060 0,
3061 ],
3062 ),
3063 Modifier::Transform3D {
3064 m,
3065 origin_x,
3066 origin_y,
3067 backface_hidden,
3068 } => (
3069 27,
3070 [
3071 pack_floats(m[0], m[1]),
3072 pack_floats(m[2], m[3]),
3073 pack_floats(m[4], m[5]),
3074 pack_floats(m[6], m[7]),
3075 pack_floats(m[8], m[9]),
3076 pack_floats(m[10], m[11]),
3077 pack_floats(m[12], m[13]),
3078 pack_floats(m[14], m[15]),
3079 pack_floats(*origin_x, *origin_y),
3080 u64::from(*backface_hidden),
3081 ],
3082 ),
3083 }
3084}
3085
3086fn decode_role<T: TryFrom<u16, Error = ()>>(first: u64) -> Result<T, ProtocolError> {
3087 let tag = u16::try_from(first).map_err(|_| ProtocolError::InvalidModifier(0))?;
3088 T::try_from(tag).map_err(|()| ProtocolError::InvalidModifier(tag))
3089}
3090
3091fn decode_paint(bits: u64) -> Result<Paint, ProtocolError> {
3092 Paint::from_bits(bits).ok_or(ProtocolError::InvalidModifier(0))
3093}
3094
3095fn decode_modifier(tag: u16, words: &[u64; MAX_MODIFIER_WORDS]) -> Result<Modifier, ProtocolError> {
3096 let [
3097 first,
3098 second,
3099 third,
3100 fourth,
3101 fifth,
3102 sixth,
3103 seventh,
3104 eighth,
3105 ninth,
3106 tenth,
3107 ] = *words;
3108 match tag {
3109 0 => Ok(Modifier::Empty),
3110 1 => Ok(Modifier::Padding(f32::from_bits(first as u32))),
3111 2 => Ok(Modifier::FillMaxWidth),
3112 3 => Ok(Modifier::FillMaxHeight),
3113 4 => Ok(Modifier::Width(f32::from_bits(first as u32))),
3114 5 => Ok(Modifier::Height(f32::from_bits(first as u32))),
3115 6 => Ok(Modifier::Size {
3116 width: f32::from_bits(first as u32),
3117 height: f32::from_bits(second as u32),
3118 }),
3119 7 => Ok(Modifier::Background(decode_paint(first)?)),
3120 8 => Ok(Modifier::Clickable { handler_id: first }),
3121 9 => Ok(Modifier::PaddingRole(decode_role::<SpaceRole>(first)?)),
3122 10 => Ok(Modifier::PaddingEach {
3123 start: unpack_low(first),
3124 top: unpack_high(first),
3125 end: unpack_low(second),
3126 bottom: unpack_high(second),
3127 }),
3128 11 => Ok(Modifier::Weight(f32::from_bits(first as u32))),
3129 12 => Ok(Modifier::Shape {
3130 top_start: unpack_low(first),
3131 top_end: unpack_high(first),
3132 bottom_end: unpack_low(second),
3133 bottom_start: unpack_high(second),
3134 }),
3135 13 => Ok(Modifier::ShapeRole(decode_role::<ShapeRole>(first)?)),
3136 14 => Ok(Modifier::Border {
3137 width: f32::from_bits(first as u32),
3138 paint: decode_paint(second)?,
3139 }),
3140 15 => Ok(Modifier::Elevation(f32::from_bits(first as u32))),
3141 16 => Ok(Modifier::ObserveSize {
3142 token: first as u32,
3143 every_dp: match second {
3144 0 => false,
3145 1 => true,
3146 _ => return Err(ProtocolError::InvalidModifier(16)),
3147 },
3148 }),
3149 17 => Ok(Modifier::Motion(decode_role::<MotionRole>(first)?)),
3150 18 => Ok(Modifier::Material(decode_role::<MaterialRole>(first)?)),
3151 19 => Ok(Modifier::Offset {
3152 x: f32::from_bits(first as u32),
3153 y: f32::from_bits(second as u32),
3154 }),
3155 20 => Ok(Modifier::RequiredSize {
3156 width: f32::from_bits(first as u32),
3157 height: f32::from_bits(second as u32),
3158 }),
3159 21 => Ok(Modifier::BorderEach {
3160 top: unpack_low(first),
3161 right: unpack_high(first),
3162 bottom: unpack_low(second),
3163 left: unpack_high(second),
3164 top_paint: decode_paint(third)?,
3165 right_paint: decode_paint(fourth)?,
3166 bottom_paint: decode_paint(fifth)?,
3167 left_paint: decode_paint(sixth)?,
3168 }),
3169 22 => Ok(Modifier::CornerEach {
3170 top_left: unpack_low(first),
3171 top_right: unpack_high(first),
3172 bottom_right: unpack_low(second),
3173 bottom_left: unpack_high(second),
3174 }),
3175 23 => Ok(Modifier::Shadow {
3176 x: unpack_low(first),
3177 y: unpack_high(first),
3178 blur: unpack_low(second),
3179 spread: unpack_high(second),
3180 paint: decode_paint(third)?,
3181 }),
3182 24 => match first {
3185 0 => Ok(Modifier::Clip(false)),
3186 1 => Ok(Modifier::Clip(true)),
3187 _ => Err(ProtocolError::InvalidModifier(24)),
3188 },
3189 32 => match first {
3190 0 => Ok(Modifier::WindowDrag(false)),
3191 1 => Ok(Modifier::WindowDrag(true)),
3192 _ => Err(ProtocolError::InvalidModifier(32)),
3193 },
3194 25 => Ok(Modifier::Alpha(f32::from_bits(first as u32))),
3195 26 => {
3196 let values = [
3197 unpack_low(first),
3198 unpack_high(first),
3199 unpack_low(second),
3200 unpack_high(second),
3201 unpack_low(third),
3202 unpack_high(third),
3203 unpack_low(fourth),
3204 unpack_high(fourth),
3205 ];
3206 if values.iter().any(|value| !value.is_finite()) {
3210 return Err(ProtocolError::InvalidModifier(26));
3211 }
3212 let [a, b, c, d, e, f, origin_x, origin_y] = values;
3213 Ok(Modifier::Transform {
3214 a,
3215 b,
3216 c,
3217 d,
3218 e,
3219 f,
3220 origin_x,
3221 origin_y,
3222 })
3223 }
3224 27 => {
3225 let values = [
3226 unpack_low(first),
3227 unpack_high(first),
3228 unpack_low(second),
3229 unpack_high(second),
3230 unpack_low(third),
3231 unpack_high(third),
3232 unpack_low(fourth),
3233 unpack_high(fourth),
3234 unpack_low(fifth),
3235 unpack_high(fifth),
3236 unpack_low(sixth),
3237 unpack_high(sixth),
3238 unpack_low(seventh),
3239 unpack_high(seventh),
3240 unpack_low(eighth),
3241 unpack_high(eighth),
3242 unpack_low(ninth),
3243 unpack_high(ninth),
3244 ];
3245 if values.iter().any(|value| !value.is_finite()) {
3248 return Err(ProtocolError::InvalidModifier(27));
3249 }
3250 let backface_hidden = match tenth {
3251 0 => false,
3252 1 => true,
3253 _ => return Err(ProtocolError::InvalidModifier(27)),
3254 };
3255 let mut m = [0.0; 16];
3256 m.copy_from_slice(&values[..16]);
3257 Ok(Modifier::Transform3D {
3258 m,
3259 origin_x: values[16],
3260 origin_y: values[17],
3261 backface_hidden,
3262 })
3263 }
3264 31 => Ok(Modifier::MatchParentSize),
3265 33 => Ok(Modifier::PressFeedback {
3266 scale: unpack_low(first),
3267 tint_alpha: unpack_high(first),
3268 }),
3269 34 => {
3270 let state = u32::try_from(second)
3271 .ok()
3272 .filter(|state| state & !SEMANTICS_STATE_MASK == 0)
3273 .ok_or(ProtocolError::InvalidModifier(34))?;
3274 Ok(Modifier::Semantics {
3275 role: decode_role::<SemanticsRole>(first)?,
3276 state,
3277 })
3278 }
3279 35 => Ok(Modifier::CornerCurve {
3282 smoothing: unpack_low(first),
3283 }),
3284 other => Err(ProtocolError::InvalidModifier(other)),
3285 }
3286}
3287
3288fn read_string(bytes: &[u8], position: usize, arena_start: usize) -> Result<&str, ProtocolError> {
3295 let value = read_bytes(bytes, position, arena_start)?;
3296 std::str::from_utf8(value).map_err(|_| ProtocolError::InvalidUtf8)
3297}
3298
3299fn read_bytes(bytes: &[u8], position: usize, arena_start: usize) -> Result<&[u8], ProtocolError> {
3301 let offset =
3302 usize::try_from(read_u32(bytes, position)?).map_err(|_| ProtocolError::LengthOverflow)?;
3303 let len = usize::try_from(read_u32(bytes, position + 4)?)
3304 .map_err(|_| ProtocolError::LengthOverflow)?;
3305 let end = offset
3306 .checked_add(len)
3307 .ok_or(ProtocolError::InvalidStringRange)?;
3308 if offset < arena_start {
3309 return Err(ProtocolError::InvalidStringRange);
3310 }
3311 bytes
3312 .get(offset..end)
3313 .ok_or(ProtocolError::InvalidStringRange)
3314}
3315
3316fn read_u8(bytes: &[u8], position: usize) -> Result<u8, ProtocolError> {
3317 bytes.get(position).copied().ok_or(ProtocolError::Truncated)
3318}
3319
3320fn read_u16(bytes: &[u8], position: usize) -> Result<u16, ProtocolError> {
3321 let value = bytes
3322 .get(position..position + 2)
3323 .ok_or(ProtocolError::Truncated)?;
3324 Ok(u16::from_le_bytes([value[0], value[1]]))
3325}
3326
3327fn read_u32(bytes: &[u8], position: usize) -> Result<u32, ProtocolError> {
3328 let value = bytes
3329 .get(position..position + 4)
3330 .ok_or(ProtocolError::Truncated)?;
3331 Ok(u32::from_le_bytes([value[0], value[1], value[2], value[3]]))
3332}
3333
3334fn read_flag(bytes: &[u8], position: usize) -> Result<bool, ProtocolError> {
3336 match read_u32(bytes, position)? {
3337 0 => Ok(false),
3338 1 => Ok(true),
3339 other => Err(ProtocolError::InvalidValueKind(
3340 other.min(u32::from(u16::MAX)) as u16,
3341 )),
3342 }
3343}
3344
3345fn read_u64(bytes: &[u8], position: usize) -> Result<u64, ProtocolError> {
3346 let value = bytes
3347 .get(position..position + 8)
3348 .ok_or(ProtocolError::Truncated)?;
3349 Ok(u64::from_le_bytes(
3350 value.try_into().map_err(|_| ProtocolError::Truncated)?,
3351 ))
3352}
3353
3354#[cfg(test)]
3355mod tests {
3356 use super::*;
3357 use crate::schema::TypeRole;
3358
3359 #[test]
3360 fn round_trips_all_m0_mutations() {
3361 let mutations = [
3362 Mutation::Create {
3363 node_id: 1,
3364 widget: WidgetKind::Column,
3365 },
3366 Mutation::SetProp {
3367 node_id: 2,
3368 property: PropertyKind::Text,
3369 value: PropertyValue::String("안녕"),
3370 },
3371 Mutation::SetProp {
3372 node_id: 3,
3373 property: PropertyKind::Enabled,
3374 value: PropertyValue::Bool(true),
3375 },
3376 Mutation::SetModifier {
3377 node_id: 1,
3378 index: 0,
3379 modifier: Modifier::Padding(16.0),
3380 },
3381 Mutation::Insert {
3382 parent_id: 1,
3383 node_id: 2,
3384 index: 0,
3385 },
3386 Mutation::Move {
3387 parent_id: 1,
3388 node_id: 2,
3389 index: 1,
3390 },
3391 Mutation::Remove { node_id: 3 },
3392 Mutation::SetText {
3393 node_id: 4,
3394 text: "compose",
3395 selection: Some(Selection { start: 1, end: 4 }),
3396 },
3397 Mutation::AppendText {
3398 node_id: 4,
3399 text: " token",
3400 },
3401 ];
3402 let mut encoder = BatchEncoder::default();
3403 for mutation in &mutations {
3404 encoder.encode(mutation).unwrap();
3405 }
3406 let decoded = decode_batch(encoder.finish().unwrap()).unwrap();
3407 assert_eq!(decoded, mutations);
3408 }
3409
3410 #[test]
3413 fn fr13_paint_role_and_literal_share_one_record_length() {
3414 let encode_one = |modifier| {
3415 let mut encoder = BatchEncoder::default();
3416 encoder
3417 .encode(&Mutation::SetModifier {
3418 node_id: 1,
3419 index: 0,
3420 modifier,
3421 })
3422 .unwrap();
3423 encoder.finish().unwrap().to_vec()
3424 };
3425 let role = encode_one(Modifier::Background(Paint::Role(
3426 crate::schema::ColorRole::Surface,
3427 )));
3428 let literal = encode_one(Modifier::Background(Paint::Literal(
3429 crate::schema::Color::rgb(0x1b1b1f),
3430 )));
3431 assert_eq!(role.len(), literal.len());
3432 assert_eq!(
3433 decode_batch(&role).unwrap(),
3434 [Mutation::SetModifier {
3435 node_id: 1,
3436 index: 0,
3437 modifier: Modifier::Background(Paint::Role(crate::schema::ColorRole::Surface)),
3438 }]
3439 );
3440 assert_eq!(
3441 decode_batch(&literal).unwrap(),
3442 [Mutation::SetModifier {
3443 node_id: 1,
3444 index: 0,
3445 modifier: Modifier::Background(Paint::Literal(crate::schema::Color::rgb(0x1b1b1f))),
3446 }]
3447 );
3448 }
3449
3450 #[test]
3451 fn fr68_semantics_round_trips_and_refuses_unknown_state_bits() {
3452 let expected = Modifier::Semantics {
3453 role: SemanticsRole::Switch,
3454 state: (2 << crate::schema::SEMANTICS_CHECKED_SHIFT)
3455 | (1 << crate::schema::SEMANTICS_SELECTED_SHIFT),
3456 };
3457 let mutation = Mutation::SetModifier {
3458 node_id: 1,
3459 index: 0,
3460 modifier: expected.clone(),
3461 };
3462 let mut encoder = BatchEncoder::default();
3463 encoder.encode(&mutation).unwrap();
3464 assert_eq!(decode_batch(encoder.finish().unwrap()).unwrap(), [mutation]);
3465
3466 let words = [SemanticsRole::Button as u64, 0x2000, 0, 0, 0, 0, 0, 0, 0, 0];
3467 assert_eq!(
3468 decode_modifier(34, &words),
3469 Err(ProtocolError::InvalidModifier(34))
3470 );
3471 }
3472
3473 #[test]
3474 fn fr71_every_new_role_round_trips_and_is_exposed() {
3475 use crate::schema::*;
3476 let roles = [
3477 (SemanticsRole::TabList, 9),
3478 (SemanticsRole::Tree, 10),
3479 (SemanticsRole::TreeItem, 11),
3480 (SemanticsRole::Menu, 12),
3481 (SemanticsRole::MenuItem, 13),
3482 (SemanticsRole::TextBox, 14),
3483 (SemanticsRole::Heading, 15),
3484 (SemanticsRole::Navigation, 16),
3485 (SemanticsRole::List, 17),
3486 (SemanticsRole::ListItem, 18),
3487 (SemanticsRole::Group, 19),
3488 ];
3489 let state = (2 << SEMANTICS_EXPANDED_SHIFT)
3490 | (2 << SEMANTICS_DISABLED_SHIFT)
3491 | (1 << SEMANTICS_FOCUSED_SHIFT)
3492 | SEMANTICS_HIDDEN_BIT
3493 | SEMANTICS_CLICK_ACTION_BIT
3494 | SEMANTICS_FOCUS_ACTION_BIT;
3495 assert_eq!(state & !SEMANTICS_STATE_MASK, 0);
3496 for (role, value) in roles {
3497 assert_eq!(role as u64, value, "{role:?} changed its wire value");
3498 let mutation = Mutation::SetModifier {
3499 node_id: 1,
3500 index: 0,
3501 modifier: Modifier::Semantics { role, state },
3502 };
3503 let mut encoder = BatchEncoder::default();
3504 encoder.encode(&mutation).unwrap();
3505 assert_eq!(decode_batch(encoder.finish().unwrap()).unwrap(), [mutation]);
3506 }
3507 assert!(
3508 ROLE_ENUM_SCHEMA
3509 .iter()
3510 .any(|e| e.name == "SemanticsRole" && e.variants.len() == 19),
3511 "the schema must list all 19 roles"
3512 );
3513 }
3514
3515 #[test]
3516 fn nfr7_unknown_semantics_bit_is_a_protocol_error() {
3517 let words = [SemanticsRole::Group as u64, 1 << 13, 0, 0, 0, 0, 0, 0, 0, 0];
3519 assert_eq!(
3520 decode_modifier(34, &words),
3521 Err(ProtocolError::InvalidModifier(34))
3522 );
3523 let words = [SemanticsRole::Group as u64, 1 << 12, 0, 0, 0, 0, 0, 0, 0, 0];
3525 assert!(decode_modifier(34, &words).is_ok());
3526 }
3527
3528 #[test]
3529 fn fr51_press_feedback_round_trips() {
3530 let expected = [Mutation::SetModifier {
3531 node_id: 1,
3532 index: 0,
3533 modifier: Modifier::PressFeedback {
3534 scale: 0.975,
3535 tint_alpha: 0.07,
3536 },
3537 }];
3538 let mut encoder = BatchEncoder::default();
3539 encoder.encode(&expected[0]).unwrap();
3540 assert_eq!(decode_batch(encoder.finish().unwrap()).unwrap(), expected);
3541 }
3542
3543 #[test]
3544 fn fr50_corner_curve_round_trips() {
3545 for smoothing in [0.0, 0.6, 1.0, -2.5, 7.0] {
3546 let expected = [Mutation::SetModifier {
3547 node_id: 1,
3548 index: 0,
3549 modifier: Modifier::CornerCurve { smoothing },
3550 }];
3551 let mut encoder = BatchEncoder::default();
3552 encoder.encode(&expected[0]).unwrap();
3553 let bytes = encoder.finish().unwrap().to_vec();
3554 assert_eq!(u16::from_le_bytes([bytes[22], bytes[23]]), 35);
3555 assert_eq!(decode_batch(&bytes).unwrap(), expected);
3556 }
3557 }
3558
3559 #[test]
3561 fn fr13_design_modifiers_round_trip_in_two_words() {
3562 let modifiers = [
3563 Modifier::PaddingRole(SpaceRole::Md),
3564 Modifier::PaddingEach {
3565 start: 1.0,
3566 top: 2.0,
3567 end: 3.0,
3568 bottom: 4.0,
3569 },
3570 Modifier::Weight(0.25),
3571 Modifier::Shape {
3572 top_start: 4.0,
3573 top_end: 8.0,
3574 bottom_end: 12.0,
3575 bottom_start: 16.0,
3576 },
3577 Modifier::ShapeRole(ShapeRole::Full),
3578 Modifier::Border {
3579 width: 2.0,
3580 paint: Paint::Role(crate::schema::ColorRole::Outline),
3581 },
3582 Modifier::Elevation(6.0),
3583 ];
3584 let mut encoder = BatchEncoder::default();
3585 let expected: Vec<_> = modifiers
3586 .iter()
3587 .enumerate()
3588 .map(|(index, modifier)| Mutation::SetModifier {
3589 node_id: 1,
3590 index: index as u16,
3591 modifier: modifier.clone(),
3592 })
3593 .collect();
3594 for mutation in &expected {
3595 encoder.encode(mutation).unwrap();
3596 }
3597 let bytes = encoder.finish().unwrap();
3598 assert_eq!(bytes.len(), ENVELOPE_LEN + expected.len() * 28);
3600 assert_eq!(decode_batch(bytes).unwrap(), expected);
3601 }
3602
3603 #[test]
3607 fn fr42_shadow_a_list_of_shadows_round_trips_in_order() {
3608 let shadows = [
3609 Modifier::Shadow {
3610 x: 0.0,
3611 y: 1.0,
3612 blur: 2.0,
3613 spread: 0.0,
3614 paint: Paint::Literal(crate::schema::Color::argb(0x1400_0000)),
3615 },
3616 Modifier::Shadow {
3617 x: 0.0,
3618 y: 4.0,
3619 blur: 12.0,
3620 spread: -2.0,
3621 paint: Paint::Literal(crate::schema::Color::argb(0x2900_0000)),
3622 },
3623 Modifier::Shadow {
3624 x: -3.5,
3625 y: 24.0,
3626 blur: 48.0,
3627 spread: 4.0,
3628 paint: Paint::Role(crate::schema::ColorRole::Outline),
3629 },
3630 ];
3631 let expected: Vec<_> = shadows
3632 .iter()
3633 .enumerate()
3634 .map(|(index, modifier)| Mutation::SetModifier {
3635 node_id: 7,
3636 index: 9 + index as u16,
3637 modifier: modifier.clone(),
3638 })
3639 .collect();
3640 let mut encoder = BatchEncoder::default();
3641 for mutation in &expected {
3642 encoder.encode(mutation).unwrap();
3643 }
3644 let bytes = encoder.finish().unwrap();
3645 assert_eq!(decode_batch(bytes).unwrap(), expected);
3646 }
3647
3648 #[test]
3654 fn fr23_set_theme_round_trips_with_a_slot_for_every_type_role() {
3655 let theme = Theme::adaptive(DesignSystem::Cupertino)
3656 .with_color_scheme(ColorScheme::Dark)
3657 .with_font(TypeRole::Display, 7);
3658 let length = 20 + 4 * TYPE_ROLE_COUNT as u16;
3659 let mut encoder = BatchEncoder::default();
3660 encoder.encode(&Mutation::SetTheme(theme)).unwrap();
3661 let bytes = encoder.finish().unwrap();
3662 assert_eq!(bytes.len(), ENVELOPE_LEN + length as usize);
3663 assert_eq!(read_u16(bytes, ENVELOPE_LEN).unwrap(), TAG_SET_THEME);
3664 assert_eq!(read_u16(bytes, ENVELOPE_LEN + 2).unwrap(), length);
3665 assert_eq!(decode_batch(bytes).unwrap(), [Mutation::SetTheme(theme)]);
3666 }
3667
3668 #[test]
3670 fn fr23_a_font_is_named_for_one_role_and_no_others() {
3671 let theme = Theme::unified(DesignSystem::Material3).with_font(TypeRole::Display, 3);
3672 assert_eq!(theme.font(TypeRole::Display), Some(3));
3673 for role in [
3674 TypeRole::Body,
3675 TypeRole::Title,
3676 TypeRole::Mono,
3677 TypeRole::Caption,
3678 ] {
3679 assert_eq!(theme.font(role), None);
3680 }
3681 }
3682
3683 #[test]
3686 fn fr52_a_default_font_names_every_role_but_mono() {
3687 let theme = Theme::unified(DesignSystem::Material3).with_default_font(5);
3688 for role in [
3689 TypeRole::Display,
3690 TypeRole::Headline,
3691 TypeRole::Title,
3692 TypeRole::Subtitle,
3693 TypeRole::Body,
3694 TypeRole::BodyStrong,
3695 TypeRole::Label,
3696 TypeRole::Caption,
3697 ] {
3698 assert_eq!(theme.font(role), Some(5), "{role:?}");
3699 }
3700 assert_eq!(theme.font(TypeRole::Mono), None);
3701 assert_eq!(theme.font(TypeRole::None), None);
3702
3703 let both = theme
3704 .with_font(TypeRole::Mono, 9)
3705 .with_font(TypeRole::Display, 3);
3706 assert_eq!(both.font(TypeRole::Mono), Some(9));
3707 assert_eq!(
3708 both.font(TypeRole::Display),
3709 Some(3),
3710 "the role named last wins"
3711 );
3712 assert_eq!(both.font(TypeRole::Body), Some(5));
3713
3714 let mut encoder = BatchEncoder::default();
3715 encoder.encode(&Mutation::SetTheme(theme)).unwrap();
3716 let bytes = encoder.finish().unwrap();
3717 assert_eq!(decode_batch(bytes).unwrap(), [Mutation::SetTheme(theme)]);
3718 }
3719
3720 #[test]
3722 fn fr14_rejects_unknown_theme_tags() {
3723 let mut encoder = BatchEncoder::default();
3724 encoder
3725 .encode(&Mutation::SetTheme(Theme::default()))
3726 .unwrap();
3727 let mut bytes = encoder.finish().unwrap().to_vec();
3728 bytes[ENVELOPE_LEN + 4..ENVELOPE_LEN + 6].copy_from_slice(&99_u16.to_le_bytes());
3729 assert_eq!(decode_batch(&bytes), Err(ProtocolError::InvalidTheme(99)));
3730 }
3731
3732 #[test]
3733 fn rejects_unknown_tags_without_panicking() {
3734 let mut bytes = vec![0, 0, 12, 0, 20, 0, 0, 0, 1, 0, 0, 0];
3735 bytes.extend_from_slice(&999_u16.to_le_bytes());
3736 bytes.extend_from_slice(&8_u16.to_le_bytes());
3737 bytes.extend_from_slice(&0_u32.to_le_bytes());
3738 assert_eq!(decode_batch(&bytes), Err(ProtocolError::InvalidTag(999)));
3739 }
3740
3741 #[test]
3742 fn pr4_renderer_events_round_trip_fixed_layout() {
3743 let events = [
3744 HostEvent {
3745 node_id: 7,
3746 handler_id: 11,
3747 payload: crate::EventPayload::Clicked,
3748 },
3749 HostEvent {
3750 node_id: 8,
3751 handler_id: 12,
3752 payload: crate::EventPayload::TextChanged("한글"),
3753 },
3754 HostEvent {
3755 node_id: 8,
3756 handler_id: 13,
3757 payload: crate::EventPayload::TextSubmitted("done"),
3758 },
3759 HostEvent {
3760 node_id: 8,
3761 handler_id: 14,
3762 payload: crate::EventPayload::FocusLost,
3763 },
3764 HostEvent {
3765 node_id: 9,
3766 handler_id: 16,
3767 payload: crate::EventPayload::RangeRequested {
3768 start: 100,
3769 count: 20,
3770 },
3771 },
3772 HostEvent {
3773 node_id: 8,
3774 handler_id: 15,
3775 payload: crate::EventPayload::KeyDown {
3776 key: Key::Enter,
3777 shift_key: true,
3778 ctrl_key: false,
3779 alt_key: true,
3780 meta_key: false,
3781 repeat: true,
3782 code: 0x28,
3783 text: "Enter",
3784 },
3785 },
3786 ];
3787 let mut bytes = Vec::new();
3788 for event in events {
3789 encode_event(&event, &mut bytes).unwrap();
3790 assert_eq!(decode_event(&bytes).unwrap(), event);
3791 }
3792 }
3793
3794 #[test]
3795 fn fr19_9_window_safe_area_round_trips() {
3796 let event = HostEvent {
3797 node_id: 0,
3798 handler_id: 0,
3799 payload: crate::EventPayload::WindowSizeChanged {
3800 width_dp: 841.5,
3801 height_dp: 600.25,
3802 class: crate::WindowSizeClass::Expanded,
3803 height_class: crate::WindowHeightClass::Medium,
3804 safe_area_top: 28.5,
3805 safe_area_leading: 72.0,
3806 safe_area_trailing: 13.0,
3807 },
3808 };
3809 let mut bytes = Vec::new();
3810 encode_event(&event, &mut bytes).unwrap();
3811 assert_eq!(bytes.len(), 44);
3812 assert_eq!(decode_event(&bytes).unwrap(), event);
3813 }
3814
3815 #[test]
3816 fn nfr7_malformed_key_down_text_is_a_protocol_error() {
3817 let event = HostEvent {
3818 node_id: 1,
3819 handler_id: 2,
3820 payload: crate::EventPayload::KeyDown {
3821 key: Key::Enter,
3822 shift_key: false,
3823 ctrl_key: false,
3824 alt_key: false,
3825 meta_key: false,
3826 repeat: false,
3827 code: 0x51,
3828 text: "ArrowDown",
3829 },
3830 };
3831 let mut bytes = Vec::new();
3832 encode_event(&event, &mut bytes).unwrap();
3833 assert_eq!(decode_event(&bytes).unwrap(), event);
3834 let mut past_end = bytes.clone();
3836 past_end[28..32].copy_from_slice(&200_u32.to_le_bytes());
3837 assert_eq!(
3838 decode_event(&past_end),
3839 Err(ProtocolError::InvalidStringRange)
3840 );
3841 let mut inside_header = bytes.clone();
3843 inside_header[24..28].copy_from_slice(&8_u32.to_le_bytes());
3844 assert_eq!(
3845 decode_event(&inside_header),
3846 Err(ProtocolError::InvalidStringRange)
3847 );
3848 let mut not_utf8 = bytes;
3850 not_utf8[32] = 0xff;
3851 assert_eq!(decode_event(¬_utf8), Err(ProtocolError::InvalidUtf8));
3852 }
3853
3854 #[test]
3855 fn fr19_9_short_window_size_record_is_a_protocol_error() {
3856 let mut bytes = vec![0; 32];
3857 bytes[0..2].copy_from_slice(&EVENT_WINDOW_SIZE_CHANGED.to_le_bytes());
3858 bytes[2..4].copy_from_slice(&32_u16.to_le_bytes());
3859 assert!(decode_event(&bytes).is_err());
3860 bytes[2..4].copy_from_slice(&44_u16.to_le_bytes());
3861 assert!(decode_event(&bytes).is_err());
3862 }
3863
3864 #[test]
3865 fn fr20_unknown_window_size_class_is_a_protocol_error() {
3866 let event = HostEvent {
3867 node_id: 0,
3868 handler_id: 0,
3869 payload: crate::EventPayload::WindowSizeChanged {
3870 width_dp: 320.0,
3871 height_dp: 640.0,
3872 class: crate::WindowSizeClass::Compact,
3873 height_class: crate::WindowHeightClass::Medium,
3874 safe_area_top: 0.0,
3875 safe_area_leading: 0.0,
3876 safe_area_trailing: 0.0,
3877 },
3878 };
3879 let mut bytes = Vec::new();
3880 encode_event(&event, &mut bytes).unwrap();
3881 bytes[24..28].copy_from_slice(&99_u32.to_le_bytes());
3882 assert_eq!(
3883 decode_event(&bytes),
3884 Err(ProtocolError::InvalidValueKind(99))
3885 );
3886 }
3887
3888 #[test]
3889 fn fr20_unknown_window_height_class_is_a_protocol_error() {
3890 let event = HostEvent {
3891 node_id: 0,
3892 handler_id: 0,
3893 payload: crate::EventPayload::WindowSizeChanged {
3894 width_dp: 320.0,
3895 height_dp: 640.0,
3896 class: crate::WindowSizeClass::Compact,
3897 height_class: crate::WindowHeightClass::Medium,
3898 safe_area_top: 0.0,
3899 safe_area_leading: 0.0,
3900 safe_area_trailing: 0.0,
3901 },
3902 };
3903 let mut bytes = Vec::new();
3904 encode_event(&event, &mut bytes).unwrap();
3905 bytes[28..32].copy_from_slice(&99_u32.to_le_bytes());
3906 assert_eq!(
3907 decode_event(&bytes),
3908 Err(ProtocolError::InvalidValueKind(99))
3909 );
3910 }
3911
3912 #[test]
3913 fn fr20_window_size_classes_follow_the_material_boundaries() {
3914 use crate::WindowSizeClass;
3915 assert_eq!(
3916 WindowSizeClass::from_width_dp(599.9),
3917 WindowSizeClass::Compact
3918 );
3919 assert_eq!(
3920 WindowSizeClass::from_width_dp(600.0),
3921 WindowSizeClass::Medium
3922 );
3923 assert_eq!(
3924 WindowSizeClass::from_width_dp(839.9),
3925 WindowSizeClass::Medium
3926 );
3927 assert_eq!(
3928 WindowSizeClass::from_width_dp(840.0),
3929 WindowSizeClass::Expanded
3930 );
3931 }
3932
3933 #[test]
3934 fn fr20_window_height_classes_follow_the_material_boundaries() {
3935 use crate::WindowHeightClass;
3936 assert_eq!(
3937 WindowHeightClass::from_height_dp(479.9),
3938 WindowHeightClass::Compact
3939 );
3940 assert_eq!(
3941 WindowHeightClass::from_height_dp(480.0),
3942 WindowHeightClass::Medium
3943 );
3944 assert_eq!(
3945 WindowHeightClass::from_height_dp(899.9),
3946 WindowHeightClass::Medium
3947 );
3948 assert_eq!(
3949 WindowHeightClass::from_height_dp(900.0),
3950 WindowHeightClass::Expanded
3951 );
3952 }
3953
3954 fn post_notification() -> Mutation<'static> {
3955 Mutation::PostNotification {
3956 key: "session/7",
3957 title: "세션이 끝났습니다",
3958 body: "refactor-parser: 테스트 214개 통과",
3959 channel: "세션",
3960 action_1: "열기",
3961 action_2: "",
3962 importance: NotificationImportance::Urgent,
3963 presentation: NotificationPresentation::WhenInactive,
3964 }
3965 }
3966
3967 #[test]
3970 fn fr36_notification_commands_round_trip_at_their_fixed_lengths() {
3971 let mutations = [
3972 post_notification(),
3973 Mutation::WithdrawNotification { key: "session/7" },
3974 Mutation::RequestNotificationPermission,
3975 ];
3976 let mut lengths = Vec::new();
3977 let mut encoder = BatchEncoder::default();
3978 for mutation in &mutations {
3979 encoder.encode(mutation).unwrap();
3980 }
3981 let bytes = encoder.finish().unwrap();
3982 let mut position = ENVELOPE_LEN;
3983 for _ in &mutations {
3984 let length = read_u16(bytes, position + 2).unwrap();
3985 lengths.push((read_u16(bytes, position).unwrap(), length));
3986 position += usize::from(length);
3987 }
3988 assert_eq!(
3989 lengths,
3990 [
3991 (TAG_POST_NOTIFICATION, 56),
3992 (TAG_WITHDRAW_NOTIFICATION, 12),
3993 (TAG_REQUEST_NOTIFICATION_PERMISSION, 4),
3994 ]
3995 );
3996 assert_eq!(decode_batch(bytes).unwrap(), mutations);
3997 }
3998
3999 #[test]
4002 fn fr36_unknown_importance_and_presentation_are_protocol_errors() {
4003 for (offset, expected) in [(52, 9_u16), (54, 7_u16)] {
4004 let mut encoder = BatchEncoder::default();
4005 encoder.encode(&post_notification()).unwrap();
4006 let mut bytes = encoder.finish().unwrap().to_vec();
4007 bytes[ENVELOPE_LEN + offset..ENVELOPE_LEN + offset + 2]
4008 .copy_from_slice(&expected.to_le_bytes());
4009 assert_eq!(
4010 decode_batch(&bytes),
4011 Err(ProtocolError::InvalidNotification(expected))
4012 );
4013 }
4014 }
4015
4016 #[test]
4019 fn fr36_a_notification_record_of_the_wrong_length_is_rejected() {
4020 let mut bytes = vec![0, 0, 12, 0, 20, 0, 0, 0, 1, 0, 0, 0];
4021 bytes.extend_from_slice(&TAG_REQUEST_NOTIFICATION_PERMISSION.to_le_bytes());
4022 bytes.extend_from_slice(&8_u16.to_le_bytes());
4023 bytes.extend_from_slice(&0_u32.to_le_bytes());
4024 assert_eq!(
4025 decode_batch(&bytes),
4026 Err(ProtocolError::InvalidRecordLength)
4027 );
4028 }
4029
4030 #[test]
4033 fn fr36_notification_events_round_trip_fixed_layout() {
4034 let activated = HostEvent {
4035 node_id: 0,
4036 handler_id: 0,
4037 payload: crate::EventPayload::NotificationActivated {
4038 action: 2,
4039 key: "session/7",
4040 },
4041 };
4042 let mut bytes = Vec::new();
4043 encode_event(&activated, &mut bytes).unwrap();
4044 assert_eq!(read_u16(&bytes, 0).unwrap(), EVENT_NOTIFICATION_ACTIVATED);
4045 assert_eq!(read_u16(&bytes, 2).unwrap(), 28);
4046 assert_eq!(bytes.len(), 28 + "session/7".len());
4047 assert_eq!(decode_event(&bytes).unwrap(), activated);
4048
4049 for state in [
4050 NotificationPermission::NotDetermined,
4051 NotificationPermission::Granted,
4052 NotificationPermission::Denied,
4053 NotificationPermission::Unsupported,
4054 ] {
4055 let changed = HostEvent {
4056 node_id: 0,
4057 handler_id: 0,
4058 payload: crate::EventPayload::NotificationPermissionChanged(state),
4059 };
4060 encode_event(&changed, &mut bytes).unwrap();
4061 assert_eq!(bytes.len(), 20);
4062 assert_eq!(decode_event(&bytes).unwrap(), changed);
4063 }
4064 }
4065
4066 #[test]
4068 fn fr36_unknown_permission_state_is_a_protocol_error() {
4069 let mut bytes = Vec::new();
4070 encode_event(
4071 &HostEvent {
4072 node_id: 0,
4073 handler_id: 0,
4074 payload: crate::EventPayload::NotificationPermissionChanged(
4075 NotificationPermission::Granted,
4076 ),
4077 },
4078 &mut bytes,
4079 )
4080 .unwrap();
4081 bytes[16..20].copy_from_slice(&5_u32.to_le_bytes());
4082 assert_eq!(
4083 decode_event(&bytes),
4084 Err(ProtocolError::InvalidValueKind(5))
4085 );
4086 }
4087
4088 #[test]
4091 fn fr36_an_activation_naming_a_third_button_is_a_protocol_error() {
4092 let mut bytes = Vec::new();
4093 encode_event(
4094 &HostEvent {
4095 node_id: 0,
4096 handler_id: 0,
4097 payload: crate::EventPayload::NotificationActivated {
4098 action: 1,
4099 key: "k",
4100 },
4101 },
4102 &mut bytes,
4103 )
4104 .unwrap();
4105 bytes[16..20].copy_from_slice(&3_u32.to_le_bytes());
4106 assert_eq!(
4107 decode_event(&bytes),
4108 Err(ProtocolError::InvalidValueKind(3))
4109 );
4110 }
4111
4112 #[test]
4114 fn fr43_zoom_changed_round_trips_fixed_layout() {
4115 let event = HostEvent {
4116 node_id: 0,
4117 handler_id: 0,
4118 payload: crate::EventPayload::ZoomChanged {
4119 k: 1.25 * 1.44,
4120 os: 1.25,
4121 level: 2,
4122 },
4123 };
4124 let mut bytes = Vec::new();
4125 encode_event(&event, &mut bytes).unwrap();
4126 assert_eq!(read_u16(&bytes, 0).unwrap(), 33);
4127 assert_eq!(read_u16(&bytes, 2).unwrap(), 28);
4128 assert_eq!(bytes.len(), 28);
4129 assert_eq!(decode_event(&bytes).unwrap(), event);
4130
4131 let smaller = HostEvent {
4132 node_id: 0,
4133 handler_id: 0,
4134 payload: crate::EventPayload::ZoomChanged {
4135 k: 0.5,
4136 os: 1.0,
4137 level: -4,
4138 },
4139 };
4140 encode_event(&smaller, &mut bytes).unwrap();
4141 assert_eq!(decode_event(&bytes).unwrap(), smaller);
4142 }
4143
4144 #[test]
4146 fn fr43_a_zoom_level_out_of_range_is_rejected() {
4147 let event = HostEvent {
4148 node_id: 0,
4149 handler_id: 0,
4150 payload: crate::EventPayload::ZoomChanged {
4151 k: 1.0,
4152 os: 1.0,
4153 level: 9,
4154 },
4155 };
4156 let mut bytes = Vec::new();
4157 encode_event(&event, &mut bytes).unwrap();
4158 assert_eq!(
4159 decode_event(&bytes),
4160 Err(ProtocolError::InvalidValueKind(9))
4161 );
4162 bytes[2..4].copy_from_slice(&24_u16.to_le_bytes());
4163 assert_eq!(
4164 decode_event(&bytes),
4165 Err(ProtocolError::InvalidRecordLength)
4166 );
4167 }
4168
4169 #[test]
4172 fn fr43_the_window_record_carries_a_zoom_level() {
4173 for level in [None, Some(-8), Some(0), Some(2), Some(8)] {
4174 let mut window = crate::schema::Window::new().with_title("zoom");
4175 window.zoom_level = level;
4176 let mut encoder = BatchEncoder::default();
4177 encoder.encode(&Mutation::SetWindow(window)).unwrap();
4178 let bytes = encoder.finish().unwrap().to_vec();
4179 if level.is_none() {
4180 assert_eq!(read_u16(&bytes, ENVELOPE_LEN + 18).unwrap(), 0);
4181 }
4182 let decoded = decode_batch(&bytes).unwrap();
4183 assert_eq!(decoded, vec![Mutation::SetWindow(window)]);
4184 }
4185 assert_eq!(
4186 crate::schema::Window::new().with_zoom_level(20).zoom_level,
4187 Some(8)
4188 );
4189 }
4190
4191 #[test]
4194 fn fr20_8_set_window_report_size_bit_roundtrips() {
4195 for report in [false, true] {
4196 let window = crate::schema::Window::new()
4197 .with_title("size")
4198 .with_report_size(report);
4199 let mut encoder = BatchEncoder::default();
4200 encoder.encode(&Mutation::SetWindow(window)).unwrap();
4201 let bytes = encoder.finish().unwrap().to_vec();
4202 let flags = read_u16(&bytes, ENVELOPE_LEN + 16).unwrap();
4203 assert_eq!(flags, 1 | (u16::from(report) << 1));
4204 assert_eq!(
4205 decode_batch(&bytes).unwrap(),
4206 vec![Mutation::SetWindow(window)]
4207 );
4208 }
4209 let window = crate::schema::Window::new();
4211 let mut encoder = BatchEncoder::default();
4212 encoder.encode(&Mutation::SetWindow(window)).unwrap();
4213 let bytes = encoder.finish().unwrap().to_vec();
4214 let record = &bytes[ENVELOPE_LEN..];
4215 assert_eq!(record.len(), 32);
4216 assert_eq!(read_u16(record, 0).unwrap(), TAG_SET_WINDOW);
4217 assert_eq!(read_u16(record, 2).unwrap(), 32);
4218 assert_eq!(read_u16(record, 4).unwrap(), window.chrome as u16);
4219 assert_eq!(read_u16(record, 6).unwrap(), window.title_bar as u16);
4220 for at in [8, 10, 12, 14] {
4221 assert_eq!(read_u16(record, at).unwrap(), 0);
4222 }
4223 let expected_flags = 1
4225 | ((window.mode as u16) << WINDOW_MODE_SHIFT)
4226 | ((window.last_window_closed as u16) << WINDOW_POLICY_SHIFT);
4227 assert_eq!(read_u16(record, 16).unwrap(), expected_flags);
4228 assert_eq!(read_u16(record, 18).unwrap(), 0);
4229 assert_eq!(read_u32(record, 28).unwrap(), 0);
4230 }
4231
4232 #[test]
4235 fn fr20_8_set_window_rejects_unknown_flag_bits() {
4236 let mut encoder = BatchEncoder::default();
4237 encoder
4238 .encode(&Mutation::SetWindow(crate::schema::Window::new()))
4239 .unwrap();
4240 let bytes = encoder.finish().unwrap().to_vec();
4241 let flags = read_u16(&bytes, ENVELOPE_LEN + 16).unwrap();
4242 for bit in 2..8 {
4243 let bad = flags | (1 << bit);
4244 let mut copy = bytes.clone();
4245 copy[ENVELOPE_LEN + 16..ENVELOPE_LEN + 18].copy_from_slice(&bad.to_le_bytes());
4246 assert_eq!(
4247 decode_batch(©),
4248 Err(ProtocolError::InvalidValueKind(bad)),
4249 "bit {bit}"
4250 );
4251 }
4252 }
4253
4254 #[test]
4255 fn fr65_request_focus_round_trips_in_eight_bytes() {
4256 for node_id in [0_u32, 7, u32::MAX] {
4257 let mut encoder = BatchEncoder::default();
4258 encoder.encode(&Mutation::RequestFocus { node_id }).unwrap();
4259 let bytes = encoder.finish().unwrap().to_vec();
4260 assert_eq!(bytes.len(), ENVELOPE_LEN + 8);
4261 assert_eq!(read_u16(&bytes, ENVELOPE_LEN).unwrap(), TAG_REQUEST_FOCUS);
4262 assert_eq!(read_u16(&bytes, ENVELOPE_LEN + 2).unwrap(), 8);
4263 assert_eq!(read_u32(&bytes, ENVELOPE_LEN + 4).unwrap(), node_id);
4264 assert_eq!(
4265 decode_batch(&bytes).unwrap(),
4266 vec![Mutation::RequestFocus { node_id }]
4267 );
4268 }
4269 let mut encoder = BatchEncoder::default();
4270 encoder
4271 .encode(&Mutation::RequestFocus { node_id: 7 })
4272 .unwrap();
4273 let mut bad = encoder.finish().unwrap().to_vec();
4274 bad.extend_from_slice(&[0; 4]);
4275 let records_len = u32::try_from(bad.len()).unwrap();
4276 bad[4..8].copy_from_slice(&records_len.to_le_bytes());
4277 bad[ENVELOPE_LEN + 2..ENVELOPE_LEN + 4].copy_from_slice(&12_u16.to_le_bytes());
4278 assert_eq!(decode_batch(&bad), Err(ProtocolError::InvalidRecordLength));
4279 }
4280
4281 #[test]
4285 fn fr72_file_dialog_records_round_trip_and_unknown_enums_are_errors() {
4286 let filters = "이미지\0png,jpg\0Docs\0pdf,txt";
4287 for kind in [
4288 FileDialogKind::OpenFile,
4289 FileDialogKind::OpenFiles,
4290 FileDialogKind::OpenFolder,
4291 FileDialogKind::OpenFolders,
4292 FileDialogKind::Save,
4293 ] {
4294 let request = Mutation::ShowFileDialog {
4295 request: 7,
4296 window: 20,
4297 kind,
4298 default_filter: 1,
4299 title: "파일 고르기",
4300 directory: "/home/한글",
4301 name: "보고서.txt",
4302 filters,
4303 };
4304 let mut encoder = BatchEncoder::default();
4305 encoder.encode(&request).unwrap();
4306 let bytes = encoder.finish().unwrap().to_vec();
4307 assert_eq!(
4308 read_u16(&bytes, ENVELOPE_LEN).unwrap(),
4309 TAG_SHOW_FILE_DIALOG
4310 );
4311 assert_eq!(read_u16(&bytes, ENVELOPE_LEN + 2).unwrap(), 48);
4312 assert_eq!(decode_batch(&bytes).unwrap(), vec![request]);
4313 }
4314
4315 let empty = Mutation::ShowFileDialog {
4316 request: u32::MAX,
4317 window: 0,
4318 kind: FileDialogKind::Save,
4319 default_filter: 0,
4320 title: "",
4321 directory: "",
4322 name: "",
4323 filters: "",
4324 };
4325 let mut encoder = BatchEncoder::default();
4326 encoder.encode(&empty).unwrap();
4327 assert_eq!(
4328 decode_batch(encoder.finish().unwrap()).unwrap(),
4329 vec![empty]
4330 );
4331
4332 let full = Mutation::ShowFileDialog {
4333 request: 1,
4334 window: 2,
4335 kind: FileDialogKind::OpenFile,
4336 default_filter: 3,
4337 title: "t",
4338 directory: "/d",
4339 name: "n",
4340 filters,
4341 };
4342 let mut encoder = BatchEncoder::default();
4343 encoder.encode(&full).unwrap();
4344 let bytes = encoder.finish().unwrap().to_vec();
4345 for raw_kind in [0_u16, 6, 9, u16::MAX] {
4346 let mut bad = bytes.clone();
4347 bad[ENVELOPE_LEN + 12..ENVELOPE_LEN + 14].copy_from_slice(&raw_kind.to_le_bytes());
4348 assert_eq!(
4349 decode_batch(&bad),
4350 Err(ProtocolError::InvalidValueKind(raw_kind))
4351 );
4352 }
4353 let mut short = bytes.clone();
4354 short[ENVELOPE_LEN + 2..ENVELOPE_LEN + 4].copy_from_slice(&44_u16.to_le_bytes());
4355 assert_eq!(
4356 decode_batch(&short),
4357 Err(ProtocolError::InvalidRecordLength)
4358 );
4359 for cut in 0..bytes.len() {
4360 assert!(
4361 decode_batch(&bytes[..cut]).is_err(),
4362 "a batch cut at {cut} bytes decoded"
4363 );
4364 }
4365
4366 let paths = "/Users/한글/문서.txt\0/tmp/b c.png";
4367 for (status, filter) in [
4368 (FileDialogStatus::Chosen, 1_u32),
4369 (FileDialogStatus::Cancelled, u32::MAX),
4370 (FileDialogStatus::Unavailable, u32::MAX),
4371 ] {
4372 let result = HostEvent {
4373 node_id: 0,
4374 handler_id: 0,
4375 payload: crate::EventPayload::FileDialogResult {
4376 request: 41,
4377 status,
4378 filter,
4379 paths: if status == FileDialogStatus::Chosen {
4380 paths
4381 } else {
4382 ""
4383 },
4384 },
4385 };
4386 let mut bytes = Vec::new();
4387 encode_event(&result, &mut bytes).unwrap();
4388 assert_eq!(read_u16(&bytes, 0).unwrap(), EVENT_FILE_DIALOG_RESULT);
4389 assert_eq!(
4390 usize::from(read_u16(&bytes, 2).unwrap()),
4391 FILE_DIALOG_RESULT_LEN
4392 );
4393 assert_eq!(decode_event(&bytes).unwrap(), result);
4394 }
4395
4396 let result = HostEvent {
4397 node_id: 0,
4398 handler_id: 0,
4399 payload: crate::EventPayload::FileDialogResult {
4400 request: 3,
4401 status: FileDialogStatus::Chosen,
4402 filter: 0,
4403 paths,
4404 },
4405 };
4406 let mut bytes = Vec::new();
4407 encode_event(&result, &mut bytes).unwrap();
4408 for raw_status in [0_u32, 4, 0x1_0000, u32::MAX] {
4409 let mut bad = bytes.clone();
4410 bad[20..24].copy_from_slice(&raw_status.to_le_bytes());
4411 assert_eq!(
4412 decode_event(&bad),
4413 Err(ProtocolError::InvalidValueKind(
4414 raw_status.min(u32::from(u16::MAX)) as u16
4415 ))
4416 );
4417 }
4418 let mut wrong_length = bytes.clone();
4419 wrong_length[2..4].copy_from_slice(&32_u16.to_le_bytes());
4420 assert_eq!(
4421 decode_event(&wrong_length),
4422 Err(ProtocolError::InvalidRecordLength)
4423 );
4424 let mut not_utf8 = bytes.clone();
4425 let last = not_utf8.len() - 1;
4426 not_utf8[last] = 0xFF;
4427 assert_eq!(decode_event(¬_utf8), Err(ProtocolError::InvalidUtf8));
4428 for cut in 0..bytes.len() {
4429 assert!(
4430 decode_event(&bytes[..cut]).is_err(),
4431 "an event cut at {cut} bytes decoded"
4432 );
4433 }
4434 }
4435
4436 #[test]
4437 fn fr65_text_field_on_composition_change_delivers_bool() {
4438 use std::cell::RefCell;
4439 use std::rc::Rc;
4440 use std::sync::Arc;
4441 use std::sync::atomic::{AtomicUsize, Ordering};
4442
4443 let seen: Rc<RefCell<Vec<bool>>> = Rc::new(RefCell::new(Vec::new()));
4444 let calls = Arc::new(AtomicUsize::new(0));
4445 let calls_for_root = Arc::clone(&calls);
4446 let mut host = crate::ComposeHost::with_content(move || {
4447 let calls_inner = Arc::clone(&calls_for_root);
4448 crate::ui::TextField().on_composition_change(move |composing| {
4449 calls_inner.fetch_add(if composing { 10 } else { 1 }, Ordering::SeqCst);
4450 });
4451 });
4452 let init = host.rebuild().unwrap().to_vec();
4453 let mutations = decode_batch(&init).unwrap();
4454 let prop = mutations
4455 .iter()
4456 .find_map(|mutation| match mutation {
4457 Mutation::SetProp {
4458 node_id,
4459 property: PropertyKind::OnCompositionChange,
4460 value: PropertyValue::Integer(handler),
4461 } => Some((*node_id, *handler as u64)),
4462 _ => None,
4463 })
4464 .expect("TextField with on_composition_change must emit OnCompositionChange");
4465 let mut wire = Vec::new();
4466 encode_event(
4467 &HostEvent {
4468 node_id: prop.0,
4469 handler_id: prop.1,
4470 payload: crate::EventPayload::CompositionChanged { composing: true },
4471 },
4472 &mut wire,
4473 )
4474 .unwrap();
4475 host.dispatch_event(&wire).unwrap();
4476 encode_event(
4477 &HostEvent {
4478 node_id: prop.0,
4479 handler_id: prop.1,
4480 payload: crate::EventPayload::CompositionChanged { composing: false },
4481 },
4482 &mut wire,
4483 )
4484 .unwrap();
4485 host.dispatch_event(&wire).unwrap();
4486 assert_eq!(calls.load(Ordering::SeqCst), 11);
4487 drop(seen);
4488 }
4489
4490 #[test]
4491 fn fr45_focus_gained_and_lost_pair() {
4492 use std::sync::Arc;
4493 use std::sync::Mutex;
4494
4495 let trace: Arc<Mutex<Vec<&'static str>>> = Arc::new(Mutex::new(Vec::new()));
4496 let trace_for_root = Arc::clone(&trace);
4497 let mut host = crate::ComposeHost::with_content(move || {
4498 let gained_trace = Arc::clone(&trace_for_root);
4499 let lost_trace = Arc::clone(&trace_for_root);
4500 crate::ui::TextField()
4501 .on_focus_gained(move || {
4502 gained_trace.lock().unwrap().push("gained");
4503 })
4504 .on_focus_lost(move || {
4505 lost_trace.lock().unwrap().push("lost");
4506 });
4507 });
4508 let init = host.rebuild().unwrap().to_vec();
4509 let mutations = decode_batch(&init).unwrap();
4510 let gained_prop = mutations
4511 .iter()
4512 .find_map(|mutation| match mutation {
4513 Mutation::SetProp {
4514 node_id,
4515 property: PropertyKind::OnFocusGained,
4516 value: PropertyValue::Integer(handler),
4517 } => Some((*node_id, *handler as u64)),
4518 _ => None,
4519 })
4520 .expect("TextField with on_focus_gained must emit OnFocusGained");
4521 let lost_prop = mutations
4522 .iter()
4523 .find_map(|mutation| match mutation {
4524 Mutation::SetProp {
4525 node_id,
4526 property: PropertyKind::OnFocusLost,
4527 value: PropertyValue::Integer(handler),
4528 } => Some((*node_id, *handler as u64)),
4529 _ => None,
4530 })
4531 .expect("TextField with on_focus_lost must emit OnFocusLost");
4532 assert_eq!(gained_prop.0, lost_prop.0);
4533 assert_eq!(PropertyKind::OnFocusGained as u16, 117);
4534
4535 let gained_event = HostEvent {
4536 node_id: gained_prop.0,
4537 handler_id: gained_prop.1,
4538 payload: crate::EventPayload::FocusGained,
4539 };
4540 let mut wire = Vec::new();
4541 encode_event(&gained_event, &mut wire).unwrap();
4542 assert_eq!(wire.len(), 16);
4543 assert_eq!(read_u16(&wire, 0).unwrap(), EVENT_FOCUS_GAINED);
4544 assert_eq!(EVENT_FOCUS_GAINED, 48);
4545 assert_eq!(decode_event(&wire).unwrap(), gained_event);
4546 host.dispatch_event(&wire).unwrap();
4547
4548 let lost_event = HostEvent {
4549 node_id: lost_prop.0,
4550 handler_id: lost_prop.1,
4551 payload: crate::EventPayload::FocusLost,
4552 };
4553 encode_event(&lost_event, &mut wire).unwrap();
4554 assert_eq!(decode_event(&wire).unwrap(), lost_event);
4555 host.dispatch_event(&wire).unwrap();
4556
4557 assert_eq!(*trace.lock().unwrap(), vec!["gained", "lost"]);
4558 }
4559
4560 #[test]
4561 fn fr45_key_up_follows_key_down() {
4562 use std::sync::Arc;
4563 use std::sync::Mutex;
4564
4565 type KeyTrace = Vec<(&'static str, Key, bool, bool, bool, bool)>;
4567 let trace: Arc<Mutex<KeyTrace>> = Arc::new(Mutex::new(Vec::new()));
4568 let trace_for_root = Arc::clone(&trace);
4569 let mut host = crate::ComposeHost::with_content(move || {
4570 let down_trace = Arc::clone(&trace_for_root);
4571 let up_trace = Arc::clone(&trace_for_root);
4572 crate::ui::TextField()
4573 .on_key_down(move |event| {
4574 down_trace.lock().unwrap().push((
4575 "down",
4576 event.key(),
4577 event.shift_key(),
4578 event.ctrl_key(),
4579 event.alt_key(),
4580 event.meta_key(),
4581 ));
4582 })
4583 .on_key_up(move |event| {
4584 up_trace.lock().unwrap().push((
4585 "up",
4586 event.key(),
4587 event.shift_key(),
4588 event.ctrl_key(),
4589 event.alt_key(),
4590 event.meta_key(),
4591 ));
4592 });
4593 });
4594 let init = host.rebuild().unwrap().to_vec();
4595 let mutations = decode_batch(&init).unwrap();
4596 let down_prop = mutations
4597 .iter()
4598 .find_map(|mutation| match mutation {
4599 Mutation::SetProp {
4600 node_id,
4601 property: PropertyKind::OnKeyDown,
4602 value: PropertyValue::Integer(handler),
4603 } => Some((*node_id, *handler as u64)),
4604 _ => None,
4605 })
4606 .expect("TextField with on_key_down must emit OnKeyDown");
4607 let up_prop = mutations
4608 .iter()
4609 .find_map(|mutation| match mutation {
4610 Mutation::SetProp {
4611 node_id,
4612 property: PropertyKind::OnKeyUp,
4613 value: PropertyValue::Integer(handler),
4614 } => Some((*node_id, *handler as u64)),
4615 _ => None,
4616 })
4617 .expect("TextField with on_key_up must emit OnKeyUp");
4618 assert_eq!(down_prop.0, up_prop.0);
4619 assert_eq!(PropertyKind::OnKeyUp as u16, 118);
4620
4621 let down_event = HostEvent {
4622 node_id: down_prop.0,
4623 handler_id: down_prop.1,
4624 payload: crate::EventPayload::KeyDown {
4625 key: Key::Enter,
4626 shift_key: true,
4627 ctrl_key: false,
4628 alt_key: true,
4629 meta_key: false,
4630 repeat: false,
4631 code: 0x28,
4632 text: "Enter",
4633 },
4634 };
4635 let mut wire = Vec::new();
4636 encode_event(&down_event, &mut wire).unwrap();
4637 assert_eq!(decode_event(&wire).unwrap(), down_event);
4638 host.dispatch_event(&wire).unwrap();
4639
4640 let up_event = HostEvent {
4641 node_id: up_prop.0,
4642 handler_id: up_prop.1,
4643 payload: crate::EventPayload::KeyUp {
4644 key: Key::Enter,
4645 shift_key: true,
4646 ctrl_key: false,
4647 alt_key: true,
4648 meta_key: false,
4649 repeat: false,
4650 code: 0x28,
4651 text: "Enter",
4652 },
4653 };
4654 encode_event(&up_event, &mut wire).unwrap();
4655 assert_eq!(wire.len(), 37);
4656 assert_eq!(read_u16(&wire, 0).unwrap(), EVENT_KEY_UP);
4657 assert_eq!(EVENT_KEY_UP, 49);
4658 assert_eq!(decode_event(&wire).unwrap(), up_event);
4659 host.dispatch_event(&wire).unwrap();
4660
4661 assert_eq!(
4662 *trace.lock().unwrap(),
4663 vec![
4664 ("down", Key::Enter, true, false, true, false),
4665 ("up", Key::Enter, true, false, true, false),
4666 ]
4667 );
4668 }
4669
4670 #[test]
4671 fn fr45_unregistered_nodes_get_no_events() {
4672 let mut host = crate::ComposeHost::with_content(|| {
4673 crate::ui::TextField();
4674 crate::ui::Box().content(|| {});
4675 });
4676 let init = host.rebuild().unwrap().to_vec();
4677 let mutations = decode_batch(&init).unwrap();
4678 for mutation in &mutations {
4679 if let Mutation::SetProp { property, .. } = mutation {
4680 assert_ne!(
4681 *property,
4682 PropertyKind::OnFocusGained,
4683 "unregistered node must not emit OnFocusGained"
4684 );
4685 assert_ne!(
4686 *property,
4687 PropertyKind::OnKeyUp,
4688 "unregistered node must not emit OnKeyUp"
4689 );
4690 }
4691 }
4692 }
4693
4694 #[test]
4697 fn fr12_named_keys_are_appended_and_round_trip() {
4698 let named = [
4699 (Key::Enter, 1u16, 0x28u32, "Enter"),
4700 (Key::Other, 2, 0x04, "a"),
4701 (Key::Escape, 3, 0x29, "Escape"),
4702 (Key::Tab, 4, 0x2B, "Tab"),
4703 (Key::Backspace, 5, 0x2A, "Backspace"),
4704 (Key::Delete, 6, 0x4C, "Delete"),
4705 (Key::ArrowUp, 7, 0x52, "ArrowUp"),
4706 (Key::ArrowDown, 8, 0x51, "ArrowDown"),
4707 (Key::ArrowLeft, 9, 0x50, "ArrowLeft"),
4708 (Key::ArrowRight, 10, 0x4F, "ArrowRight"),
4709 (Key::Home, 11, 0x4A, "Home"),
4710 (Key::End, 12, 0x4D, "End"),
4711 (Key::PageUp, 13, 0x4B, "PageUp"),
4712 (Key::PageDown, 14, 0x4E, "PageDown"),
4713 ];
4714 let mut wire = Vec::new();
4715 for (key, tag, code, text) in named {
4716 assert_eq!(Key::try_from(tag), Ok(key), "tag {tag} names {text}");
4717 let event = HostEvent {
4718 node_id: 4,
4719 handler_id: 9,
4720 payload: crate::EventPayload::KeyDown {
4721 key,
4722 shift_key: false,
4723 ctrl_key: false,
4724 alt_key: false,
4725 meta_key: false,
4726 repeat: false,
4727 code,
4728 text,
4729 },
4730 };
4731 encode_event(&event, &mut wire).unwrap();
4732 assert_eq!(read_u16(&wire, 16).unwrap(), tag);
4733 assert_eq!(decode_event(&wire).unwrap(), event);
4734 }
4735 assert_eq!(Key::try_from(15), Err(()));
4736 }
4737
4738 #[test]
4742 fn fr45_container_key_down_sees_escape_and_consumes_it() {
4743 use std::sync::{Arc, Mutex};
4744
4745 let seen: Arc<Mutex<Vec<(String, u32)>>> = Arc::new(Mutex::new(Vec::new()));
4746 let seen_for_root = Arc::clone(&seen);
4747 let mut host = crate::ComposeHost::with_content(move || {
4748 let seen = Arc::clone(&seen_for_root);
4749 crate::ui::Column()
4750 .on_key_down(move |event| {
4751 seen.lock()
4752 .unwrap()
4753 .push((event.text().to_owned(), event.code()));
4754 if event.text() == "Escape" {
4755 event.consume();
4756 }
4757 })
4758 .content(|| {
4759 crate::ui::Text("session list");
4760 });
4761 });
4762 let init = host.rebuild().unwrap().to_vec();
4763 let mutations = decode_batch(&init).unwrap();
4764 let (node_id, handler_id) = mutations
4765 .iter()
4766 .find_map(|mutation| match mutation {
4767 Mutation::SetProp {
4768 node_id,
4769 property: PropertyKind::OnKeyDown,
4770 value: PropertyValue::Integer(handler),
4771 } => Some((*node_id, *handler as u64)),
4772 _ => None,
4773 })
4774 .expect("a Column with on_key_down must emit OnKeyDown");
4775 let created_column = mutations.iter().any(|mutation| {
4776 matches!(
4777 mutation,
4778 Mutation::Create { node_id: id, widget: crate::WidgetKind::Column, .. } if *id == node_id
4779 )
4780 });
4781 assert!(
4782 created_column,
4783 "the key handler belongs to the Column itself"
4784 );
4785
4786 let mut wire = Vec::new();
4787 let mut press = |code: u32, text: &'static str| {
4788 encode_event(
4789 &HostEvent {
4790 node_id,
4791 handler_id,
4792 payload: crate::EventPayload::KeyDown {
4793 key: if code == 0x29 {
4794 Key::Escape
4795 } else {
4796 Key::ArrowDown
4797 },
4798 shift_key: false,
4799 ctrl_key: false,
4800 alt_key: false,
4801 meta_key: false,
4802 repeat: false,
4803 code,
4804 text,
4805 },
4806 },
4807 &mut wire,
4808 )
4809 .unwrap();
4810 host.dispatch_event(&wire).unwrap().1
4811 };
4812 assert_ne!(
4813 press(0x29, "Escape"),
4814 0,
4815 "Escape was consumed by the container"
4816 );
4817 assert_eq!(
4818 press(0x51, "ArrowDown"),
4819 0,
4820 "an unhandled key is left to the Renderer"
4821 );
4822
4823 assert_eq!(
4824 *seen.lock().unwrap(),
4825 vec![("Escape".to_owned(), 0x29), ("ArrowDown".to_owned(), 0x51)]
4826 );
4827 }
4828}