diff options
| author | Berke Güzel <wenekar1@gmail.com> | 2026-10-12 13:00:53 +0300 |
|---|---|---|
| committer | Berke Güzel <wenekar1@gmail.com> | 2026-10-12 13:00:53 +0300 |
| commit | b20263cf9b6d85c7e5919497b5d36b4c81463b15 (patch) | |
| tree | d46d216fdde20dc36023a9b5ef285e8ba4bf69fd /tests/x11 | |
| parent | 2984d50b78e57c35c068136ca9e65416f193d21f (diff) | |
feat: implement X11 and Cocoa drop-targets, popups, and selection gaps
Resolve gaps identified in yasl-gaps-report.md:
- X11:
- Add XDND v5 file drop support and text/uri-list parsing (YASL_INPUT_DROP_FILES)
- Dispatch surface drop-target events (YASL_DRAG_OVER, YASL_DRAG_LEAVE, YASL_DRAG_DROP, YASL_DROP_OVER, YASL_DROP_LEAVE)
- Implement override-redirect popups (surface_create_popup, surface_popup_reposition)
- Support drag images with override-redirect icon window
- Set YASL_CAP_POPUPS capability
- Handle XIHierarchyChanged reset cleanly
- Cocoa:
- Dispatch surface drop-target events for internal drags and external NSDraggingDestination
- Implement child window popups (cocoa_surface_create_popup, cocoa_surface_popup_reposition)
- Implement primary selection via dedicated pasteboard
- Support drag images with floating borderless NSWindow
- Track pointer buttons and push YASL_INPUT_RESET on focus loss
- Set YASL_CAP_POPUPS and YASL_CAP_PRIMARY_SELECTION capabilities
- Tests:
- Add comprehensive unit test suite in tests/x11/test_tablet.c
Diffstat (limited to 'tests/x11')
| -rw-r--r-- | tests/x11/test_tablet.c | 252 |
1 files changed, 252 insertions, 0 deletions
diff --git a/tests/x11/test_tablet.c b/tests/x11/test_tablet.c index 39717df..c35a266 100644 --- a/tests/x11/test_tablet.c +++ b/tests/x11/test_tablet.c @@ -9,6 +9,257 @@ static struct yasl_mouse_event next(struct x11_surface *surface) return event.mouse; } +struct drag_test_recorder { + int over_count; + int leave_count; + int drop_count; + struct yasl_vector2i last_pos; +}; + +static void on_test_drag(struct yasl_surface *s, enum yasl_drag_event ev, + struct yasl_vector2i pos, void *ud) +{ + (void)s; + struct drag_test_recorder *rec = ud; + rec->last_pos = pos; + if (ev == YASL_DRAG_OVER) rec->over_count++; + else if (ev == YASL_DRAG_LEAVE) rec->leave_count++; + else if (ev == YASL_DRAG_DROP) rec->drop_count++; +} + +struct drop_test_recorder { + int over_count; + int leave_count; + struct yasl_vector2i last_pos; +}; + +static void on_test_drop(struct yasl_surface *s, enum yasl_drop_event ev, + struct yasl_vector2i pos, void *ud) +{ + (void)s; + struct drop_test_recorder *rec = ud; + rec->last_pos = pos; + if (ev == YASL_DROP_OVER) rec->over_count++; + else if (ev == YASL_DROP_LEAVE) rec->leave_count++; +} + +static void on_test_drag_end(void *ud, enum yasl_drag_end how) +{ + enum yasl_drag_end *res = ud; + *res = how; +} + +static void test_x11_gaps(struct x11_display *d, struct x11_surface *s) +{ + /* 1. Caps */ + assert(caps(&d->base) & YASL_CAP_POPUPS); + assert(caps(&d->base) & YASL_CAP_DRAG); + assert(caps(&d->base) & YASL_CAP_DRAG_CARRY); + + /* 2. Popups: spec validation, calculation, creation, reposition, destruction */ + struct yasl_popup_spec invalid_specs[] = { + {.size = {0, 100}}, + {.size = {100, 0}}, + {.size = {35000, 100}}, + {.size = {100, 100}, .content_offset = {-1, 0}}, + {.size = {100, 100}, .content_offset = {100, 0}, .content_size = {10, 0}}, + {.size = {100, 100}, .anchor = {INT_MIN, 0, 1, 1}}, + }; + for (size_t i = 0; i < sizeof(invalid_specs)/sizeof(invalid_specs[0]); i++) { + assert(!popup_spec_valid(s, &invalid_specs[i])); + assert(!surface_create_popup(&s->base, &invalid_specs[i])); + } + + struct yasl_popup_spec valid_spec = { + .size = {100, 120}, + .anchor = {30, 40, 50, 60}, + .beside = false, + .content_offset = {5, 10}, + }; + assert(popup_spec_valid(s, &valid_spec)); + int x = 0, y = 0; + popup_calculate_position(d, s, &valid_spec, &x, &y); + assert(x == 30 - 5); + assert(y == 40 + 60 - 10); + + valid_spec.beside = true; + popup_calculate_position(d, s, &valid_spec, &x, &y); + assert(x == 30 + 50 - 5); + assert(y == 40 - 10); + + struct yasl_surface *popup_base = surface_create_popup(&s->base, &valid_spec); + assert(popup_base != NULL); + struct x11_surface *popup = (struct x11_surface *)popup_base; + assert(popup->is_popup); + assert(popup->parent == s); + assert(popup->size.x == 100 && popup->size.y == 120); + + struct yasl_popup_spec repo_spec = { + .size = {150, 180}, + .anchor = {10, 20, 30, 40}, + .beside = false, + .content_offset = {0, 0}, + }; + assert(surface_popup_reposition(popup_base, &repo_spec)); + assert(popup->size.x == 150 && popup->size.y == 180); + + surface_destroy(popup_base); + assert(d->surfaces == s); + + /* 3. Drag-and-drop target events */ + struct x11_surface s2 = {.base.display = &d->base, .window = 43}; + s->next = &s2; + s2.next = NULL; + + struct drag_test_recorder rec1 = {0}; + struct drag_test_recorder rec2 = {0}; + struct yasl_surface_cbs cbs1 = {.drag = on_test_drag}; + struct yasl_surface_cbs cbs2 = {.drag = on_test_drag}; + set_callbacks(&s->base, &cbs1, &rec1); + set_callbacks(&s2.base, &cbs2, &rec2); + + enum yasl_drag_end end_reason = (enum yasl_drag_end)-1; + struct yasl_drag_listener l = {.end = on_test_drag_end}; + assert(drag_start(&d->base, &s->base, NULL, &l, &end_reason)); + assert(d->drag.active); + + /* Motion on s (window 42) -> YASL_DRAG_OVER on s */ + XIDeviceEvent mev = {.evtype = XI_Motion, .sourceid = 8, .event = 42, .event_x = 12.0, .event_y = 34.0}; + XGenericEventCookie cookie = {.evtype = XI_Motion, .data = &mev}; + xi_event(d, &cookie); + assert(rec1.over_count == 1); + assert(rec1.last_pos.x == 12 && rec1.last_pos.y == 34); + + /* Motion on s2 (window 43) -> YASL_DRAG_LEAVE on s, YASL_DRAG_OVER on s2 */ + mev.event = 43; + mev.event_x = 56.0; + mev.event_y = 78.0; + xi_event(d, &cookie); + assert(rec1.leave_count == 1); + assert(rec2.over_count == 1); + assert(rec2.last_pos.x == 56 && rec2.last_pos.y == 78); + + /* ButtonRelease -> YASL_DRAG_DROP on s2 and drag end */ + XIDeviceEvent rel = {.evtype = XI_ButtonRelease, .sourceid = 8, .event = 43, .detail = 1}; + cookie = (XGenericEventCookie){.evtype = XI_ButtonRelease, .data = &rel}; + xi_event(d, &cookie); + assert(rec2.drop_count == 1); + assert(end_reason == YASL_DRAG_DROPPED); + assert(!d->drag.active); + + /* Drag cancel */ + assert(drag_start(&d->base, &s->base, NULL, &l, &end_reason)); + cookie = (XGenericEventCookie){.evtype = XI_Motion, .data = &mev}; + mev.event = 42; + xi_event(d, &cookie); + assert(rec1.over_count == 2); + drag_cancel(&d->base); + assert(rec1.leave_count == 2); + assert(end_reason == YASL_DRAG_CANCELLED); + assert(!d->drag.active); + + /* Hierarchy change cancels drag and pushes reset */ + input_queue_clear(&s->input); + assert(drag_start(&d->base, &s->base, NULL, &l, &end_reason)); + XIHierarchyEvent hiev = {.evtype = XI_HierarchyChanged, .flags = XISlaveRemoved}; + cookie = (XGenericEventCookie){.evtype = XI_HierarchyChanged, .data = &hiev}; + xi_event(d, &cookie); + assert(end_reason == YASL_DRAG_CANCELLED); + assert(!d->drag.active); + struct yasl_input_event reset_ev; + assert(yasl_surface_input_poll(&s->base, &reset_ev)); + assert(reset_ev.kind == YASL_INPUT_RESET); + + /* 4. Drag image parameter */ + uint32_t pixels[16] = {0xFF00FF00}; + struct yasl_drag_image img = {.pixels = pixels, .size = {4, 4}, .hotspot = {2, 2}}; + assert(drag_start(&d->base, &s->base, &img, &l, &end_reason)); + assert(d->drag.active); + assert(drag_carry(&s->base, (struct yasl_vector2i){10, 10})); + drag_cancel(&d->base); + + /* 5. XDND drop-target events and URI parsing */ + size_t out_len = 0; + char *parsed = parse_file_uris("file:///tmp/doc.txt\r\n", 21, &out_len); + assert(parsed && !strcmp(parsed, "file:///tmp/doc.txt\r\n") && out_len == 21); + free(parsed); + + parsed = parse_file_uris("# comment\nfile:///a\nfile:///b\n", 30, &out_len); + assert(parsed && !strcmp(parsed, "file:///a\r\nfile:///b\r\n") && out_len == 22); + free(parsed); + + parsed = parse_file_uris("http://example.com\r\n", 20, &out_len); + assert(parsed == NULL && out_len == 0); + + /* Test XDND ClientMessages */ + d->protocols = 1; + d->delete_window = 2; + d->xdnd_enter = 10; + d->xdnd_position = 11; + d->xdnd_leave = 12; + d->xdnd_drop = 13; + d->uri_list = 20; + + struct drop_test_recorder drec = {0}; + cbs1.drop = on_test_drop; + set_callbacks(&s->base, &cbs1, &drec); + + XClientMessageEvent enter_msg = { + .type = ClientMessage, + .message_type = d->xdnd_enter, + .format = 32, + .data.l = {99, 5 << 24, d->uri_list, 0, 0}, + }; + handle_client_message(d, s, &enter_msg); + assert(d->xdnd.accepts); + + XClientMessageEvent pos_msg = { + .type = ClientMessage, + .message_type = d->xdnd_position, + .format = 32, + .data.l = {99, 0, (100 << 16) | 200, 1000, 0}, + }; + handle_client_message(d, s, &pos_msg); + assert(drec.over_count == 1); + assert(drec.last_pos.x == 100 && drec.last_pos.y == 200); + + XClientMessageEvent leave_msg = { + .type = ClientMessage, + .message_type = d->xdnd_leave, + .format = 32, + .data.l = {99, 0, 0, 0, 0}, + }; + handle_client_message(d, s, &leave_msg); + assert(drec.leave_count == 1); + + handle_client_message(d, s, &enter_msg); + handle_client_message(d, s, &pos_msg); + assert(drec.over_count == 2); + + XClientMessageEvent drop_msg = { + .type = ClientMessage, + .message_type = d->xdnd_drop, + .format = 32, + .data.l = {99, 0, 1005, 0, 0}, + }; + handle_client_message(d, s, &drop_msg); + assert(drec.leave_count == 2); + assert(d->pending_drop.active); + + /* Push YASL_INPUT_DROP_FILES */ + push(s, (struct yasl_input_event){.kind = YASL_INPUT_DROP_FILES}, "file:///sample.png\r\n", 20); + struct yasl_input_event drop_ev; + assert(yasl_surface_input_poll(&s->base, &drop_ev)); + assert(drop_ev.kind == YASL_INPUT_DROP_FILES); + assert(drop_ev.drop_files.len == 20); + assert(!strcmp(drop_ev.drop_files.files, "file:///sample.png\r\n")); + + /* Clean up s2 */ + s->next = NULL; + input_queue_clear(&s->input); + input_queue_clear(&s2.input); +} + int main(void) { struct x11_display display = { @@ -99,5 +350,6 @@ int main(void) assert(tilt_value(&pointer.tilt_x) == 90); display.pointers = NULL; input_queue_clear(&surface.input); + test_x11_gaps(&display, &surface); return 0; } |
