#import #include #include #include #include /* Reject character queries on modifier-only events, as AppKit does. */ @interface InputSample : NSObject @property NSEventType type; @property unsigned short keyCode; @property NSEventModifierFlags modifierFlags; @property(copy) NSString *label; @property(copy) NSString *shiftedLabel; @property NSPoint locationInWindow; @property CGFloat scrollingDeltaX; @property CGFloat scrollingDeltaY; @property BOOL hasPreciseScrollingDeltas; @end @implementation InputSample - (NSString *)charactersByApplyingModifiers:(NSEventModifierFlags)modifiers { assert(self.type == NSEventTypeKeyDown || self.type == NSEventTypeKeyUp); return modifiers & NSEventModifierFlagShift ? self.shiftedLabel : self.label; } - (void)dealloc { [_label release]; [_shiftedLabel release]; [super dealloc]; } @end static struct yasl_input_event next(struct yasl_surface *surface, enum yasl_input_kind kind) { struct yasl_input_event event; assert(yasl_surface_input_poll(surface, &event)); assert(event.kind == kind && event.surface == surface); return event; } static void empty(struct yasl_surface *surface) { struct yasl_input_event event; assert(!yasl_surface_input_poll(surface, &event)); } int main(void) { @autoreleasepool { struct yasl_display *display = yasl_display_open(NULL, NULL); assert(display); struct yasl_window_spec spec = {.title = "Keyboard test", .size = {320, 240}}; struct yasl_surface *surface = yasl_surface_create_toplevel(display, &spec); assert(surface); NSWindow *window = (NSWindow *)yasl_surface_get_native_handle(surface); NSView *view = (id)window.contentView; struct yasl_input_event event; while (yasl_surface_input_poll(surface, &event)) {} InputSample *sample = [InputSample new]; NSEvent *native = (NSEvent *)sample; sample.type = NSEventTypeKeyUp; sample.keyCode = 0; /* US A, even with a different active layout. */ sample.label = @"q"; sample.shiftedLabel = @"Q"; [view keyUp:native]; event = next(surface, YASL_INPUT_KEY); assert(event.key.key == YASL_KEY_A && event.key.native_code == 0); assert(event.key.action == YASL_KEY_RELEASE); assert(event.key.codepoint == 'q' && event.key.shifted == 'Q'); empty(surface); sample.label = @"😀"; /* One scalar, two UTF-16 units. */ sample.shiftedLabel = @"ab"; [view keyUp:native]; event = next(surface, YASL_INPUT_KEY); assert(event.key.codepoint == 0x1f600 && event.key.shifted == 0); sample.type = NSEventTypeFlagsChanged; sample.keyCode = 56; /* Left Shift. */ sample.modifierFlags = NSEventModifierFlagShift | 0x0002; [view flagsChanged:native]; event = next(surface, YASL_INPUT_KEY); assert(event.key.key == YASL_KEY_LEFT_SHIFT && event.key.action == YASL_KEY_PRESS); assert(event.key.mods == YASL_MOD_SHIFT && !event.key.codepoint && !event.key.shifted); sample.modifierFlags = 0; [view flagsChanged:native]; assert(next(surface, YASL_INPUT_KEY).key.action == YASL_KEY_RELEASE); /* Native text services own preedit and commits. Marked text is not an * insertion, and commits retain all UTF-8 bytes, including non-BMP. */ [view setMarkedText:@"é" selectedRange:NSMakeRange(1, 0) replacementRange:NSMakeRange(NSNotFound, 0)]; assert(strcmp(yasl_surface_ime_preedit(surface), "é") == 0); empty(surface); [view insertText:@"😀é" replacementRange:NSMakeRange(NSNotFound, 0)]; event = next(surface, YASL_INPUT_TEXT); assert(event.text.len == strlen("😀é") && strcmp(event.text.text, "😀é") == 0); assert(!yasl_surface_ime_preedit(surface)); empty(surface); /* Exercise the keyDown -> interpretKeyEvents -> insertText route with * an actual AppKit key event rather than the strict event double. */ NSEvent *press = [NSEvent keyEventWithType:NSEventTypeKeyDown location:NSZeroPoint modifierFlags:0 timestamp:0 windowNumber:window.windowNumber context:nil characters:@"a" charactersIgnoringModifiers:@"a" isARepeat:NO keyCode:0]; [view keyDown:press]; assert(next(surface, YASL_INPUT_KEY).key.key == YASL_KEY_A); event = next(surface, YASL_INPUT_TEXT); assert(event.text.len == 1 && event.text.text[0] == 'a'); empty(surface); sample.type = NSEventTypeScrollWheel; sample.hasPreciseScrollingDeltas = YES; sample.scrollingDeltaX = -2.5; sample.scrollingDeltaY = 1.25; [view scrollWheel:native]; event = next(surface, YASL_INPUT_MOUSE); assert(event.mouse.kind == YASL_PTR_SCROLL); assert(event.mouse.scroll.x == 0.25 && event.mouse.scroll.y == -0.125); sample.hasPreciseScrollingDeltas = NO; sample.scrollingDeltaX = 1; sample.scrollingDeltaY = -1; [view scrollWheel:native]; event = next(surface, YASL_INPUT_MOUSE); assert(event.mouse.scroll.x == -1 && event.mouse.scroll.y == 1); sample.scrollingDeltaX = NAN; [view scrollWheel:native]; empty(surface); [sample release]; yasl_surface_destroy(surface); yasl_display_close(display); } return 0; }