diff --git a/ios/ios_event.m b/ios/ios_event.m index 363bdf9..511fb89 100644 --- a/ios/ios_event.m +++ b/ios/ios_event.m @@ -24,7 +24,7 @@ int axWaitMessage(longTime_t msec) { } void axRemoveFromQueue(void *target) { - ios_remove_messages(target); + ios_remove_messages(target, 0, AX_ANY_WPARAM); for (NSNumber *key in ios_timers.allKeys) { if ([objc_getAssociatedObject(ios_timers[key], &ios_timer_owner) pointerValue] == target) axCancelTimer(key.unsignedIntValue); } diff --git a/ios/ios_message_queue.h b/ios/ios_message_queue.h index 94d8e7f..b21207c 100644 --- a/ios/ios_message_queue.h +++ b/ios/ios_message_queue.h @@ -10,7 +10,8 @@ void ios_post_display_paint(uint32_t size); // Freeze the readable queue at the start of a UIKit display callback. void ios_begin_message_frame(void); int ios_messages_ready(void); -void ios_remove_messages(void *target); +// Remove queued messages for target; message 0 matches any type. +void ios_remove_messages(void *target, uint32_t message, uint32_t wparam); void ios_shutdown_messages(void); #endif diff --git a/ios/ios_message_queue.m b/ios/ios_message_queue.m index 1bb9e83..25e5ef9 100644 --- a/ios/ios_message_queue.m +++ b/ios/ios_message_queue.m @@ -99,13 +99,14 @@ int axPeekMessage(struct AXmessage *msg) { return ready; } -void ios_remove_messages(void *target) { +void ios_remove_messages(void *target, uint32_t message, uint32_t wparam) { pthread_mutex_lock(&ios_queue_mutex); ios_message_t **link = &ios_head; ios_tail = NULL; while (*link) { ios_message_t *p = *link; - if (p->value.target == target) { + if (p->value.target == target && (!message || p->value.message == message) && + (wparam == AX_ANY_WPARAM || p->value.wParam == wparam)) { *link = p->next; if (p->value.message == kEventDragDrop) free(p->value.lParam); free(p); @@ -115,6 +116,10 @@ void ios_remove_messages(void *target) { pthread_mutex_unlock(&ios_queue_mutex); } +void axRemoveMessages(void *target, uint32_t message, uint32_t wparam) { + if (message) ios_remove_messages(target, message, wparam); +} + void ios_shutdown_messages(void) { pthread_mutex_lock(&ios_queue_mutex); while (ios_head) { diff --git a/macos/macos_event.m b/macos/macos_event.m index 33e615a..3852a2b 100644 --- a/macos/macos_event.m +++ b/macos/macos_event.m @@ -40,6 +40,17 @@ uint32_t KEY_GetKeyName(uint32_t keycode) { axCancelTimer(s_timers[i].id); } +void +axRemoveMessages(void* target, uint32_t message, uint32_t wparam) +{ + // Cocoa events refer to queue slots by index, so clear matches in place. + for (uint16_t r = queue.read; r != queue.write; r++) { + struct AXmessage const *e = &queue.data[r]; + if (e->target == target && e->message == message && (wparam == AX_ANY_WPARAM || e->wParam == wparam)) + memset(&queue.data[r], 0, sizeof(queue.data[r])); + } +} + void axPostMessageW(void* obj, uint32_t Msg, wParam_t wParam, lParam_t lParam) { diff --git a/platform.h b/platform.h index 1f20f93..7cc8af2 100644 --- a/platform.h +++ b/platform.h @@ -406,6 +406,25 @@ axGetMessage(struct AXmessage* msg); AX_API void axRemoveFromQueue(void* target); +/** @brief Wildcard for #axRemoveMessages: match an event whatever its wParam. */ +#define AX_ANY_WPARAM ((uint32_t)-1) + +/** + * @brief Remove queued events of one type for one target. + * + * Removes every queued event whose target is @p target and whose type is + * @p message, keeping the order of the others. Pass #AX_ANY_WPARAM as + * @p wparam to match any wParam, or a value to remove only the events that + * carry it, such as the ticks of one cancelled timer. Unlike + * #axRemoveFromQueue, this leaves the target's timers running. + * + * @param target Window or object handle to match against. + * @param message Event type to remove (e.g. #kEventTimer). + * @param wparam wParam to match, or #AX_ANY_WPARAM for any. + */ +AX_API void +axRemoveMessages(void* target, uint32_t message, uint32_t wparam); + /** * @brief Notify the event system that a file was dropped onto the window. * diff --git a/qnx/qnx_system.c b/qnx/qnx_system.c index edd013b..71974a5 100644 --- a/qnx/qnx_system.c +++ b/qnx/qnx_system.c @@ -369,6 +369,18 @@ axRemoveFromQueue(void *target) memset(&queue.data[r], 0, sizeof(queue.data[r])); } +void +axRemoveMessages(void *target, uint32_t message, uint32_t wparam) +{ + uint16_t r = queue.read, w = queue.write, nw = queue.read; + while (r != w) { + struct AXmessage const *e = &queue.data[r++]; + if (e->target != target || e->message != message || (wparam != AX_ANY_WPARAM && e->wParam != wparam)) + queue.data[nw++] = *e; + } + queue.write = nw; +} + int axWaitMessage(longTime_t msec) { diff --git a/tests/test_ios_message_queue.c b/tests/test_ios_message_queue.c index 398a4d7..2ee917e 100644 --- a/tests/test_ios_message_queue.c +++ b/tests/test_ios_message_queue.c @@ -73,10 +73,10 @@ static void test_removal_and_reset(void) { axPostMessageW(&first, kEventDragDrop, 0, strdup("removed file")); ios_begin_message_frame(); axPostMessageW(&second, kEventKeyUp, 3, NULL); - ios_remove_messages(&first); + ios_remove_messages(&first, 0, AX_ANY_WPARAM); assert(axPeekMessage(&msg) && msg.target == &second && msg.wParam == 2); assert(!axPeekMessage(&msg)); - ios_remove_messages(&second); + ios_remove_messages(&second, 0, AX_ANY_WPARAM); axPostMessageW(&first, kEventKeyDown, 4, NULL); assert(!axPeekMessage(&msg)); ios_begin_message_frame(); @@ -89,6 +89,23 @@ static void test_removal_and_reset(void) { assert(!axPeekMessage(&msg)); } +static void test_remove_by_type(void) { + ios_shutdown_messages(); + int first, second; + struct AXmessage msg; + axPostMessageW(&first, kEventTimer, 1, NULL); + axPostMessageW(&first, kEventTimer, 2, NULL); + axPostMessageW(&second, kEventTimer, 1, NULL); + axPostMessageW(&first, kEventKeyDown, 3, NULL); + axRemoveMessages(&first, kEventTimer, 1); + assert(axPeekMessage(&msg) && msg.target == &first && msg.message == kEventTimer && msg.wParam == 2); + assert(axPeekMessage(&msg) && msg.target == &second && msg.wParam == 1); + axPostMessageW(&first, kEventTimer, 4, NULL); + axRemoveMessages(&first, kEventTimer, AX_ANY_WPARAM); + assert(axPeekMessage(&msg) && msg.message == kEventKeyDown && msg.wParam == 3); + assert(!axPeekMessage(&msg)); +} + static void test_sequence_wrap(void) { ios_shutdown_messages(); ios_test_message_sequence(UINT32_MAX - 1); @@ -116,8 +133,9 @@ int main(void) { test_inertia_interrupt(); test_complete_snapshot(); test_removal_and_reset(); + test_remove_by_type(); test_sequence_wrap(); ios_shutdown_messages(); - puts("PASS: iOS queue frame boundaries, touch interruption, FIFO, removal, reset and sequence wrap"); + puts("PASS: iOS queue frame boundaries, touch interruption, FIFO, removal by target and type, reset and sequence wrap"); return 0; } diff --git a/tests/test_macos_events.m b/tests/test_macos_events.m index 2d3d13d..ee03f3c 100644 --- a/tests/test_macos_events.m +++ b/tests/test_macos_events.m @@ -39,6 +39,28 @@ static void test_event_lifetime(void) } } +// Removed messages leave cleared slots that Cocoa still points at. +static void test_remove_messages(void) +{ + struct AXmessage msg; + void *a = (void *)&test_remove_messages, *b = (void *)&drain_events; + drain_events(); + axPostMessageW(a, kEventTimer, 1, NULL); + axPostMessageW(a, kEventTimer, 2, NULL); + axPostMessageW(b, kEventTimer, 1, NULL); + axPostMessageW(a, kEventKeyDown, 3, NULL); + axPostMessageW(a, kEventTimer, 1, NULL); + axRemoveMessages(a, kEventTimer, 1); + assert(axPeekMessage(&msg) == 1 && msg.target == a && msg.message == kEventTimer && msg.wParam == 2); + assert(axPeekMessage(&msg) == 1 && msg.target == b && msg.message == kEventTimer && msg.wParam == 1); + assert(axPeekMessage(&msg) == 1 && msg.target == a && msg.message == kEventKeyDown); + axPostMessageW(a, kEventTimer, 4, NULL); + axPostMessageW(a, kEventKeyUp, 5, NULL); + axRemoveMessages(a, kEventTimer, AX_ANY_WPARAM); + assert(axPeekMessage(&msg) == 1 && msg.message == kEventKeyUp && msg.wParam == 5); + drain_events(); +} + static void test_fullscreen(void) { assert(axCreateWindow("Fullscreen event test", 640, 480, @@ -67,6 +89,7 @@ int main(int argc, char **argv) axInit(); drain_events(); test_event_lifetime(); + test_remove_messages(); if (argc > 1 && !strcmp(argv[1], "--fullscreen")) test_fullscreen(); puts("macOS event lifetime tests passed."); } diff --git a/tests/test_messages.c b/tests/test_messages.c index 81cf44f..998c83d 100644 --- a/tests/test_messages.c +++ b/tests/test_messages.c @@ -1,7 +1,8 @@ /* * test_messages.c – behavioral tests for the platform event-queue API. * - * Tests axPostMessageW, axPeekMessage, and axRemoveFromQueue without + * Tests axPostMessageW, axPeekMessage, axRemoveFromQueue and + * axRemoveMessages without * requiring a live display server or windowing context. * * On macOS the internal queue is coupled to NSApp, so message-delivery tests @@ -136,6 +137,45 @@ static void test_remove_nonexistent_target(void) assert(axPeekMessage(&msg) == 0); } +/* ------------------------------------------------------------------- + * test_remove_messages + * axRemoveMessages must remove only one type for one target, matching + * wParam exactly unless AX_ANY_WPARAM is given. + * ------------------------------------------------------------------- */ +static void test_remove_messages(void) +{ + struct AXmessage msg; + void *handle_a = (void *)0xA0A0; + void *handle_b = (void *)0xB0B0; + + drain_queue(); + + axPostMessageW(handle_a, kEventTimer, 1, NULL); + axPostMessageW(handle_a, kEventTimer, 2, NULL); + axPostMessageW(handle_b, kEventTimer, 1, NULL); + axPostMessageW(handle_a, kEventKeyDown, 1, NULL); + axPostMessageW(handle_a, kEventTimer, 1, NULL); + + /* One timer of handle_a: other timers, targets and types stay queued */ + axRemoveMessages(handle_a, kEventTimer, 1); + assert(axPeekMessage(&msg) == 1); + assert(msg.target == handle_a && msg.message == kEventTimer && msg.wParam == 2); + assert(axPeekMessage(&msg) == 1); + assert(msg.target == handle_b && msg.message == kEventTimer && msg.wParam == 1); + assert(axPeekMessage(&msg) == 1); + assert(msg.target == handle_a && msg.message == kEventKeyDown); + assert(axPeekMessage(&msg) == 0); + + /* AX_ANY_WPARAM removes the type whatever its wParam */ + axPostMessageW(handle_a, kEventTimer, 3, NULL); + axPostMessageW(handle_a, kEventKeyDown, 4, NULL); + axPostMessageW(handle_a, kEventTimer, 5, NULL); + axRemoveMessages(handle_a, kEventTimer, AX_ANY_WPARAM); + assert(axPeekMessage(&msg) == 1); + assert(msg.message == kEventKeyDown && msg.wParam == 4); + assert(axPeekMessage(&msg) == 0); +} + /* ------------------------------------------------------------------- * test_lparam_roundtrip * The lParam pointer must survive the queue round-trip unchanged. @@ -230,6 +270,7 @@ int main(void) test_fifo_ordering(); test_remove_from_queue(); test_remove_nonexistent_target(); + test_remove_messages(); test_lparam_roundtrip(); test_double_click_sequence(); test_get_message(); diff --git a/wayland/wayland_event.c b/wayland/wayland_event.c index 94eed13..a545e59 100644 --- a/wayland/wayland_event.c +++ b/wayland/wayland_event.c @@ -797,6 +797,21 @@ axRemoveFromQueue(void* hobj) axCancelTimer(s_timers[i].id); } +void +axRemoveMessages(void* hobj, uint32_t event, uint32_t wparam) +{ + WORD read_idx = events.read; + WORD write_idx = events.write; + WORD new_write = events.read; + + while (read_idx != write_idx) { + EVENT const *e = &events.queue[read_idx++]; + if (e->target != hobj || e->message != event || (wparam != AX_ANY_WPARAM && e->wParam != wparam)) + events.queue[new_write++] = *e; + } + events.write = new_write; +} + void axNotifyFileDropEvent(char const* filename, float x, float y) { diff --git a/webgl/webgl_event.c b/webgl/webgl_event.c index d24b1c3..5c09c2d 100644 --- a/webgl/webgl_event.c +++ b/webgl/webgl_event.c @@ -376,6 +376,21 @@ axRemoveFromQueue(void *hobj) axCancelTimer(s_timers[i].id); } +void +axRemoveMessages(void *hobj, uint32_t event, uint32_t wparam) +{ + WORD read_idx = events.read; + WORD write_idx = events.write; + WORD new_write = events.read; + + while (read_idx != write_idx) { + EVENT const *e = &events.queue[read_idx++]; + if (e->target != hobj || e->message != event || (wparam != AX_ANY_WPARAM && e->wParam != wparam)) + events.queue[new_write++] = *e; + } + events.write = new_write; +} + void axNotifyFileDropEvent(char const *filename, float x, float y) { diff --git a/windows/windows_event.c b/windows/windows_event.c index f6ef540..83ddba7 100644 --- a/windows/windows_event.c +++ b/windows/windows_event.c @@ -495,6 +495,21 @@ axRemoveFromQueue(void *hobj) axCancelTimer(s_timers[i].id); } +void +axRemoveMessages(void *hobj, uint32_t event, uint32_t wparam) +{ + WORD read_idx = events.read; + WORD write_idx = events.write; + WORD new_write = events.read; + + while (read_idx != write_idx) { + EVENT const *e = &events.queue[read_idx++]; + if (e->target != hobj || e->message != event || (wparam != AX_ANY_WPARAM && e->wParam != wparam)) + events.queue[new_write++] = *e; + } + events.write = new_write; +} + void axNotifyFileDropEvent(char const *filename, float x, float y) { diff --git a/x11/x11_event.c b/x11/x11_event.c index 41d9ef5..1ca691b 100644 --- a/x11/x11_event.c +++ b/x11/x11_event.c @@ -765,6 +765,21 @@ axRemoveFromQueue(void* hobj) #endif } +void +axRemoveMessages(void* hobj, uint32_t event, uint32_t wparam) +{ + WORD read_idx = events.read; + WORD write_idx = events.write; + WORD new_write = events.read; + + while (read_idx != write_idx) { + EVENT const *e = &events.queue[read_idx++]; + if (e->target != hobj || e->message != event || (wparam != AX_ANY_WPARAM && e->wParam != wparam)) + events.queue[new_write++] = *e; + } + events.write = new_write; +} + void axNotifyFileDropEvent(char const* filename, float x, float y) {