Skip to main content

systemless/
event_queue.rs

1//! Architecture-neutral ownership for Event Manager queue and redraw state.
2
3use std::collections::VecDeque;
4use std::ops::{Deref, DerefMut};
5
6/// A queued Mac event (mouseDown, mouseUp, keyDown, etc.).
7///
8/// The Operating System Event Manager owns one queue, and `EventRecord.where`
9/// uses global coordinates. Inside Macintosh Volume I, I-244 and I-259.
10#[derive(Clone, Debug, PartialEq, Eq)]
11pub struct QueuedEvent {
12    /// Event type (1=mouseDown, 2=mouseUp, 3=keyDown, etc.).
13    pub what: u16,
14    /// Event message (key code for key events, window ptr for activate, etc.).
15    pub message: u32,
16    /// Guest tick when the event was posted. Retrieval must not rewrite this
17    /// timestamp; EventRecord.when is the posting time on classic Mac OS.
18    pub when: u32,
19    /// Mouse location at event time, in global Macintosh coordinates.
20    pub where_v: i16,
21    pub where_h: i16,
22    /// Modifier flags.
23    pub modifiers: u16,
24}
25
26/// Architecture-neutral semantic copy of the EventRecord most recently
27/// exposed to guest code. Keeping the full-width message and posting tick
28/// here lets 68K and PowerPC showcase probes assert identical behavior
29/// without reading ABI-specific guest memory layouts.
30#[derive(Clone, Debug, Default, PartialEq, Eq)]
31pub struct EventRecordSnapshot {
32    pub what: u16,
33    pub message: u32,
34    pub when: u32,
35    pub where_v: i16,
36    pub where_h: i16,
37    pub modifiers: u16,
38}
39
40/// Result from one Event Manager queue probe call.
41#[derive(Clone, Debug, Default, PartialEq, Eq)]
42pub struct EventProbeResult {
43    pub available: bool,
44    pub record: EventRecordSnapshot,
45}
46
47/// Architecture-neutral results from the Event Manager queue probe used by
48/// the showcase. Each optional value records the most recent call of that
49/// kind, preserving the returned event type even after a later call consumes
50/// the queue entry.
51#[derive(Clone, Debug, Default, PartialEq, Eq)]
52pub struct EventQueueProbeSnapshot {
53    pub post_result: Option<i16>,
54    pub event_avail: Option<EventProbeResult>,
55    pub os_event_avail: Option<EventProbeResult>,
56    pub get_os_event: Option<EventProbeResult>,
57}
58
59/// Architecture-neutral Event Manager state exposed by deterministic fixture
60/// runners. This is intentionally semantic: callers can assert queue order,
61/// live input, lifecycle delivery, and cursor state on either CPU adapter.
62#[derive(Clone, Debug, PartialEq, Eq)]
63pub struct EventManagerSnapshot {
64    pub last_record: Option<EventRecordSnapshot>,
65    pub queue_probe: EventQueueProbeSnapshot,
66    pub queue_len: usize,
67    pub queued_event_types: Vec<u16>,
68    pub mouse_position: (i16, i16),
69    pub mouse_button: bool,
70    pub button_result: Option<bool>,
71    pub still_down_result: Option<bool>,
72    pub wait_mouse_up_result: Option<bool>,
73    pub key_map: [u8; 16],
74    pub lifecycle_activation_seen: bool,
75    pub lifecycle_update_seen: bool,
76    pub cursor_visible: bool,
77    pub cursor_level: i16,
78}
79
80/// Architecture-neutral Event Manager state. It owns the OS event queue and the
81/// menu-bar-invalid bit consumed during Toolbox event scans.
82#[derive(Clone, Debug, Default, PartialEq, Eq)]
83pub struct EventQueue {
84    events: VecDeque<QueuedEvent>,
85    menu_bar_invalid: bool,
86}
87
88impl EventQueue {
89    #[allow(dead_code)]
90    pub fn new() -> Self {
91        Self::default()
92    }
93
94    pub(crate) fn is_pristine(&self) -> bool {
95        self.events.is_empty() && !self.menu_bar_invalid
96    }
97
98    /// Mark the menu bar for one deferred redraw by the Toolbox Event
99    /// Manager. Repeated invalidations coalesce until the next event scan.
100    /// Macintosh Toolbox Essentials (1992), pp. 3-93 and 3-114.
101    pub fn invalidate_menu_bar(&mut self) {
102        self.menu_bar_invalid = true;
103    }
104
105    /// Consume the deferred menu-bar redraw request at an event scan.
106    pub fn take_menu_bar_invalidation(&mut self) -> bool {
107        std::mem::take(&mut self.menu_bar_invalid)
108    }
109
110    #[cfg(test)]
111    pub fn menu_bar_is_invalid(&self) -> bool {
112        self.menu_bar_invalid
113    }
114
115    /// Merge another detached queue into this one by preserving all existing events,
116    /// appending the other queue's events, and OR-ing their menu bar invalidation flags.
117    pub fn merge(&mut self, mut other: Self) {
118        if other.take_menu_bar_invalidation() {
119            self.invalidate_menu_bar();
120        }
121        self.events.append(&mut other.events);
122    }
123}
124
125impl Deref for EventQueue {
126    type Target = VecDeque<QueuedEvent>;
127
128    fn deref(&self) -> &Self::Target {
129        &self.events
130    }
131}
132
133impl DerefMut for EventQueue {
134    fn deref_mut(&mut self) -> &mut Self::Target {
135        &mut self.events
136    }
137}
138
139impl FromIterator<QueuedEvent> for EventQueue {
140    fn from_iter<T: IntoIterator<Item = QueuedEvent>>(iter: T) -> Self {
141        Self {
142            events: iter.into_iter().collect(),
143            menu_bar_invalid: false,
144        }
145    }
146}
147
148#[cfg(test)]
149mod tests {
150    use super::*;
151
152    #[test]
153    fn clone_is_a_detached_snapshot() {
154        let mut live = EventQueue::default();
155        live.push_back(QueuedEvent {
156            what: 3,
157            message: 0x1122_3344,
158            when: 0,
159            where_v: 10,
160            where_h: 20,
161            modifiers: 0x0100,
162        });
163
164        let mut snapshot = live.clone();
165        snapshot.front_mut().unwrap().message = 0x5566_7788;
166        snapshot.invalidate_menu_bar();
167
168        assert_eq!(live.front().unwrap().message, 0x1122_3344);
169        assert_eq!(snapshot.front().unwrap().message, 0x5566_7788);
170        assert!(!live.menu_bar_is_invalid());
171        assert!(snapshot.menu_bar_is_invalid());
172    }
173
174    #[test]
175    fn repeated_menu_bar_invalidations_coalesce_until_consumed() {
176        let mut queue = EventQueue::default();
177
178        queue.invalidate_menu_bar();
179        queue.invalidate_menu_bar();
180
181        assert!(queue.take_menu_bar_invalidation());
182        assert!(!queue.take_menu_bar_invalidation());
183    }
184
185    #[test]
186    fn queue_ordering_and_mutations_are_preserved() {
187        let mut queue = EventQueue::default();
188        queue.push_back(QueuedEvent {
189            what: 1,
190            message: 0x100,
191            when: 0,
192            where_v: 1,
193            where_h: 2,
194            modifiers: 0,
195        });
196        queue.push_back(QueuedEvent {
197            what: 2,
198            message: 0x200,
199            when: 0,
200            where_v: 3,
201            where_h: 4,
202            modifiers: 0,
203        });
204
205        assert_eq!(queue.len(), 2);
206        assert_eq!(queue.pop_front().map(|e| e.message), Some(0x100));
207        assert_eq!(queue.pop_front().map(|e| e.message), Some(0x200));
208        assert!(queue.is_empty());
209    }
210
211    #[test]
212    fn merge_preserves_order_and_combines_invalidation() {
213        let mut target = EventQueue::default();
214        target.push_back(QueuedEvent {
215            what: 1,
216            message: 0x111,
217            when: 0,
218            where_v: 1,
219            where_h: 2,
220            modifiers: 0,
221        });
222
223        let mut source = EventQueue::default();
224        source.push_back(QueuedEvent {
225            what: 2,
226            message: 0x222,
227            when: 0,
228            where_v: 3,
229            where_h: 4,
230            modifiers: 0,
231        });
232        source.invalidate_menu_bar();
233
234        assert!(!target.menu_bar_is_invalid());
235        assert!(source.menu_bar_is_invalid());
236
237        target.merge(source);
238
239        assert_eq!(target.len(), 2);
240        assert_eq!(target[0].message, 0x111);
241        assert_eq!(target[1].message, 0x222);
242        assert!(target.menu_bar_is_invalid());
243    }
244}