use super::dispatch::trace_input_enabled;
use crate::cpu::{CpuOps, Register};
use crate::memory::{MacMemoryBus, MemoryBus};
use crate::Result;
impl super::TrapDispatcher {
const HIGH_LEVEL_EVENT_MASK: u16 = 0x0400;
const K_HIGH_LEVEL_EVENT: u16 = 23;
const K_CORE_EVENT_CLASS: u32 = 0x61657674; const K_AE_OPEN_APPLICATION: u32 = 0x6F617070; const AUTO_KEY_EVENT: u16 = 5;
const OS_EVENT: u16 = 15;
const MOUSE_MOVED_MESSAGE: u32 = 0xFA00_0000;
const QHDR_HEAD_OFFSET: u32 = 2;
const QHDR_TAIL_OFFSET: u32 = 6;
const QELEM_LINK_OFFSET: u32 = 0;
const QERR: u32 = (-1i32) as u32;
const EVT_NOT_ENB: u32 = 1;
const EVQEL_QTYPE: u16 = 4; const EVQEL_WHAT_OFFSET: u32 = 6;
const EVQEL_MESSAGE_OFFSET: u32 = 8;
const EVQEL_WHEN_OFFSET: u32 = 12;
const EVQEL_WHERE_V_OFFSET: u32 = 16;
const EVQEL_WHERE_H_OFFSET: u32 = 18;
const EVQEL_MODIFIERS_OFFSET: u32 = 20;
pub(crate) fn event_matches_mask(event_mask: u16, what: u16) -> bool {
match what {
Self::K_HIGH_LEVEL_EVENT => (event_mask & Self::HIGH_LEVEL_EVENT_MASK) != 0,
0..=15 => {
let bit = 1u16 << what;
(event_mask & bit) != 0
}
_ => false,
}
}
fn toolbox_event_priority(what: u16) -> u8 {
match what {
8 => 0,
1..=4 | 7 => 1,
5 => 2,
6 => 3,
15 => 4,
Self::K_HIGH_LEVEL_EVENT => 5,
_ => 6,
}
}
fn matching_toolbox_event_index(&self, event_mask: u16) -> Option<usize> {
self.event_queue
.iter()
.enumerate()
.filter(|(_, event)| {
event.what != 6 && Self::event_matches_mask(event_mask, event.what)
})
.min_by_key(|(index, event)| (Self::toolbox_event_priority(event.what), *index))
.map(|(index, _)| index)
}
fn preferred_marked_update_event(
&mut self,
bus: &MacMemoryBus,
event_mask: u16,
) -> Option<(super::dispatch::QueuedEvent, bool)> {
if !Self::event_matches_mask(event_mask, 6) {
return None;
}
let known_windows = &self.window_list;
self.flushed_update_events
.retain(|event| event.what == 6 && known_windows.contains(&event.message));
for &window in &self.window_list {
if !self.window_visible(bus, window) || !self.window_has_pending_update(bus, window) {
continue;
}
const WINDOW_PIC_OFFSET: u32 = 148;
let picture_handle = bus.read_long(window + WINDOW_PIC_OFFSET);
if picture_handle != 0 && bus.read_long(picture_handle) != 0 {
continue;
}
if let Some(event) = self
.event_queue
.iter()
.find(|event| event.what == 6 && event.message == window)
{
return Some((event.clone(), false));
}
if let Some(event) = self
.flushed_update_events
.iter()
.find(|event| event.what == 6 && event.message == window)
{
return Some((event.clone(), true));
}
}
self.event_queue
.iter()
.find(|event| event.what == 6 && !self.window_list.contains(&event.message))
.cloned()
.map(|event| (event, false))
}
fn dequeue_preferred_marked_update_event(
&mut self,
bus: &MacMemoryBus,
event_mask: u16,
) -> Option<super::dispatch::QueuedEvent> {
let (event, recovered) = self.preferred_marked_update_event(bus, event_mask)?;
let queue = if recovered {
&mut self.flushed_update_events
} else {
&mut self.event_queue
};
let index = queue
.iter()
.position(|candidate| candidate.what == 6 && candidate.message == event.message)?;
queue.remove(index)
}
pub(crate) fn mouse_moved_event_for_region(
&self,
bus: &MacMemoryBus,
event_mask: u16,
mouse_rgn: u32,
) -> Option<super::dispatch::QueuedEvent> {
if !Self::event_matches_mask(event_mask, Self::OS_EVENT) || mouse_rgn == 0 {
return None;
}
if Self::region_bbox(bus, mouse_rgn).is_none()
|| Self::region_contains_point(bus, mouse_rgn, self.mouse_pos.0, self.mouse_pos.1)
{
return None;
}
Some(super::dispatch::QueuedEvent {
what: Self::OS_EVENT,
message: Self::MOUSE_MOVED_MESSAGE,
where_v: self.mouse_pos.0,
where_h: self.mouse_pos.1,
modifiers: self.current_event_modifiers(),
})
}
pub(crate) fn posted_event_is_enabled(system_event_mask: u16, what: u16) -> bool {
match what {
Self::K_HIGH_LEVEL_EVENT => (system_event_mask & Self::HIGH_LEVEL_EVENT_MASK) != 0,
0..=15 => {
let bit = 1u16 << what;
(system_event_mask & bit) != 0
}
_ => true,
}
}
fn make_posted_event(&self, what: u16, message: u32) -> super::dispatch::QueuedEvent {
super::dispatch::QueuedEvent {
what,
message,
where_v: self.mouse_pos.0,
where_h: self.mouse_pos.1,
modifiers: self.current_event_modifiers(),
}
}
fn alloc_evqel_snapshot(
&self,
bus: &mut MacMemoryBus,
event: &super::dispatch::QueuedEvent,
) -> u32 {
let ptr = bus.alloc(22);
if ptr == 0 {
return 0;
}
bus.write_long(ptr + Self::QELEM_LINK_OFFSET, 0);
bus.write_word(ptr + 4, Self::EVQEL_QTYPE);
bus.write_word(ptr + Self::EVQEL_WHAT_OFFSET, event.what);
bus.write_long(ptr + Self::EVQEL_MESSAGE_OFFSET, event.message);
bus.write_long(ptr + Self::EVQEL_WHEN_OFFSET, self.tick_count);
bus.write_word(ptr + Self::EVQEL_WHERE_V_OFFSET, event.where_v as u16);
bus.write_word(ptr + Self::EVQEL_WHERE_H_OFFSET, event.where_h as u16);
bus.write_word(ptr + Self::EVQEL_MODIFIERS_OFFSET, event.modifiers);
ptr
}
fn post_os_event(&mut self, bus: &mut MacMemoryBus, what: u16, message: u32) -> (u32, u32) {
if !Self::posted_event_is_enabled(
bus.read_word(crate::memory::globals::addr::SYS_EVT_MASK),
what,
) {
return (Self::EVT_NOT_ENB, 0);
}
let event = self.make_posted_event(what, message);
let qel_ptr = if (self.current_trap_word & 0x0100) != 0 {
self.alloc_evqel_snapshot(bus, &event)
} else {
0
};
self.event_queue.push_back(event);
(0, qel_ptr)
}
fn flush_events_with_masks(&mut self, event_mask: u16, stop_mask: u16) -> u32 {
let mut result = 0;
let mut stopped = false;
let mut remaining = std::collections::VecDeque::with_capacity(self.event_queue.len());
while let Some(event) = self.event_queue.pop_front() {
if !stopped && stop_mask != 0 && Self::event_matches_mask(stop_mask, event.what) {
result = event.what as u32;
stopped = true;
remaining.push_back(event);
continue;
}
if !stopped && Self::event_matches_mask(event_mask, event.what) {
if event.what == 6 {
self.remember_flushed_update_event(&event);
}
continue;
}
remaining.push_back(event);
}
*self.event_queue = remaining;
result
}
fn remember_flushed_update_event(&mut self, event: &super::dispatch::QueuedEvent) {
if event.what != 6 || event.message == 0 {
return;
}
if self
.flushed_update_events
.iter()
.any(|queued| queued.what == 6 && queued.message == event.message)
{
return;
}
if std::env::var_os("SYSTEMLESS_TRACE_INVAL").is_some() {
eprintln!(
"[INVAL] remember_flushed_update_event window=${:08X} tick={}",
event.message, self.tick_count
);
}
self.flushed_update_events.push_back(event.clone());
}
fn enqueue_open_application_event_if_needed(&mut self, event_mask: u16) {
if !self.application_high_level_event_aware
|| self.sent_open_app_event
|| (event_mask & Self::HIGH_LEVEL_EVENT_MASK) == 0
{
return;
}
self.sent_open_app_event = true;
self.event_queue.push_front(super::dispatch::QueuedEvent {
what: Self::K_HIGH_LEVEL_EVENT,
message: Self::K_CORE_EVENT_CLASS,
where_v: (Self::K_AE_OPEN_APPLICATION >> 16) as i16,
where_h: (Self::K_AE_OPEN_APPLICATION & 0xFFFF) as i16,
modifiers: 0,
});
}
fn tick_has_reached(now: u32, due: u32) -> bool {
now.wrapping_sub(due) < 0x8000_0000
}
pub(crate) fn post_auto_key_if_due(&mut self, system_event_mask: u16) {
if !Self::posted_event_is_enabled(system_event_mask, Self::AUTO_KEY_EVENT) {
return;
}
let Some(repeat) = self.key_repeat else {
return;
};
if !Self::key_generates_auto_key(repeat.key_code) || !self.key_is_down(repeat.key_code) {
self.key_repeat = None;
return;
}
if !Self::tick_has_reached(self.tick_count, repeat.next_tick) {
return;
}
if let Some(state) = self.key_repeat.as_mut() {
state.next_tick = self.tick_count.wrapping_add(Self::AUTO_KEY_RATE_TICKS);
}
let message = ((repeat.key_code as u32) << 8) | (repeat.char_code as u32);
let modifiers = self.current_event_modifiers();
self.event_queue.push_back(super::dispatch::QueuedEvent {
what: Self::AUTO_KEY_EVENT,
message,
where_v: self.mouse_pos.0,
where_h: self.mouse_pos.1,
modifiers,
});
}
fn enqueue_auto_key_if_due(&mut self, system_event_mask: u16, event_mask: u16) {
if Self::event_matches_mask(event_mask, Self::AUTO_KEY_EVENT) {
self.post_auto_key_if_due(system_event_mask);
}
}
fn peek_pending_native_menu_event(
&self,
event_mask: u16,
) -> Option<super::dispatch::QueuedEvent> {
if self.pending_native_menu_event_tick == Some(self.tick_count) {
return None;
}
self.pending_native_menu_event
.as_ref()
.filter(|event| Self::event_matches_mask(event_mask, event.what))
.cloned()
}
fn pending_native_menu_wins_fifo_tie(&self) -> bool {
self.pending_native_menu_event_tick.is_some()
}
fn dequeue_pending_native_menu_event(
&mut self,
event_mask: u16,
) -> Option<super::dispatch::QueuedEvent> {
let event = self.peek_pending_native_menu_event(event_mask)?;
self.pending_native_menu_event_tick = Some(self.tick_count);
if trace_input_enabled() || super::dispatch::trace_delivered_events_enabled() {
eprintln!(
"[INPUT] present latched native-menu mouseDown where=({}, {}) mask=${:04X} tick={}",
event.where_v, event.where_h, event_mask, self.tick_count
);
}
Some(event)
}
pub(crate) fn has_pending_native_menu_event(&self) -> bool {
self.pending_native_menu_event.is_some()
}
pub(crate) fn peek_toolbox_event(
&mut self,
bus: &MacMemoryBus,
event_mask: u16,
) -> Option<super::dispatch::QueuedEvent> {
self.enqueue_open_application_event_if_needed(event_mask);
self.enqueue_auto_key_if_due(
bus.read_word(crate::memory::globals::addr::SYS_EVT_MASK),
event_mask,
);
let pending_menu = self.peek_pending_native_menu_event(event_mask);
let queued = self
.matching_toolbox_event_index(event_mask)
.and_then(|index| self.event_queue.get(index))
.cloned();
let update = self
.preferred_marked_update_event(bus, event_mask)
.map(|(event, _)| event);
let queued_or_menu = match (pending_menu, queued) {
(Some(pending), Some(queued)) => Some(
if Self::toolbox_event_priority(pending.what)
< Self::toolbox_event_priority(queued.what)
|| (Self::toolbox_event_priority(pending.what)
== Self::toolbox_event_priority(queued.what)
&& self.pending_native_menu_wins_fifo_tie())
{
pending
} else {
queued
},
),
(pending, queued) => pending.or(queued),
};
match (queued_or_menu, update) {
(Some(queued), Some(update)) => Some(
if Self::toolbox_event_priority(queued.what)
<= Self::toolbox_event_priority(update.what)
{
queued
} else {
update
},
),
(queued, update) => queued.or(update),
}
}
fn peek_event(
&mut self,
bus: &MacMemoryBus,
event_mask: u16,
) -> Option<super::dispatch::QueuedEvent> {
self.enqueue_auto_key_if_due(
bus.read_word(crate::memory::globals::addr::SYS_EVT_MASK),
event_mask,
);
let pending = self.peek_pending_native_menu_event(event_mask);
let queued = self
.event_queue
.iter()
.find(|event| {
Self::is_low_level_os_event(event.what)
&& Self::event_matches_mask(event_mask, event.what)
})
.cloned();
match (pending, queued) {
(Some(_), Some(queued)) if !self.pending_native_menu_wins_fifo_tie() => Some(queued),
(pending, queued) => pending.or(queued),
}
}
pub(crate) fn dequeue_toolbox_event<C: CpuOps>(
&mut self,
cpu: &mut C,
bus: &mut MacMemoryBus,
event_mask: u16,
) -> (u16, u32, i16, i16, u16, bool) {
self.enqueue_open_application_event_if_needed(event_mask);
self.enqueue_auto_key_if_due(
bus.read_word(crate::memory::globals::addr::SYS_EVT_MASK),
event_mask,
);
if Self::event_matches_mask(event_mask, 6) {
self.service_window_picture_updates(cpu, bus);
}
let pending_menu = self.peek_pending_native_menu_event(event_mask);
let first_idx = self.matching_toolbox_event_index(event_mask);
let preferred_update = self
.preferred_marked_update_event(bus, event_mask)
.map(|(event, _)| event);
let pending_has_priority = pending_menu.as_ref().is_some_and(|pending| {
let precedes_queued = first_idx
.and_then(|index| self.event_queue.get(index))
.is_none_or(|queued| {
Self::toolbox_event_priority(pending.what)
< Self::toolbox_event_priority(queued.what)
|| (Self::toolbox_event_priority(pending.what)
== Self::toolbox_event_priority(queued.what)
&& self.pending_native_menu_wins_fifo_tie())
});
let precedes_update = preferred_update.as_ref().is_none_or(|update| {
Self::toolbox_event_priority(pending.what)
<= Self::toolbox_event_priority(update.what)
});
precedes_queued && precedes_update
});
if pending_has_priority {
let event = self.dequeue_pending_native_menu_event(event_mask).unwrap();
return (
event.what,
event.message,
event.where_v,
event.where_h,
event.modifiers,
true,
);
}
let update_has_priority = preferred_update.as_ref().is_some_and(|update| {
first_idx
.and_then(|index| self.event_queue.get(index))
.is_none_or(|queued| {
Self::toolbox_event_priority(update.what)
< Self::toolbox_event_priority(queued.what)
})
});
if update_has_priority {
let event = self
.dequeue_preferred_marked_update_event(bus, event_mask)
.expect("selected update must remain available");
if trace_input_enabled() || super::dispatch::trace_delivered_events_enabled() {
eprintln!(
"[INPUT] dequeue update what={} message=${:08X} mask=${:04X}",
event.what, event.message, event_mask
);
}
return (
event.what,
event.message,
event.where_v,
event.where_h,
event.modifiers,
true,
);
}
if let Some(first_idx) = first_idx {
let idx = first_idx;
let event = self.event_queue[idx].clone();
if self.consume_retained_modal_dialog_event(cpu, bus, &event) {
self.event_queue.remove(idx);
self.acknowledge_window_activation_event(bus, &event);
if trace_input_enabled() || super::dispatch::trace_delivered_events_enabled() {
eprintln!(
"[INPUT] consumed retained-modal what={} where=({}, {}) mask=${:04X}",
event.what, event.where_v, event.where_h, event_mask
);
}
return (
0,
0,
self.mouse_pos.0,
self.mouse_pos.1,
self.current_event_modifiers(),
false,
);
}
let event = self.event_queue.remove(idx).unwrap();
self.acknowledge_window_activation_event(bus, &event);
if trace_input_enabled() || super::dispatch::trace_delivered_events_enabled() {
eprintln!(
"[INPUT] dequeue what={} message=${:08X} where=({}, {}) mask=${:04X}",
event.what, event.message, event.where_v, event.where_h, event_mask
);
}
if event.what == 2 {
self.mouse_button = false;
}
self.begin_app_owned_modal_dialog_button_tracking(bus, &event);
if matches!(event.what, 3 | 4 | 5) {
self.debug_key_event_delivery_count =
self.debug_key_event_delivery_count.saturating_add(1);
self.debug_last_key_event_message = event.message;
}
return (
event.what,
event.message,
event.where_v,
event.where_h,
event.modifiers,
true,
);
}
if let Some(event) = self.dequeue_preferred_marked_update_event(bus, event_mask) {
if trace_input_enabled() || super::dispatch::trace_delivered_events_enabled() {
eprintln!(
"[INPUT] dequeue update what={} message=${:08X} mask=${:04X}",
event.what, event.message, event_mask
);
}
return (
event.what,
event.message,
event.where_v,
event.where_h,
event.modifiers,
true,
);
}
(
0,
0,
self.mouse_pos.0,
self.mouse_pos.1,
self.current_event_modifiers(),
false,
)
}
pub(crate) fn write_event_record(
&self,
bus: &mut MacMemoryBus,
event_ptr: u32,
what: u16,
message: u32,
where_v: i16,
where_h: i16,
modifiers: u16,
) {
let when = self.tick_count;
let rec: [u8; 16] = [
(what >> 8) as u8,
what as u8,
(message >> 24) as u8,
(message >> 16) as u8,
(message >> 8) as u8,
message as u8,
(when >> 24) as u8,
(when >> 16) as u8,
(when >> 8) as u8,
when as u8,
((where_v as u16) >> 8) as u8,
(where_v as u16) as u8,
((where_h as u16) >> 8) as u8,
(where_h as u16) as u8,
(modifiers >> 8) as u8,
modifiers as u8,
];
bus.write_bytes(event_ptr, &rec);
let mb_state: u8 = if self.mouse_button { 0x00 } else { 0x80 };
bus.write_byte(0x0172, mb_state);
let v = self.mouse_pos.0 as u16;
let h = self.mouse_pos.1 as u16;
let mouse_globals: [u8; 12] = [
(v >> 8) as u8,
v as u8,
(h >> 8) as u8,
h as u8,
(v >> 8) as u8,
v as u8,
(h >> 8) as u8,
h as u8,
(v >> 8) as u8,
v as u8,
(h >> 8) as u8,
h as u8,
];
bus.write_bytes(0x0828, &mouse_globals);
}
pub(crate) fn dequeue_event(
&mut self,
bus: &MacMemoryBus,
event_mask: u16,
) -> (u16, u32, i16, i16, u16, bool) {
self.enqueue_auto_key_if_due(
bus.read_word(crate::memory::globals::addr::SYS_EVT_MASK),
event_mask,
);
let queued_idx = self.event_queue.iter().position(|event| {
Self::is_low_level_os_event(event.what)
&& Self::event_matches_mask(event_mask, event.what)
});
if queued_idx.is_none() || self.pending_native_menu_wins_fifo_tie() {
if let Some(event) = self.dequeue_pending_native_menu_event(event_mask) {
return (
event.what,
event.message,
event.where_v,
event.where_h,
event.modifiers,
true,
);
}
}
if let Some(idx) = queued_idx {
let ev = self.event_queue.remove(idx).unwrap();
if trace_input_enabled() || super::dispatch::trace_delivered_events_enabled() {
eprintln!(
"[INPUT] dequeue what={} message=${:08X} where=({}, {}) mask=${:04X}",
ev.what, ev.message, ev.where_v, ev.where_h, event_mask
);
}
if ev.what == 2 {
self.mouse_button = false;
}
(
ev.what,
ev.message,
ev.where_v,
ev.where_h,
ev.modifiers,
true,
)
} else {
(
0,
0,
self.mouse_pos.0,
self.mouse_pos.1,
self.current_event_modifiers(),
false,
)
}
}
fn is_low_level_os_event(what: u16) -> bool {
matches!(what, 1..=5 | 7)
}
fn enqueue_qelem(&self, bus: &mut MacMemoryBus, q_entry: u32, q_header: u32) {
let head = bus.read_long(q_header + Self::QHDR_HEAD_OFFSET);
let tail = bus.read_long(q_header + Self::QHDR_TAIL_OFFSET);
bus.write_long(q_entry + Self::QELEM_LINK_OFFSET, 0);
if head == 0 {
bus.write_long(q_header + Self::QHDR_HEAD_OFFSET, q_entry);
bus.write_long(q_header + Self::QHDR_TAIL_OFFSET, q_entry);
return;
}
let current_tail = if tail != 0 {
tail
} else {
let mut cursor = head;
loop {
let next = bus.read_long(cursor + Self::QELEM_LINK_OFFSET);
if next == 0 {
break cursor;
}
cursor = next;
}
};
bus.write_long(current_tail + Self::QELEM_LINK_OFFSET, q_entry);
bus.write_long(q_header + Self::QHDR_TAIL_OFFSET, q_entry);
}
fn dequeue_qelem(&self, bus: &mut MacMemoryBus, q_entry: u32, q_header: u32) -> u32 {
let mut prev = 0;
let mut current = bus.read_long(q_header + Self::QHDR_HEAD_OFFSET);
while current != 0 {
let next = bus.read_long(current + Self::QELEM_LINK_OFFSET);
if current == q_entry {
if prev == 0 {
bus.write_long(q_header + Self::QHDR_HEAD_OFFSET, next);
} else {
bus.write_long(prev + Self::QELEM_LINK_OFFSET, next);
}
if bus.read_long(q_header + Self::QHDR_TAIL_OFFSET) == current {
bus.write_long(q_header + Self::QHDR_TAIL_OFFSET, prev);
}
return 0;
}
prev = current;
current = next;
}
Self::QERR
}
pub(crate) fn dispatch_event<C: CpuOps>(
&mut self,
is_tool: bool,
trap_num: u16,
cpu: &mut C,
bus: &mut MacMemoryBus,
) -> Option<Result<()>> {
Some(match (is_tool, trap_num) {
(false, 0x32) => {
let masks = cpu.read_reg(Register::D0);
let event_mask = masks as u16;
let stop_mask = (masks >> 16) as u16;
let queue_len_before = self.event_queue.len();
let result = self.flush_events_with_masks(event_mask, stop_mask);
if trace_input_enabled() {
eprintln!(
"[INPUT] FlushEvents event_mask=${:04X} stop_mask=${:04X} -> result={} queue_len {}->{}",
event_mask,
stop_mask,
result,
queue_len_before,
self.event_queue.len()
);
}
cpu.write_reg(Register::D0, result);
Ok(())
}
(false, 0x70) => {
let event_mask = cpu.read_reg(Register::D0) as u16;
let event_ptr = cpu.read_reg(Register::A0);
let (what, message, where_v, where_h, modifiers, has_event) =
self.dequeue_toolbox_event(cpu, bus, event_mask);
self.write_event_record(bus, event_ptr, what, message, where_v, where_h, modifiers);
cpu.write_reg(Register::D0, if has_event { 0xFFFF } else { 0 });
Ok(())
}
(false, 0x71) => {
let slot = cpu.read_reg(Register::D0) as u16 as i16;
if !(0..=15).contains(&slot) {
cpu.write_reg(Register::D0, (-337i32) as u32);
return Some(Ok(()));
}
self.primary_vbl_slot = slot;
cpu.write_reg(Register::D0, 0);
Ok(())
}
(false, 0x2F) => {
let event_code = cpu.read_reg(Register::A0) as u16;
let event_msg = cpu.read_reg(Register::D0);
let (result, qel_ptr) = self.post_os_event(bus, event_code, event_msg);
cpu.write_reg(Register::D0, result);
if (self.current_trap_word & 0x0100) != 0 {
cpu.write_reg(Register::A0, qel_ptr);
}
Ok(())
}
(false, 0x6D) => {
cpu.write_reg(Register::D0, 0);
Ok(())
}
(true, 0x16F) => {
let q_entry = cpu.read_reg(Register::A0);
let q_header = cpu.read_reg(Register::A1);
self.enqueue_qelem(bus, q_entry, q_header);
Ok(())
}
(true, 0x16E) => {
let q_entry = cpu.read_reg(Register::A0);
let q_header = cpu.read_reg(Register::A1);
cpu.write_reg(Register::D0, self.dequeue_qelem(bus, q_entry, q_header));
Ok(())
}
(false, 0x3D) => {
cpu.write_reg(Register::D0, 0);
Ok(())
}
(false, 0x3E) => {
cpu.write_reg(Register::D0, 0);
Ok(())
}
(false, 0x4F) => {
cpu.write_reg(Register::D0, 0);
Ok(())
}
(false, 0x72) => {
let slot = cpu.read_reg(Register::D0) as u16 as i16;
if !(0..=15).contains(&slot) {
cpu.write_reg(Register::D0, (-337i32) as u32);
return Some(Ok(()));
}
cpu.write_reg(Register::D0, 0);
Ok(())
}
(false, 0x75) => {
cpu.write_reg(Register::D0, 0);
Ok(())
}
(false, 0x76) => {
cpu.write_reg(Register::D0, 0);
Ok(())
}
(false, 0x30) => {
let event_mask = cpu.read_reg(Register::D0) as u16;
let event_ptr = cpu.read_reg(Register::A0);
if let Some(event) = self.peek_event(bus, event_mask) {
self.write_event_record(
bus,
event_ptr,
event.what,
event.message,
event.where_v,
event.where_h,
event.modifiers,
);
cpu.write_reg(Register::D0, 0);
} else {
self.write_event_record(
bus,
event_ptr,
0,
0,
self.mouse_pos.0,
self.mouse_pos.1,
self.current_event_modifiers(),
);
cpu.write_reg(Register::D0, 0xFFFF);
}
Ok(())
}
(false, 0x31) => {
let event_mask = cpu.read_reg(Register::D0) as u16;
let event_ptr = cpu.read_reg(Register::A0);
let (what, message, where_v, where_h, modifiers, has_event) =
self.dequeue_event(bus, event_mask);
self.write_event_record(bus, event_ptr, what, message, where_v, where_h, modifiers);
cpu.write_reg(Register::D0, if has_event { 0 } else { 0xFFFF });
Ok(())
}
_ => return None,
})
}
}
#[cfg(test)]
mod tests {
use super::super::dispatch::{QueuedEvent, TrapDispatcher};
use super::super::test_helpers::setup;
use crate::cpu::{CpuOps, Register};
use crate::memory::MemoryBus;
#[test]
fn native_menu_mouse_down_is_latched_and_rate_limited_until_menuselect() {
let (mut disp, _cpu, bus) = setup();
disp.tick_count = 100;
disp.pending_native_menu_event = Some(QueuedEvent {
what: 1,
message: 0,
where_v: 10,
where_h: 42,
modifiers: 0,
});
let first = disp.dequeue_event(&bus, 1 << 1);
assert!(first.5);
assert_eq!((first.0, first.2, first.3), (1, 10, 42));
assert!(disp.pending_native_menu_event.is_some());
let same_tick = disp.dequeue_event(&bus, 1 << 1);
assert!(!same_tick.5, "latched click must not spin an event loop");
disp.tick_count += 1;
let next_tick = disp.dequeue_event(&bus, 1 << 1);
assert!(next_tick.5, "ignored menu click must be presented again");
assert!(disp.pending_native_menu_event.is_some());
}
#[test]
fn native_menu_mouse_down_follows_older_low_level_events() {
let (mut disp, mut cpu, mut bus) = setup();
disp.tick_count = 100;
disp.event_queue.push_back(QueuedEvent {
what: 3,
message: 0x1234,
where_v: 20,
where_h: 30,
modifiers: 0,
});
disp.pending_native_menu_event = Some(QueuedEvent {
what: 1,
message: 0,
where_v: 10,
where_h: 42,
modifiers: 0,
});
assert_eq!(disp.peek_event(&bus, u16::MAX).unwrap().what, 3);
let event = disp.dequeue_toolbox_event(&mut cpu, &mut bus, u16::MAX);
assert_eq!((event.0, event.1), (3, 0x1234));
let event = disp.dequeue_event(&bus, u16::MAX);
assert_eq!((event.0, event.2, event.3), (1, 10, 42));
disp.tick_count += 1;
disp.event_queue.push_back(QueuedEvent {
what: 4,
message: 0x5678,
where_v: 20,
where_h: 30,
modifiers: 0,
});
assert_eq!(disp.peek_event(&bus, u16::MAX).unwrap().what, 1);
assert_eq!(disp.dequeue_event(&bus, u16::MAX).0, 1);
}
#[test]
fn flush_events_clears_queue_and_sets_d0_zero() {
let (mut disp, mut cpu, mut bus) = setup();
disp.event_queue.push_back(QueuedEvent {
what: 1,
message: 0,
where_v: 10,
where_h: 20,
modifiers: 0,
});
disp.event_queue.push_back(QueuedEvent {
what: 3,
message: 42,
where_v: 30,
where_h: 40,
modifiers: 0,
});
assert_eq!(disp.event_queue.len(), 2);
cpu.write_reg(Register::D0, 0xFFFFu32);
let result = disp.dispatch_event(false, 0x32, &mut cpu, &mut bus);
assert!(result.unwrap().is_ok());
assert!(disp.event_queue.is_empty());
assert_eq!(cpu.read_reg(Register::D0), 0);
}
#[test]
fn initevents_returns_noerr_and_preserves_stack_pointer() {
let (mut disp, mut cpu, mut bus) = setup();
let stack_ptr = 0x00F0_3000;
cpu.write_reg(Register::D0, 0xFFFF_FFEC);
cpu.write_reg(Register::A7, stack_ptr);
let result = disp.dispatch_event(false, 0x6D, &mut cpu, &mut bus);
assert!(result.is_some(), "InitEvents should be handled");
assert!(result.unwrap().is_ok(), "InitEvents should return normally");
assert_eq!(
cpu.read_reg(Register::D0),
0,
"InitEvents should return noErr in D0"
);
assert_eq!(
cpu.read_reg(Register::A7),
stack_ptr,
"InitEvents should preserve A7"
);
}
#[test]
fn held_key_generates_autokey_after_default_threshold() {
let (mut disp, mut cpu, mut bus) = setup();
disp.sent_open_app_event = true;
disp.push_key_down(0x7D, 31); let (what, message, _, _, _, has_event) =
disp.dequeue_toolbox_event(&mut cpu, &mut bus, 0x0008);
assert!(has_event, "initial keyDown should be delivered");
assert_eq!(what, 3);
assert_eq!(message, 0x0000_7D1F);
let first_repeat_tick = disp.tick_count + TrapDispatcher::AUTO_KEY_THRESHOLD_TICKS;
disp.tick_count = first_repeat_tick - 1;
let (_, _, _, _, _, has_event) = disp.dequeue_toolbox_event(&mut cpu, &mut bus, 0x0020);
assert!(
!has_event,
"autoKey should wait for the 16-tick default threshold"
);
disp.tick_count = first_repeat_tick;
let (what, message, _, _, _, has_event) =
disp.dequeue_toolbox_event(&mut cpu, &mut bus, 0x0020);
assert!(has_event, "held key should generate autoKey at threshold");
assert_eq!(what, 5);
assert_eq!(message, 0x0000_7D1F);
let second_repeat_tick = disp.tick_count + TrapDispatcher::AUTO_KEY_RATE_TICKS;
disp.tick_count = second_repeat_tick - 1;
let (_, _, _, _, _, has_event) = disp.dequeue_toolbox_event(&mut cpu, &mut bus, 0x0020);
assert!(
!has_event,
"autoKey should wait for the 4-tick default repeat rate"
);
disp.tick_count = second_repeat_tick;
let (what, message, _, _, _, has_event) =
disp.dequeue_toolbox_event(&mut cpu, &mut bus, 0x0020);
assert!(has_event, "held key should repeat after the rate interval");
assert_eq!(what, 5);
assert_eq!(message, 0x0000_7D1F);
bus.write_word(
crate::memory::globals::addr::SYS_EVT_MASK,
bus.read_word(crate::memory::globals::addr::SYS_EVT_MASK) | (1 << 4),
);
disp.push_key_up_with_system_event_mask(
bus.read_word(crate::memory::globals::addr::SYS_EVT_MASK),
0x7D,
31,
);
let (what, _, _, _, _, has_event) = disp.dequeue_toolbox_event(&mut cpu, &mut bus, 0x0010);
assert!(has_event, "keyUp should be delivered");
assert_eq!(what, 4);
disp.tick_count = 100;
let (_, _, _, _, _, has_event) = disp.dequeue_toolbox_event(&mut cpu, &mut bus, 0x0020);
assert!(!has_event, "released key should not keep auto-keying");
}
#[test]
fn autokey_is_posted_when_ticks_advance_before_the_next_poll() {
let (mut disp, mut cpu, mut bus) = setup();
disp.sent_open_app_event = true;
disp.push_key_down(0x00, b'a');
let (_, _, _, _, _, has_event) = disp.dequeue_toolbox_event(&mut cpu, &mut bus, 0x0008);
assert!(has_event, "initial keyDown should be delivered");
disp.tick_count += TrapDispatcher::AUTO_KEY_THRESHOLD_TICKS;
disp.post_auto_key_if_due(bus.read_word(crate::memory::globals::addr::SYS_EVT_MASK));
disp.push_key_up(0x00, b'a');
let (what, message, _, _, _, has_event) =
disp.dequeue_toolbox_event(&mut cpu, &mut bus, 0x0020);
assert!(
has_event,
"elapsed autoKey must survive a later key release"
);
assert_eq!(what, 5);
assert_eq!(message, 0x0000_0061);
}
#[test]
fn default_system_event_mask_suppresses_keyup_but_clears_keymap() {
let (mut disp, mut cpu, mut bus) = setup();
disp.sent_open_app_event = true;
disp.push_key_down(0x24, 13);
let (_, _, _, _, _, has_event) = disp.dequeue_toolbox_event(&mut cpu, &mut bus, 0x0008);
assert!(
has_event,
"keyDown should use the default system event mask"
);
assert!(disp.key_is_down(0x24));
disp.push_key_up(0x24, 13);
assert!(!disp.key_is_down(0x24), "keyUp must clear physical state");
let (_, _, _, _, _, has_event) = disp.dequeue_toolbox_event(&mut cpu, &mut bus, 0x0010);
assert!(
!has_event,
"the default SysEvtMask must not post keyUp events"
);
}
#[test]
fn keys_use_classic_keymap_bit_order() {
let (mut disp, _, _) = setup();
for (key_code, expected_byte_index, expected_mask) in [
(0x00, 0, 0x01), (0x31, 6, 0x02), (0x37, 6, 0x80), (0x7B, 15, 0x08), (0x7C, 15, 0x10), (0x7D, 15, 0x20), (0x7E, 15, 0x40), ] {
disp.push_key_down(key_code, 0);
assert_eq!(
disp.key_map_bytes()[expected_byte_index],
expected_mask,
"wrong KeyMap bit for key code {key_code:#04X}"
);
disp.push_key_up(key_code, 0);
assert_eq!(disp.key_map_bytes()[expected_byte_index], 0);
}
}
#[test]
fn repeated_host_keydown_does_not_duplicate_keydown_or_restart_autokey() {
let (mut disp, mut cpu, mut bus) = setup();
disp.sent_open_app_event = true;
disp.push_key_down(0x30, 9); let first_repeat_tick = disp.key_repeat.expect("Tab should arm autoKey").next_tick;
disp.tick_count = disp.tick_count.wrapping_add(5);
disp.push_key_down(0x30, 9);
assert_eq!(disp.event_queue.len(), 1, "only one keyDown may be queued");
assert_eq!(
disp.key_repeat
.expect("autoKey should remain armed")
.next_tick,
first_repeat_tick,
"a repeated host callback must not postpone autoKey"
);
let (what, message, _, _, _, has_event) =
disp.dequeue_toolbox_event(&mut cpu, &mut bus, 0x0008);
assert!(has_event);
assert_eq!((what, message), (3, 0x0000_3009));
}
#[test]
fn event_avail_peeks_autokey_without_duplicating_it() {
let (mut disp, mut cpu, mut bus) = setup();
disp.sent_open_app_event = true;
disp.push_key_down(0x7D, 31);
let (_, _, _, _, _, has_event) = disp.dequeue_toolbox_event(&mut cpu, &mut bus, 0x0008);
assert!(has_event, "initial keyDown should be drained");
disp.tick_count += TrapDispatcher::AUTO_KEY_THRESHOLD_TICKS;
let first = disp
.peek_toolbox_event(&bus, 0x0020)
.expect("EventAvail should see due autoKey");
assert_eq!(first.what, 5);
assert_eq!(first.message, 0x0000_7D1F);
assert_eq!(disp.event_queue.len(), 1);
let second = disp
.peek_toolbox_event(&bus, 0x0020)
.expect("second EventAvail should see same autoKey");
assert_eq!(second.what, 5);
assert_eq!(second.message, 0x0000_7D1F);
assert_eq!(
disp.event_queue.len(),
1,
"peek should not duplicate autoKey"
);
}
#[test]
fn modifier_keys_update_keymap_without_generating_keyboard_events() {
let (mut disp, mut cpu, mut bus) = setup();
disp.sent_open_app_event = true;
disp.push_key_down(0x38, 0); let (_, _, _, _, _, has_event) = disp.dequeue_toolbox_event(&mut cpu, &mut bus, 0x0008);
assert!(disp.key_is_down(0x38), "Shift must update the KeyMap");
assert!(
!has_event,
"modifier keys must not generate standalone keyDown events"
);
disp.push_key_up(0x38, 0);
assert!(
!disp.key_is_down(0x38),
"Shift release must clear the KeyMap"
);
let (_, _, _, _, _, has_event) = disp.dequeue_toolbox_event(&mut cpu, &mut bus, 0x0010);
assert!(
!has_event,
"modifier keys must not generate standalone keyUp events"
);
}
#[test]
fn caps_lock_latches_keymap_and_alpha_lock_until_second_press() {
let (mut disp, mut cpu, mut bus) = setup();
disp.sent_open_app_event = true;
disp.push_key_down(0x39, 0);
assert!(disp.key_is_down(0x39), "first press should latch Caps Lock");
assert_eq!(disp.current_event_modifiers() & 0x0400, 0x0400);
assert!(
!disp.dequeue_toolbox_event(&mut cpu, &mut bus, 0x0008).5,
"Caps Lock must not post a standalone keyDown event"
);
disp.push_key_down(0x39, 0);
assert!(
disp.key_is_down(0x39),
"a repeated host keydown while physically held must not toggle the latch"
);
disp.push_key_up(0x39, 0);
assert!(
disp.key_is_down(0x39),
"physical release must preserve the logical Caps Lock latch"
);
assert_eq!(disp.current_event_modifiers() & 0x0400, 0x0400);
disp.push_key_down(0x00, b'a');
let (what, _, _, _, modifiers, has_event) =
disp.dequeue_toolbox_event(&mut cpu, &mut bus, 0x0008);
assert!(has_event);
assert_eq!(what, 3);
assert_eq!(
modifiers & 0x0400,
0x0400,
"character events must carry alphaLock while Caps Lock is latched"
);
disp.push_key_up(0x00, b'a');
disp.push_key_down(0x39, 0);
assert!(
!disp.key_is_down(0x39),
"second physical press should release Caps Lock"
);
assert_eq!(disp.current_event_modifiers() & 0x0400, 0);
disp.push_key_up(0x39, 0);
assert!(
!disp.key_is_down(0x39),
"second physical release must leave Caps Lock clear"
);
disp.push_key_down(0x00, b'a');
let (_, _, _, _, modifiers, has_event) =
disp.dequeue_toolbox_event(&mut cpu, &mut bus, 0x0008);
assert!(has_event);
assert_eq!(
modifiers & 0x0400,
0,
"character events must clear alphaLock after Caps Lock is released"
);
}
#[test]
fn command_modifier_is_reported_on_following_character_event() {
let (mut disp, mut cpu, mut bus) = setup();
disp.sent_open_app_event = true;
disp.push_key_down(0x37, 0); disp.push_key_down(0x01, b's');
let (what, message, _, _, modifiers, has_event) =
disp.dequeue_toolbox_event(&mut cpu, &mut bus, 0x0008);
assert!(has_event);
assert_eq!(what, 3, "only the character key should post keyDown");
assert_eq!(message, 0x0000_0173);
assert_ne!(modifiers & 0x0100, 0, "cmdKey must be set on Cmd-S");
}
#[test]
fn drvrinstall_uses_a0_driverptr_d0_refnum_and_returns_oserr_in_d0() {
let (mut disp, mut cpu, mut bus) = setup();
let driver_ptr = 0x320000;
let stack_ptr = 0x00F0_1000;
cpu.write_reg(Register::A0, driver_ptr);
cpu.write_reg(Register::D0, 0xFFFF_FFEC);
cpu.write_reg(Register::A7, stack_ptr);
let result = disp.dispatch_event(false, 0x3D, &mut cpu, &mut bus);
assert!(result.is_some(), "DrvrInstall should be handled");
assert!(
result.unwrap().is_ok(),
"DrvrInstall should return normally"
);
assert_eq!(
cpu.read_reg(Register::D0),
0,
"DrvrInstall should return noErr in D0 for nominal calls"
);
assert_eq!(
cpu.read_reg(Register::A0),
driver_ptr,
"DrvrInstall should not rewrite the A0 driver pointer"
);
assert_eq!(
cpu.read_reg(Register::A7),
stack_ptr,
"DrvrInstall is register-based and should preserve A7"
);
}
#[test]
fn drvrremove_uses_d0_refnum_and_returns_oserr_in_d0() {
let (mut disp, mut cpu, mut bus) = setup();
let stack_ptr = 0x00F0_2000;
cpu.write_reg(Register::D0, 0xFFFF_FFEC);
cpu.write_reg(Register::A7, stack_ptr);
let result = disp.dispatch_event(false, 0x3E, &mut cpu, &mut bus);
assert!(result.is_some(), "DrvrRemove should be handled");
assert!(result.unwrap().is_ok(), "DrvrRemove should return normally");
assert_eq!(
cpu.read_reg(Register::D0),
0,
"DrvrRemove should return noErr in D0 for nominal calls"
);
assert_eq!(
cpu.read_reg(Register::A7),
stack_ptr,
"DrvrRemove is register-based and should preserve A7"
);
}
#[test]
fn drvrinstall_drvrremove_install_then_remove_composition_balances_stack() {
let (mut disp, mut cpu, mut bus) = setup();
let driver_ptr: u32 = 0x320200;
let stack_ptr: u32 = 0x200000;
let sentinel_addr = stack_ptr;
let sentinel: u32 = 0xBADC_0DE0;
let ref_num: u32 = 0xFFFF_FFCE;
bus.write_long(sentinel_addr, sentinel);
cpu.write_reg(Register::A7, stack_ptr);
cpu.write_reg(Register::A0, driver_ptr);
cpu.write_reg(Register::D0, ref_num);
let r1 = disp.dispatch_event(false, 0x3D, &mut cpu, &mut bus);
assert!(r1.is_some());
assert!(r1.unwrap().is_ok());
assert_eq!(
cpu.read_reg(Register::D0),
0,
"DrvrInstall composition: noErr"
);
cpu.write_reg(Register::D0, ref_num);
let r2 = disp.dispatch_event(false, 0x3E, &mut cpu, &mut bus);
assert!(r2.is_some());
assert!(r2.unwrap().is_ok());
assert_eq!(
cpu.read_reg(Register::D0),
0,
"DrvrRemove composition: noErr"
);
assert_eq!(
cpu.read_reg(Register::A7),
stack_ptr,
"Install + Remove must leave A7 untouched"
);
assert_eq!(
bus.read_long(sentinel_addr),
sentinel,
"Install + Remove must not clobber caller memory above SP"
);
}
#[test]
fn dovbltask_uses_d0_slot_and_returns_oserr_in_d0() {
let (mut disp, mut cpu, mut bus) = setup();
let stack_ptr = 0x00F0_3000;
cpu.write_reg(Register::D0, 0x0000_0009);
cpu.write_reg(Register::A7, stack_ptr);
let result = disp.dispatch_event(false, 0x72, &mut cpu, &mut bus);
assert!(result.is_some(), "DoVBLTask should be handled");
assert!(result.unwrap().is_ok(), "DoVBLTask should return normally");
assert_eq!(
cpu.read_reg(Register::D0),
0,
"DoVBLTask should return noErr in D0 for nominal calls"
);
assert_eq!(
cpu.read_reg(Register::A7),
stack_ptr,
"DoVBLTask is register-based and should preserve A7"
);
}
#[test]
fn dovbltask_register_only_calling_convention_preserves_stack_across_mixed_slots() {
let (mut disp, mut cpu, mut bus) = setup();
let stack_ptr = 0x00F0_3000;
cpu.write_reg(Register::A7, stack_ptr);
for slot in [0i32, 1, 0, 2, 0] {
cpu.write_reg(Register::D0, slot as u32);
let result = disp.dispatch_event(false, 0x72, &mut cpu, &mut bus);
assert!(result.is_some(), "DoVBLTask should be handled");
assert!(result.unwrap().is_ok(), "DoVBLTask should return normally");
assert_eq!(
cpu.read_reg(Register::A7),
stack_ptr,
"DoVBLTask must preserve A7 across dispatch with slot={slot}"
);
}
}
#[test]
fn dovbltask_invalid_slot_returns_slotnumerr_without_consuming_stack() {
let (mut disp, mut cpu, mut bus) = setup();
let stack_ptr = 0x00F0_5000;
cpu.write_reg(Register::D0, 16);
cpu.write_reg(Register::A7, stack_ptr);
let result = disp.dispatch_event(false, 0x72, &mut cpu, &mut bus);
assert!(result.is_some(), "DoVBLTask should be handled");
assert!(result.unwrap().is_ok(), "DoVBLTask should return normally");
assert_eq!(
cpu.read_reg(Register::D0) as i32,
-337,
"DoVBLTask should return slotNumErr for an invalid slot"
);
assert_eq!(
cpu.read_reg(Register::A7),
stack_ptr,
"DoVBLTask should preserve A7 on the invalid-slot path"
);
}
#[test]
fn attachvbl_uses_d0_slot_and_updates_primary_vbl_slot() {
let (mut disp, mut cpu, mut bus) = setup();
let stack_ptr = 0x00F0_6000;
cpu.write_reg(Register::D0, 10);
cpu.write_reg(Register::A7, stack_ptr);
let result = disp.dispatch_event(false, 0x71, &mut cpu, &mut bus);
assert!(result.is_some(), "AttachVBL should be handled");
assert!(result.unwrap().is_ok(), "AttachVBL should return normally");
assert_eq!(
cpu.read_reg(Register::D0),
0,
"AttachVBL should return noErr in D0 for a valid slot"
);
assert_eq!(
cpu.read_reg(Register::A7),
stack_ptr,
"AttachVBL is register-based and should preserve A7"
);
assert_eq!(
disp.primary_vbl_slot, 10,
"AttachVBL should record the newly selected primary slot"
);
}
#[test]
fn attachvbl_invalid_slot_returns_slotnumerr_without_changing_primary_slot() {
let (mut disp, mut cpu, mut bus) = setup();
let stack_ptr = 0x00F0_7000;
disp.primary_vbl_slot = 7;
cpu.write_reg(Register::D0, 16);
cpu.write_reg(Register::A7, stack_ptr);
let result = disp.dispatch_event(false, 0x71, &mut cpu, &mut bus);
assert!(result.is_some(), "AttachVBL should be handled");
assert!(result.unwrap().is_ok(), "AttachVBL should return normally");
assert_eq!(
cpu.read_reg(Register::D0) as i32,
-337,
"AttachVBL should return slotNumErr for an invalid slot"
);
assert_eq!(
cpu.read_reg(Register::A7),
stack_ptr,
"AttachVBL should preserve A7 on an invalid-slot path"
);
assert_eq!(
disp.primary_vbl_slot, 7,
"AttachVBL should not mutate the recorded primary slot on error"
);
}
#[test]
fn sintinstall_uses_a0_qelemptr_d0_slot_and_returns_oserr_in_d0() {
let (mut disp, mut cpu, mut bus) = setup();
let queue_elem_ptr = 0x320100;
let stack_ptr = 0x00F0_4000;
bus.write_long(queue_elem_ptr, 0xA5A5_5A5A);
bus.write_long(queue_elem_ptr + 4, 0x1122_3344);
cpu.write_reg(Register::A0, queue_elem_ptr);
cpu.write_reg(Register::D0, 0x0000_0009);
cpu.write_reg(Register::A7, stack_ptr);
let result = disp.dispatch_event(false, 0x75, &mut cpu, &mut bus);
assert!(result.is_some(), "SIntInstall should be handled");
assert!(
result.unwrap().is_ok(),
"SIntInstall should return normally"
);
assert_eq!(
cpu.read_reg(Register::D0),
0,
"SIntInstall should return noErr in D0 for nominal calls"
);
assert_eq!(
cpu.read_reg(Register::A0),
queue_elem_ptr,
"SIntInstall should not rewrite the queue-element pointer"
);
assert_eq!(
bus.read_long(queue_elem_ptr),
0xA5A5_5A5A,
"SIntInstall no-op path should not mutate queue-element memory"
);
assert_eq!(
bus.read_long(queue_elem_ptr + 4),
0x1122_3344,
"SIntInstall no-op path should preserve queue-element fields"
);
assert_eq!(
cpu.read_reg(Register::A7),
stack_ptr,
"SIntInstall is register-based and should preserve A7"
);
}
#[test]
fn sintremove_uses_a0_qelemptr_d0_slot_and_returns_oserr_in_d0() {
let (mut disp, mut cpu, mut bus) = setup();
let queue_elem_ptr = 0x320200;
let stack_ptr = 0x00F0_5000;
bus.write_long(queue_elem_ptr, 0x55AA_33CC);
bus.write_long(queue_elem_ptr + 4, 0xDEAD_BEEF);
cpu.write_reg(Register::A0, queue_elem_ptr);
cpu.write_reg(Register::D0, 0x0000_000A);
cpu.write_reg(Register::A7, stack_ptr);
let result = disp.dispatch_event(false, 0x76, &mut cpu, &mut bus);
assert!(result.is_some(), "SIntRemove should be handled");
assert!(result.unwrap().is_ok(), "SIntRemove should return normally");
assert_eq!(
cpu.read_reg(Register::D0),
0,
"SIntRemove should return noErr in D0 for nominal calls"
);
assert_eq!(
cpu.read_reg(Register::A0),
queue_elem_ptr,
"SIntRemove should not rewrite the queue-element pointer"
);
assert_eq!(
bus.read_long(queue_elem_ptr),
0x55AA_33CC,
"SIntRemove no-op path should not mutate queue-element memory"
);
assert_eq!(
bus.read_long(queue_elem_ptr + 4),
0xDEAD_BEEF,
"SIntRemove no-op path should preserve queue-element fields"
);
assert_eq!(
cpu.read_reg(Register::A7),
stack_ptr,
"SIntRemove is register-based and should preserve A7"
);
}
const EVENT_PTR: u32 = 0x300000;
const QHDR_FLAGS_OFFSET: u32 = 0;
const QHDR_HEAD_OFFSET: u32 = 2;
const QHDR_TAIL_OFFSET: u32 = 6;
const QELEM_LINK_OFFSET: u32 = 0;
fn read_event_record(
bus: &crate::memory::MacMemoryBus,
ptr: u32,
) -> (u16, u32, u32, u16, u16, u16) {
let what = bus.read_word(ptr);
let message = bus.read_long(ptr + 2);
let when = bus.read_long(ptr + 6);
let where_v = bus.read_word(ptr + 10);
let where_h = bus.read_word(ptr + 12);
let modifiers = bus.read_word(ptr + 14);
(what, message, when, where_v, where_h, modifiers)
}
#[test]
fn enqueue_on_empty_queue_sets_qhead_qtail_and_terminal_link() {
let (mut disp, mut cpu, mut bus) = setup();
let q_header = 0x310000;
let q_entry = 0x310100;
bus.write_word(q_header + QHDR_FLAGS_OFFSET, 0xA5A5);
bus.write_long(q_header + QHDR_HEAD_OFFSET, 0);
bus.write_long(q_header + QHDR_TAIL_OFFSET, 0);
bus.write_long(q_entry + QELEM_LINK_OFFSET, 0xDEAD_BEEF);
cpu.write_reg(Register::A0, q_entry);
cpu.write_reg(Register::A1, q_header);
let result = disp.dispatch_event(true, 0x16F, &mut cpu, &mut bus);
assert!(result.is_some(), "Enqueue should be handled");
assert!(result.unwrap().is_ok(), "Enqueue should succeed");
assert_eq!(
bus.read_long(q_header + QHDR_HEAD_OFFSET),
q_entry,
"Enqueue should set qHead to inserted entry for an empty queue"
);
assert_eq!(
bus.read_long(q_header + QHDR_TAIL_OFFSET),
q_entry,
"Enqueue should set qTail to inserted entry for an empty queue"
);
assert_eq!(
bus.read_long(q_entry + QELEM_LINK_OFFSET),
0,
"Enqueue should terminate the inserted entry with qLink=NIL"
);
assert_eq!(
bus.read_word(q_header + QHDR_FLAGS_OFFSET),
0xA5A5,
"Enqueue should not modify qFlags"
);
}
#[test]
fn enqueue_appends_after_existing_tail_and_preserves_qflags() {
let (mut disp, mut cpu, mut bus) = setup();
let q_header = 0x310200;
let first_entry = 0x310300;
let second_entry = 0x310400;
bus.write_word(q_header + QHDR_FLAGS_OFFSET, 0x55AA);
bus.write_long(q_header + QHDR_HEAD_OFFSET, first_entry);
bus.write_long(q_header + QHDR_TAIL_OFFSET, first_entry);
bus.write_long(first_entry + QELEM_LINK_OFFSET, 0);
bus.write_long(second_entry + QELEM_LINK_OFFSET, 0x1111_2222);
cpu.write_reg(Register::A0, second_entry);
cpu.write_reg(Register::A1, q_header);
let result = disp.dispatch_event(true, 0x16F, &mut cpu, &mut bus);
assert!(result.is_some(), "Enqueue should be handled");
assert!(result.unwrap().is_ok(), "Enqueue should succeed");
assert_eq!(
bus.read_long(q_header + QHDR_HEAD_OFFSET),
first_entry,
"Enqueue should preserve qHead when appending"
);
assert_eq!(
bus.read_long(q_header + QHDR_TAIL_OFFSET),
second_entry,
"Enqueue should move qTail to the appended entry"
);
assert_eq!(
bus.read_long(first_entry + QELEM_LINK_OFFSET),
second_entry,
"Enqueue should link the previous tail to the appended entry"
);
assert_eq!(
bus.read_long(second_entry + QELEM_LINK_OFFSET),
0,
"Enqueue should terminate the appended entry with qLink=NIL"
);
assert_eq!(
bus.read_word(q_header + QHDR_FLAGS_OFFSET),
0x55AA,
"Enqueue should not modify qFlags"
);
}
#[test]
fn dequeue_present_entry_unlinks_element_and_returns_noerr() {
let (mut disp, mut cpu, mut bus) = setup();
let q_header = 0x310500;
let first_entry = 0x310600;
let second_entry = 0x310700;
bus.write_word(q_header + QHDR_FLAGS_OFFSET, 0x0F0F);
bus.write_long(q_header + QHDR_HEAD_OFFSET, first_entry);
bus.write_long(q_header + QHDR_TAIL_OFFSET, second_entry);
bus.write_long(first_entry + QELEM_LINK_OFFSET, second_entry);
bus.write_long(second_entry + QELEM_LINK_OFFSET, 0);
cpu.write_reg(Register::A0, first_entry);
cpu.write_reg(Register::A1, q_header);
let result = disp.dispatch_event(true, 0x16E, &mut cpu, &mut bus);
assert!(result.is_some(), "Dequeue should be handled");
assert!(
result.unwrap().is_ok(),
"Dequeue should return from dispatch"
);
assert_eq!(
cpu.read_reg(Register::D0) as i32,
0,
"Dequeue should return noErr when entry is present"
);
assert_eq!(
bus.read_long(q_header + QHDR_HEAD_OFFSET),
second_entry,
"Dequeue should promote next entry to qHead when removing head"
);
assert_eq!(
bus.read_long(q_header + QHDR_TAIL_OFFSET),
second_entry,
"Dequeue should update qTail when head removal leaves one entry"
);
assert_eq!(
bus.read_word(q_header + QHDR_FLAGS_OFFSET),
0x0F0F,
"Dequeue should not modify qFlags"
);
}
#[test]
fn dequeue_missing_entry_returns_qerr_and_preserves_queue() {
let (mut disp, mut cpu, mut bus) = setup();
let q_header = 0x310800;
let first_entry = 0x310900;
let second_entry = 0x310A00;
let missing_entry = 0x310B00;
bus.write_word(q_header + QHDR_FLAGS_OFFSET, 0xAAAA);
bus.write_long(q_header + QHDR_HEAD_OFFSET, first_entry);
bus.write_long(q_header + QHDR_TAIL_OFFSET, second_entry);
bus.write_long(first_entry + QELEM_LINK_OFFSET, second_entry);
bus.write_long(second_entry + QELEM_LINK_OFFSET, 0);
cpu.write_reg(Register::A0, missing_entry);
cpu.write_reg(Register::A1, q_header);
let result = disp.dispatch_event(true, 0x16E, &mut cpu, &mut bus);
assert!(result.is_some(), "Dequeue should be handled");
assert!(
result.unwrap().is_ok(),
"Dequeue should return from dispatch"
);
assert_eq!(
cpu.read_reg(Register::D0) as i32,
-1,
"Dequeue should return qErr for missing entry"
);
assert_eq!(
bus.read_long(q_header + QHDR_HEAD_OFFSET),
first_entry,
"Missing-entry Dequeue should preserve qHead"
);
assert_eq!(
bus.read_long(q_header + QHDR_TAIL_OFFSET),
second_entry,
"Missing-entry Dequeue should preserve qTail"
);
assert_eq!(
bus.read_long(first_entry + QELEM_LINK_OFFSET),
second_entry,
"Missing-entry Dequeue should preserve existing queue links"
);
assert_eq!(
bus.read_long(second_entry + QELEM_LINK_OFFSET),
0,
"Missing-entry Dequeue should preserve existing terminal qLink"
);
assert_eq!(
bus.read_word(q_header + QHDR_FLAGS_OFFSET),
0xAAAA,
"Missing-entry Dequeue should preserve qFlags"
);
}
#[test]
fn enqueue_dequeue_dispatcher_convention_preserves_register_only_abi() {
let (mut disp, mut cpu, mut bus) = setup();
let q_header = 0x320000;
let elem_a = 0x320100;
let elem_b = 0x320200;
let elem_c = 0x320300;
bus.write_word(q_header + QHDR_FLAGS_OFFSET, 0x5A5A);
bus.write_long(q_header + QHDR_HEAD_OFFSET, 0);
bus.write_long(q_header + QHDR_TAIL_OFFSET, 0);
bus.write_long(elem_a + QELEM_LINK_OFFSET, 0xDEAD_BEEF);
bus.write_long(elem_b + QELEM_LINK_OFFSET, 0xCAFE_F00D);
bus.write_long(elem_c + QELEM_LINK_OFFSET, 0xBAAD_F00D);
cpu.write_reg(Register::A0, elem_a);
cpu.write_reg(Register::A1, q_header);
assert!(disp
.dispatch_event(true, 0x16F, &mut cpu, &mut bus)
.is_some());
assert_eq!(bus.read_long(q_header + QHDR_HEAD_OFFSET), elem_a);
assert_eq!(bus.read_long(q_header + QHDR_TAIL_OFFSET), elem_a);
assert_eq!(bus.read_long(elem_a + QELEM_LINK_OFFSET), 0);
cpu.write_reg(Register::A0, elem_b);
cpu.write_reg(Register::A1, q_header);
assert!(disp
.dispatch_event(true, 0x16F, &mut cpu, &mut bus)
.is_some());
assert_eq!(bus.read_long(q_header + QHDR_HEAD_OFFSET), elem_a);
assert_eq!(bus.read_long(q_header + QHDR_TAIL_OFFSET), elem_b);
assert_eq!(bus.read_long(elem_a + QELEM_LINK_OFFSET), elem_b);
assert_eq!(bus.read_long(elem_b + QELEM_LINK_OFFSET), 0);
assert_eq!(bus.read_word(q_header + QHDR_FLAGS_OFFSET), 0x5A5A);
cpu.write_reg(Register::D0, 0x3FFF_3FFF); cpu.write_reg(Register::A0, elem_a);
cpu.write_reg(Register::A1, q_header);
assert!(disp
.dispatch_event(true, 0x16E, &mut cpu, &mut bus)
.is_some());
assert_eq!(cpu.read_reg(Register::D0) as i32, 0);
assert_eq!(bus.read_long(q_header + QHDR_HEAD_OFFSET), elem_b);
assert_eq!(bus.read_long(q_header + QHDR_TAIL_OFFSET), elem_b);
cpu.write_reg(Register::D0, 0x3FFF_3FFF); cpu.write_reg(Register::A0, elem_c);
cpu.write_reg(Register::A1, q_header);
assert!(disp
.dispatch_event(true, 0x16E, &mut cpu, &mut bus)
.is_some());
assert_eq!(cpu.read_reg(Register::D0) as i32, -1);
assert_eq!(bus.read_long(q_header + QHDR_HEAD_OFFSET), elem_b);
assert_eq!(bus.read_long(q_header + QHDR_TAIL_OFFSET), elem_b);
assert_eq!(bus.read_word(q_header + QHDR_FLAGS_OFFSET), 0x5A5A);
}
#[test]
fn get_next_event_empty_queue_returns_null_event() {
let (mut disp, mut cpu, mut bus) = setup();
disp.sent_open_app_event = true;
cpu.write_reg(Register::D0, 0xFFFF);
cpu.write_reg(Register::A0, EVENT_PTR);
let result = disp.dispatch_event(false, 0x70, &mut cpu, &mut bus);
assert!(result.unwrap().is_ok());
assert_eq!(cpu.read_reg(Register::D0), 0);
let (what, _message, _when, _where_v, _where_h, _modifiers) =
read_event_record(&bus, EVENT_PTR);
assert_eq!(what, 0);
}
#[test]
fn get_next_event_matching_event_returns_it() {
let (mut disp, mut cpu, mut bus) = setup();
disp.sent_open_app_event = true;
disp.event_queue.push_back(QueuedEvent {
what: 1,
message: 0,
where_v: 100,
where_h: 200,
modifiers: 0,
});
cpu.write_reg(Register::D0, 0xFFFF);
cpu.write_reg(Register::A0, EVENT_PTR);
let result = disp.dispatch_event(false, 0x70, &mut cpu, &mut bus);
assert!(result.unwrap().is_ok());
assert_eq!(cpu.read_reg(Register::D0), 0xFFFF);
let (what, _message, when, where_v, where_h, _modifiers) =
read_event_record(&bus, EVENT_PTR);
assert_eq!(what, 1); assert_eq!(where_v, 100);
assert_eq!(where_h, 200);
assert_eq!(when, 100);
assert!(disp.event_queue.is_empty());
}
#[test]
fn get_next_event_non_matching_mask_returns_no_event() {
let (mut disp, mut cpu, mut bus) = setup();
disp.event_queue.push_back(QueuedEvent {
what: 1,
message: 0,
where_v: 50,
where_h: 60,
modifiers: 0,
});
cpu.write_reg(Register::D0, 0x0004);
cpu.write_reg(Register::A0, EVENT_PTR);
let result = disp.dispatch_event(false, 0x70, &mut cpu, &mut bus);
assert!(result.unwrap().is_ok());
assert_eq!(cpu.read_reg(Register::D0), 0);
assert_eq!(disp.event_queue.len(), 1);
}
#[test]
fn get_next_event_mouse_up_clears_mouse_button() {
let (mut disp, mut cpu, mut bus) = setup();
disp.mouse_button = true;
disp.event_queue.push_back(QueuedEvent {
what: 2,
message: 0,
where_v: 75,
where_h: 150,
modifiers: 0,
});
cpu.write_reg(Register::D0, 0x0004); cpu.write_reg(Register::A0, EVENT_PTR);
assert!(disp.mouse_button);
let result = disp.dispatch_event(false, 0x70, &mut cpu, &mut bus);
assert!(result.unwrap().is_ok());
assert_eq!(cpu.read_reg(Register::D0), 0xFFFF);
assert!(!disp.mouse_button);
let (what, _message, _when, where_v, where_h, _modifiers) =
read_event_record(&bus, EVENT_PTR);
assert_eq!(what, 2);
assert_eq!(where_v, 75);
assert_eq!(where_h, 150);
}
#[test]
fn event_avail_returns_d0_zero() {
let (mut disp, mut cpu, mut bus) = setup();
let result = disp.dispatch_event(false, 0x71, &mut cpu, &mut bus);
assert!(result.unwrap().is_ok());
assert_eq!(cpu.read_reg(Register::D0), 0);
}
#[test]
fn rdrvrinstall_returns_d0_zero_and_preserves_stack_pointer() {
let (mut disp, mut cpu, mut bus) = setup();
let sp_pre = cpu.read_reg(Register::A7);
cpu.write_reg(Register::A0, 0x1234_5678);
cpu.write_reg(Register::D0, 7);
let result = disp.dispatch_event(false, 0x4F, &mut cpu, &mut bus);
assert!(result.unwrap().is_ok());
assert_eq!(cpu.read_reg(Register::D0), 0);
assert_eq!(cpu.read_reg(Register::A7), sp_pre);
}
#[test]
fn post_event_returns_d0_zero() {
let (mut disp, mut cpu, mut bus) = setup();
let result = disp.dispatch_event(false, 0x2F, &mut cpu, &mut bus);
assert!(result.unwrap().is_ok());
assert_eq!(cpu.read_reg(Register::D0), 0);
}
#[test]
fn post_event_observes_direct_sys_evt_mask_writes() {
let (mut disp, mut cpu, mut bus) = setup();
cpu.write_reg(Register::A0, 4);
cpu.write_reg(Register::D0, 0x1234);
let result = disp.dispatch_event(false, 0x2F, &mut cpu, &mut bus);
assert!(result.unwrap().is_ok());
assert_eq!(cpu.read_reg(Register::D0), TrapDispatcher::EVT_NOT_ENB);
assert!(disp.event_queue.is_empty());
bus.write_word(crate::memory::globals::addr::SYS_EVT_MASK, 0xffff);
cpu.write_reg(Register::A0, 4);
cpu.write_reg(Register::D0, 0x5678);
let result = disp.dispatch_event(false, 0x2F, &mut cpu, &mut bus);
assert!(result.unwrap().is_ok());
assert_eq!(cpu.read_reg(Register::D0), 0);
assert_eq!(disp.event_queue.front().map(|event| event.what), Some(4));
assert_eq!(
disp.event_queue.front().map(|event| event.message),
Some(0x5678)
);
}
#[test]
fn os_event_avail_returns_d0_ffff_when_empty() {
let (mut disp, mut cpu, mut bus) = setup();
cpu.write_reg(Register::D0, 0xFFFF);
cpu.write_reg(Register::A0, EVENT_PTR);
let result = disp.dispatch_event(false, 0x30, &mut cpu, &mut bus);
assert!(result.unwrap().is_ok());
assert_eq!(cpu.read_reg(Register::D0), 0xFFFF);
let (what, _message, _when, _where_v, _where_h, modifiers) =
read_event_record(&bus, EVENT_PTR);
assert_eq!(what, 0);
assert_eq!(modifiers & 0x0080, 0x0080);
}
#[test]
fn os_event_avail_peeks_latched_native_menu_mouse_down() {
let (mut disp, mut cpu, mut bus) = setup();
disp.tick_count = 100;
disp.pending_native_menu_event = Some(QueuedEvent {
what: 1,
message: 0,
where_v: 10,
where_h: 42,
modifiers: 0,
});
cpu.write_reg(Register::D0, 1 << 1);
cpu.write_reg(Register::A0, EVENT_PTR);
let result = disp.dispatch_event(false, 0x30, &mut cpu, &mut bus);
assert!(result.unwrap().is_ok());
assert_eq!(cpu.read_reg(Register::D0), 0);
assert_eq!(read_event_record(&bus, EVENT_PTR).0, 1);
assert!(disp.pending_native_menu_event.is_some());
cpu.write_reg(Register::D0, 1 << 1);
let result = disp.dispatch_event(false, 0x31, &mut cpu, &mut bus);
assert!(result.unwrap().is_ok());
assert_eq!(cpu.read_reg(Register::D0), 0);
assert_eq!(read_event_record(&bus, EVENT_PTR).0, 1);
}
#[test]
fn get_os_event_returns_d0_ffff_when_empty() {
let (mut disp, mut cpu, mut bus) = setup();
cpu.write_reg(Register::D0, 0xFFFF);
cpu.write_reg(Register::A0, EVENT_PTR);
let result = disp.dispatch_event(false, 0x31, &mut cpu, &mut bus);
assert!(result.unwrap().is_ok());
assert_eq!(cpu.read_reg(Register::D0), 0xFFFF);
let (what, _message, _when, _where_v, _where_h, modifiers) =
read_event_record(&bus, EVENT_PTR);
assert_eq!(what, 0);
assert_eq!(modifiers & 0x0080, 0x0080);
}
#[test]
fn get_os_event_skips_toolbox_and_high_level_events() {
let (mut disp, mut cpu, mut bus) = setup();
disp.event_queue.push_back(QueuedEvent {
what: 6,
message: 0x1000,
where_v: 10,
where_h: 20,
modifiers: 0,
});
disp.event_queue.push_back(QueuedEvent {
what: 23,
message: u32::from_be_bytes(*b"aevt"),
where_v: 0,
where_h: 0,
modifiers: 0,
});
disp.push_key_down(0x00, b'a');
cpu.write_reg(Register::D0, 0xFFFF);
cpu.write_reg(Register::A0, EVENT_PTR);
let result = disp.dispatch_event(false, 0x31, &mut cpu, &mut bus);
assert!(result.unwrap().is_ok());
assert_eq!(cpu.read_reg(Register::D0), 0);
assert_eq!(read_event_record(&bus, EVENT_PTR).0, 3);
assert_eq!(disp.event_queue.len(), 2);
assert_eq!(disp.event_queue[0].what, 6);
assert_eq!(disp.event_queue[1].what, 23);
cpu.write_reg(Register::D0, 0xFFFF);
let result = disp.dispatch_event(false, 0x30, &mut cpu, &mut bus);
assert!(result.unwrap().is_ok());
assert_eq!(cpu.read_reg(Register::D0), 0xFFFF);
assert_eq!(read_event_record(&bus, EVENT_PTR).0, 0);
assert_eq!(disp.event_queue.len(), 2);
}
#[test]
fn toolbox_event_accessors_apply_documented_event_priority() {
let (mut disp, mut cpu, mut bus) = setup();
for (what, message) in [
(6, 0x1000),
(23, u32::from_be_bytes(*b"aevt")),
(1, 0),
(8, 0x2000),
] {
disp.event_queue.push_back(QueuedEvent {
what,
message,
where_v: 10,
where_h: 20,
modifiers: 0,
});
}
let event = disp.dequeue_toolbox_event(&mut cpu, &mut bus, u16::MAX);
assert_eq!(event.0, 8, "activate events have highest priority");
let event = disp.dequeue_toolbox_event(&mut cpu, &mut bus, u16::MAX);
assert_eq!(event.0, 1, "user input precedes update events");
let event = disp.dequeue_toolbox_event(&mut cpu, &mut bus, u16::MAX);
assert_eq!(event.0, 6, "update events precede high-level events");
assert_eq!(disp.event_queue.front().map(|event| event.what), Some(23));
}
#[test]
fn low_level_toolbox_events_preserve_fifo_order() {
let (mut disp, mut cpu, mut bus) = setup();
for (what, message) in [(3, 0x3000), (2, 0x2000), (7, 0x7000)] {
disp.event_queue.push_back(QueuedEvent {
what,
message,
where_v: 10,
where_h: 20,
modifiers: 0,
});
}
for expected in [(3, 0x3000), (2, 0x2000), (7, 0x7000)] {
let peeked = disp.peek_toolbox_event(&bus, u16::MAX).unwrap();
assert_eq!((peeked.what, peeked.message), expected);
let event = disp.dequeue_toolbox_event(&mut cpu, &mut bus, u16::MAX);
assert_eq!((event.0, event.1), expected);
}
}
fn make_dirty_visible_window(bus: &mut crate::memory::MacMemoryBus) -> u32 {
let window = bus.alloc(160);
bus.write_byte(window + 110, 0xFF); let region = bus.alloc(10);
bus.write_word(region, 10);
bus.write_word(region + 2, 0);
bus.write_word(region + 4, 0);
bus.write_word(region + 6, 20);
bus.write_word(region + 8, 20);
let update_handle = bus.alloc(4);
bus.write_long(update_handle, region);
bus.write_long(window + 122, update_handle); window
}
#[test]
fn flushed_update_keeps_priority_between_autokey_and_os_events() {
let (mut disp, mut cpu, mut bus) = setup();
let window = make_dirty_visible_window(&mut bus);
disp.window_list.push(window);
disp.queue_window_update_event(window);
disp.flush_events_with_masks(1 << 6, 0);
assert!(disp.event_queue.is_empty());
assert_eq!(disp.flushed_update_events.len(), 1);
for what in [15, 5] {
disp.event_queue.push_back(QueuedEvent {
what,
message: 0,
where_v: 10,
where_h: 20,
modifiers: 0,
});
}
let peeked = disp.peek_toolbox_event(&bus, u16::MAX).unwrap();
assert_eq!(peeked.what, 5, "autoKey precedes recovered updateEvt");
let event = disp.dequeue_toolbox_event(&mut cpu, &mut bus, u16::MAX);
assert_eq!(event.0, 5);
let peeked = disp.peek_toolbox_event(&bus, u16::MAX).unwrap();
assert_eq!(peeked.what, 6, "recovered updateEvt precedes OS events");
assert_eq!(peeked.message, window);
let event = disp.dequeue_toolbox_event(&mut cpu, &mut bus, u16::MAX);
assert_eq!((event.0, event.1), (6, window));
let event = disp.dequeue_toolbox_event(&mut cpu, &mut bus, u16::MAX);
assert_eq!(event.0, 15);
}
#[test]
fn mixed_source_updates_follow_front_to_back_window_order() {
let (mut disp, mut cpu, mut bus) = setup();
let front = make_dirty_visible_window(&mut bus);
let back = make_dirty_visible_window(&mut bus);
disp.window_list = vec![front, back];
disp.queue_window_update_event(front);
disp.flush_events_with_masks(1 << 6, 0);
disp.queue_window_update_event(back);
assert_eq!(disp.flushed_update_events.len(), 1);
assert_eq!(disp.event_queue.len(), 1);
let peeked = disp.peek_toolbox_event(&bus, u16::MAX).unwrap();
assert_eq!(peeked.message, front, "recovered front update must win");
let event = disp.dequeue_toolbox_event(&mut cpu, &mut bus, u16::MAX);
assert_eq!((event.0, event.1), (6, front));
let peeked = disp.peek_toolbox_event(&bus, u16::MAX).unwrap();
assert_eq!(peeked.message, back);
let event = disp.dequeue_toolbox_event(&mut cpu, &mut bus, u16::MAX);
assert_eq!((event.0, event.1), (6, back));
}
#[test]
fn stale_ordinary_update_does_not_precede_valid_recovered_update() {
let (mut disp, mut cpu, mut bus) = setup();
let hidden = make_dirty_visible_window(&mut bus);
bus.write_byte(hidden + 110, 0);
let visible = make_dirty_visible_window(&mut bus);
disp.window_list = vec![hidden, visible];
disp.event_queue.push_back(QueuedEvent {
what: 6,
message: hidden,
where_v: 0,
where_h: 0,
modifiers: 0,
});
disp.queue_window_update_event(visible);
disp.flush_events_with_masks(1 << 6, 0);
disp.event_queue.push_back(QueuedEvent {
what: 6,
message: hidden,
where_v: 0,
where_h: 0,
modifiers: 0,
});
let peeked = disp.peek_toolbox_event(&bus, u16::MAX).unwrap();
assert_eq!(peeked.message, visible);
let event = disp.dequeue_toolbox_event(&mut cpu, &mut bus, u16::MAX);
assert_eq!((event.0, event.1), (6, visible));
}
#[test]
fn explicitly_posted_orphan_update_remains_deliverable() {
let (mut disp, mut cpu, mut bus) = setup();
disp.event_queue.push_back(QueuedEvent {
what: 6,
message: 0x1234_5678,
where_v: 0,
where_h: 0,
modifiers: 0,
});
assert_eq!(
disp.peek_toolbox_event(&bus, u16::MAX).unwrap().message,
0x1234_5678
);
let event = disp.dequeue_toolbox_event(&mut cpu, &mut bus, u16::MAX);
assert_eq!((event.0, event.1), (6, 0x1234_5678));
}
#[test]
fn picture_backed_update_peek_and_dequeue_skip_the_same_marker() {
let (mut disp, mut cpu, mut bus) = setup();
let picture_window = make_dirty_visible_window(&mut bus);
let picture_handle = bus.alloc(4);
bus.write_long(picture_handle, 0x0010_0000);
bus.write_long(picture_window + 148, picture_handle);
disp.window_list.push(picture_window);
disp.queue_window_update_event(picture_window);
disp.event_queue.push_back(QueuedEvent {
what: 15,
message: 0,
where_v: 0,
where_h: 0,
modifiers: 0,
});
assert_eq!(disp.peek_toolbox_event(&bus, u16::MAX).unwrap().what, 15);
let event = disp.dequeue_toolbox_event(&mut cpu, &mut bus, u16::MAX);
assert_eq!(event.0, 15);
}
#[test]
fn dequeue_does_not_synthesize_markerless_dirty_window_updates() {
let (mut disp, mut cpu, mut bus) = setup();
let window = make_dirty_visible_window(&mut bus);
disp.window_list.push(window);
disp.event_queue.push_back(QueuedEvent {
what: 15,
message: 0,
where_v: 10,
where_h: 20,
modifiers: 0,
});
let event = disp.dequeue_toolbox_event(&mut cpu, &mut bus, u16::MAX);
assert_eq!(event.0, 15);
let event = disp.dequeue_toolbox_event(&mut cpu, &mut bus, u16::MAX);
assert!(!event.5, "a dirty region alone must not stream updateEvts");
}
#[test]
fn peek_and_dequeue_agree_on_markerless_dirty_window() {
let (mut disp, mut cpu, mut bus) = setup();
let window = make_dirty_visible_window(&mut bus);
disp.window_list.push(window);
assert!(disp.peek_toolbox_event(&bus, u16::MAX).is_none());
let event = disp.dequeue_toolbox_event(&mut cpu, &mut bus, u16::MAX);
assert!(!event.5);
assert_eq!(event.0, 0);
}
#[test]
fn get_os_event_mouse_up_reports_button_up_modifier() {
let (mut disp, mut cpu, mut bus) = setup();
disp.push_mouse_down(75, 150);
let _ = disp.dequeue_event(&bus, 0xFFFF);
disp.push_mouse_up(75, 150);
cpu.write_reg(Register::D0, 0x0004);
cpu.write_reg(Register::A0, EVENT_PTR);
let result = disp.dispatch_event(false, 0x31, &mut cpu, &mut bus);
assert!(result.unwrap().is_ok());
assert_eq!(cpu.read_reg(Register::D0), 0);
let (what, _message, _when, where_v, where_h, modifiers) =
read_event_record(&bus, EVENT_PTR);
assert_eq!(what, 2);
assert_eq!(where_v, 75);
assert_eq!(where_h, 150);
assert_eq!(modifiers & 0x0080, 0x0080);
}
}