123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432 |
- // Copyright 2019 The Chromium Authors. All rights reserved.
- // Use of this source code is governed by a BSD-style license that can be
- // found in the LICENSE file.
- #import "ios/testing/hardware_keyboard_util.h"
- #import "base/test/ios/wait_util.h"
- #if !defined(__has_feature) || !__has_feature(objc_arc)
- #error "This file requires ARC support."
- #endif
- #pragma mark - Private API from IOKit to support CreateHIDKeyEvent
- typedef UInt32 IOOptionBits;
- typedef struct __IOHIDEvent* IOHIDEventRef;
- extern "C" {
- // This function is privately defined in IOKit framework.
- IOHIDEventRef IOHIDEventCreateKeyboardEvent(CFAllocatorRef,
- uint64_t,
- uint32_t,
- uint32_t,
- boolean_t,
- IOOptionBits);
- }
- // This enum is defined in IOKit framework.
- typedef enum {
- kUIKeyboardInputRepeat = 1 << 0,
- kUIKeyboardInputPopupVariant = 1 << 1,
- kUIKeyboardInputMultitap = 1 << 2,
- kUIKeyboardInputSkipCandidateSelection = 1 << 3,
- kUIKeyboardInputDeadKey = 1 << 4,
- kUIKeyboardInputModifierFlagsChanged = 1 << 5,
- kUIKeyboardInputFlick = 1 << 6,
- kUIKeyboardInputPreProcessed = 1 << 7,
- } UIKeyboardInputFlags;
- // This enum is defined in IOKit framework.
- enum {
- kHIDUsage_KeyboardA = 0x04,
- kHIDUsage_Keyboard1 = 0x1E,
- kHIDUsage_Keyboard2 = 0x1F,
- kHIDUsage_Keyboard3 = 0x20,
- kHIDUsage_Keyboard4 = 0x21,
- kHIDUsage_Keyboard5 = 0x22,
- kHIDUsage_Keyboard6 = 0x23,
- kHIDUsage_Keyboard7 = 0x24,
- kHIDUsage_Keyboard8 = 0x25,
- kHIDUsage_Keyboard9 = 0x26,
- kHIDUsage_Keyboard0 = 0x27,
- kHIDUsage_KeyboardReturnOrEnter = 0x28,
- kHIDUsage_KeyboardEscape = 0x29,
- kHIDUsage_KeyboardDeleteOrBackspace = 0x2A,
- kHIDUsage_KeyboardTab = 0x2B,
- kHIDUsage_KeyboardSpacebar = 0x2C,
- kHIDUsage_KeyboardHyphen = 0x2D,
- kHIDUsage_KeyboardEqualSign = 0x2E,
- kHIDUsage_KeyboardOpenBracket = 0x2F,
- kHIDUsage_KeyboardCloseBracket = 0x30,
- kHIDUsage_KeyboardBackslash = 0x31,
- kHIDUsage_KeyboardSemicolon = 0x33,
- kHIDUsage_KeyboardQuote = 0x34,
- kHIDUsage_KeyboardGraveAccentAndTilde = 0x35,
- kHIDUsage_KeyboardComma = 0x36,
- kHIDUsage_KeyboardPeriod = 0x37,
- kHIDUsage_KeyboardSlash = 0x38,
- kHIDUsage_KeyboardCapsLock = 0x39,
- kHIDUsage_KeyboardF1 = 0x3A,
- kHIDUsage_KeyboardF12 = 0x45,
- kHIDUsage_KeyboardPrintScreen = 0x46,
- kHIDUsage_KeyboardInsert = 0x49,
- kHIDUsage_KeyboardHome = 0x4A,
- kHIDUsage_KeyboardPageUp = 0x4B,
- kHIDUsage_KeyboardDeleteForward = 0x4C,
- kHIDUsage_KeyboardEnd = 0x4D,
- kHIDUsage_KeyboardPageDown = 0x4E,
- kHIDUsage_KeyboardRightArrow = 0x4F,
- kHIDUsage_KeyboardLeftArrow = 0x50,
- kHIDUsage_KeyboardDownArrow = 0x51,
- kHIDUsage_KeyboardUpArrow = 0x52,
- kHIDUsage_KeypadNumLock = 0x53,
- kHIDUsage_KeyboardF13 = 0x68,
- kHIDUsage_KeyboardF24 = 0x73,
- kHIDUsage_KeyboardMenu = 0x76,
- kHIDUsage_KeypadComma = 0x85,
- kHIDUsage_KeyboardLeftControl = 0xE0,
- kHIDUsage_KeyboardLeftShift = 0xE1,
- kHIDUsage_KeyboardLeftAlt = 0xE2,
- kHIDUsage_KeyboardLeftGUI = 0xE3,
- kHIDUsage_KeyboardRightControl = 0xE4,
- kHIDUsage_KeyboardRightShift = 0xE5,
- kHIDUsage_KeyboardRightAlt = 0xE6,
- kHIDUsage_KeyboardRightGUI = 0xE7,
- };
- // This function is privately defined in IOKit framework.
- static uint32_t keyCodeForFunctionKey(NSString* key) {
- // Compare the input string with the function-key names (i.e. "F1",...,"F24").
- for (int i = 1; i <= 12; ++i) {
- if ([key isEqualToString:[NSString stringWithFormat:@"F%d", i]])
- return kHIDUsage_KeyboardF1 + i - 1;
- }
- for (int i = 13; i <= 24; ++i) {
- if ([key isEqualToString:[NSString stringWithFormat:@"F%d", i]])
- return kHIDUsage_KeyboardF13 + i - 13;
- }
- return 0;
- }
- // This function is privately defined in IOKit framework.
- static inline uint32_t hidUsageCodeForCharacter(NSString* key) {
- const int uppercaseAlphabeticOffset = 'A' - kHIDUsage_KeyboardA;
- const int lowercaseAlphabeticOffset = 'a' - kHIDUsage_KeyboardA;
- const int numericNonZeroOffset = '1' - kHIDUsage_Keyboard1;
- if (key.length == 1) {
- // Handle alphanumeric characters and basic symbols.
- int keyCode = [key characterAtIndex:0];
- if (97 <= keyCode && keyCode <= 122) // Handle a-z.
- return keyCode - lowercaseAlphabeticOffset;
- if (65 <= keyCode && keyCode <= 90) // Handle A-Z.
- return keyCode - uppercaseAlphabeticOffset;
- if (49 <= keyCode && keyCode <= 57) // Handle 1-9.
- return keyCode - numericNonZeroOffset;
- // Handle all other cases.
- switch (keyCode) {
- case '`':
- case '~':
- return kHIDUsage_KeyboardGraveAccentAndTilde;
- case '!':
- return kHIDUsage_Keyboard1;
- case '@':
- return kHIDUsage_Keyboard2;
- case '#':
- return kHIDUsage_Keyboard3;
- case '$':
- return kHIDUsage_Keyboard4;
- case '%':
- return kHIDUsage_Keyboard5;
- case '^':
- return kHIDUsage_Keyboard6;
- case '&':
- return kHIDUsage_Keyboard7;
- case '*':
- return kHIDUsage_Keyboard8;
- case '(':
- return kHIDUsage_Keyboard9;
- case ')':
- case '0':
- return kHIDUsage_Keyboard0;
- case '-':
- case '_':
- return kHIDUsage_KeyboardHyphen;
- case '=':
- case '+':
- return kHIDUsage_KeyboardEqualSign;
- case '\b':
- return kHIDUsage_KeyboardDeleteOrBackspace;
- case '\t':
- return kHIDUsage_KeyboardTab;
- case '[':
- case '{':
- return kHIDUsage_KeyboardOpenBracket;
- case ']':
- case '}':
- return kHIDUsage_KeyboardCloseBracket;
- case '\\':
- case '|':
- return kHIDUsage_KeyboardBackslash;
- case ';':
- case ':':
- return kHIDUsage_KeyboardSemicolon;
- case '\'':
- case '"':
- return kHIDUsage_KeyboardQuote;
- case '\r':
- case '\n':
- return kHIDUsage_KeyboardReturnOrEnter;
- case ',':
- case '<':
- return kHIDUsage_KeyboardComma;
- case '.':
- case '>':
- return kHIDUsage_KeyboardPeriod;
- case '/':
- case '?':
- return kHIDUsage_KeyboardSlash;
- case ' ':
- return kHIDUsage_KeyboardSpacebar;
- }
- }
- uint32_t keyCode = keyCodeForFunctionKey(key);
- if (keyCode)
- return keyCode;
- if ([key isEqualToString:@"capsLock"] || [key isEqualToString:@"capsLockKey"])
- return kHIDUsage_KeyboardCapsLock;
- if ([key isEqualToString:@"pageUp"])
- return kHIDUsage_KeyboardPageUp;
- if ([key isEqualToString:@"pageDown"])
- return kHIDUsage_KeyboardPageDown;
- if ([key isEqualToString:@"home"])
- return kHIDUsage_KeyboardHome;
- if ([key isEqualToString:@"insert"])
- return kHIDUsage_KeyboardInsert;
- if ([key isEqualToString:@"end"])
- return kHIDUsage_KeyboardEnd;
- if ([key isEqualToString:@"escape"])
- return kHIDUsage_KeyboardEscape;
- if ([key isEqualToString:@"return"] || [key isEqualToString:@"enter"])
- return kHIDUsage_KeyboardReturnOrEnter;
- if ([key isEqualToString:@"leftArrow"])
- return kHIDUsage_KeyboardLeftArrow;
- if ([key isEqualToString:@"rightArrow"])
- return kHIDUsage_KeyboardRightArrow;
- if ([key isEqualToString:@"upArrow"])
- return kHIDUsage_KeyboardUpArrow;
- if ([key isEqualToString:@"downArrow"])
- return kHIDUsage_KeyboardDownArrow;
- if ([key isEqualToString:@"delete"])
- return kHIDUsage_KeyboardDeleteOrBackspace;
- if ([key isEqualToString:@"forwardDelete"])
- return kHIDUsage_KeyboardDeleteForward;
- if ([key isEqualToString:@"leftCommand"] || [key isEqualToString:@"metaKey"])
- return kHIDUsage_KeyboardLeftGUI;
- if ([key isEqualToString:@"rightCommand"])
- return kHIDUsage_KeyboardRightGUI;
- if ([key isEqualToString:@"clear"]) // Num Lock / Clear
- return kHIDUsage_KeypadNumLock;
- if ([key isEqualToString:@"leftControl"] || [key isEqualToString:@"ctrlKey"])
- return kHIDUsage_KeyboardLeftControl;
- if ([key isEqualToString:@"rightControl"])
- return kHIDUsage_KeyboardRightControl;
- if ([key isEqualToString:@"leftShift"] || [key isEqualToString:@"shiftKey"])
- return kHIDUsage_KeyboardLeftShift;
- if ([key isEqualToString:@"rightShift"])
- return kHIDUsage_KeyboardRightShift;
- if ([key isEqualToString:@"leftAlt"] || [key isEqualToString:@"altKey"])
- return kHIDUsage_KeyboardLeftAlt;
- if ([key isEqualToString:@"rightAlt"])
- return kHIDUsage_KeyboardRightAlt;
- if ([key isEqualToString:@"numpadComma"])
- return kHIDUsage_KeypadComma;
- return 0;
- }
- // These are privately defined in IOKit framework.
- enum { kHIDPage_KeyboardOrKeypad = 0x07, kHIDPage_VendorDefinedStart = 0xFF00 };
- enum {
- kIOHIDEventOptionNone = 0,
- };
- #pragma mark - Private API to fake keyboard events.
- // Convenience wrapper for IOHIDEventCreateKeyboardEvent.
- IOHIDEventRef CreateHIDKeyEvent(NSString* character,
- uint64_t timestamp,
- bool isKeyDown) {
- return IOHIDEventCreateKeyboardEvent(
- kCFAllocatorDefault, timestamp, kHIDPage_KeyboardOrKeypad,
- hidUsageCodeForCharacter(character), isKeyDown, kIOHIDEventOptionNone);
- }
- // A fake class that mirrors UIPhysicalKeyboardEvent private class' fields.
- // This class is never used, but it allows to call UIPhysicalKeyboardEvent's API
- // by casting an instance of UIPhysicalKeyboardEvent to PhysicalKeyboardEvent.
- @interface PhysicalKeyboardEvent : UIEvent
- + (id)_eventWithInput:(id)arg1 inputFlags:(int)arg2;
- - (void)_setHIDEvent:(IOHIDEventRef)event keyboard:(void*)gsKeyboard;
- @property(nonatomic) UIKeyModifierFlags _modifierFlags;
- @end
- // Private API in UIKit.
- @interface UIApplication ()
- - (void)handleKeyUIEvent:(id)event;
- - (void)handleKeyHIDEvent:(id)event;
- @end
- #pragma mark - Implementation
- namespace {
- // Delay between simulated keyboard press events.
- const double kKeyPressDelay = 0.02;
- // Utility to describe modifier flags. Useful in debugging.
- [[maybe_unused]] NSString* DescribeFlags(UIKeyModifierFlags flags);
- NSString* DescribeFlags(UIKeyModifierFlags flags) {
- NSMutableString* s = [NSMutableString new];
- if (flags & UIKeyModifierAlphaShift) {
- [s appendString:@"CapsLock+"];
- }
- if (flags & UIKeyModifierShift) {
- [s appendString:@"Shift+"];
- }
- if (flags & UIKeyModifierControl) {
- [s appendString:@"Ctrl+"];
- }
- if (flags & UIKeyModifierAlternate) {
- [s appendString:@"Alt+"];
- }
- if (flags & UIKeyModifierCommand) {
- [s appendString:@"Command+"];
- }
- if (flags & UIKeyModifierNumericPad) {
- [s appendString:@"NumPad+"];
- }
- return s;
- }
- // Sends an individual keyboard press event.
- void SendKBEventWithModifiers(UIKeyModifierFlags flags, NSString* input) {
- // Fake up an event.
- PhysicalKeyboardEvent* keyboardEvent =
- [NSClassFromString(@"UIPhysicalKeyboardEvent") _eventWithInput:input
- inputFlags:0];
- keyboardEvent._modifierFlags = flags;
- IOHIDEventRef hidEvent =
- CreateHIDKeyEvent(input, keyboardEvent.timestamp, true);
- [keyboardEvent _setHIDEvent:hidEvent keyboard:0];
- [[UIApplication sharedApplication] handleKeyUIEvent:keyboardEvent];
- }
- // Lifts the keypresses one by one.
- // Once all keypresses are reversed, executes |completion| on the main thread.
- void UnwindFakeKeyboardPressWithFlags(UIKeyModifierFlags flags,
- NSString* input,
- void (^completion)()) {
- if (flags == 0 && input.length == 0) {
- if (completion) {
- completion();
- }
- return;
- }
- dispatch_after(
- dispatch_time(DISPATCH_TIME_NOW, kKeyPressDelay * NSEC_PER_SEC),
- dispatch_get_main_queue(), ^{
- // First release all the non-modifier keys.
- if (input.length > 0) {
- NSString* remainingInput = [input substringFromIndex:1];
- SendKBEventWithModifiers(flags, remainingInput);
- UnwindFakeKeyboardPressWithFlags(flags, remainingInput, completion);
- return;
- }
- // Unwind the modifier keys.
- for (int i = 16; i < 22; i++) {
- UIKeyModifierFlags flag = 1 << i;
- if (flags & flag) {
- SendKBEventWithModifiers(flags & ~flag, input);
- UnwindFakeKeyboardPressWithFlags(flags & ~flag, input, completion);
- }
- }
- });
- }
- // Programmatically simulates pressing the keys one by one, starting with
- // modifier keys and moving to input string through recursion, then calls
- // unwindFakeKeyboardPressWithFlags to release the pressed keys in reverse
- // order.
- // Once all key downs and key ups are simulated, executes |completion| on the
- // main thread.
- void SimulatePhysicalKeyboardEventInternal(UIKeyModifierFlags flags,
- NSString* input,
- UIKeyModifierFlags previousFlags,
- NSString* previousInput,
- void (^completion)()) {
- dispatch_after(
- dispatch_time(DISPATCH_TIME_NOW, kKeyPressDelay * NSEC_PER_SEC),
- dispatch_get_main_queue(), ^{
- // First dial in all the modifier keys.
- for (int i = 15; i < 25; i++) {
- UIKeyModifierFlags flag = 1 << i;
- if (flags & flag) {
- SendKBEventWithModifiers(previousFlags ^ flag, previousInput);
- SimulatePhysicalKeyboardEventInternal(flags & ~flag, input,
- previousFlags ^ flag,
- previousInput, completion);
- return;
- }
- }
- // Now add the next input char.
- if (input.length > 0) {
- NSString* pressedKey = [input substringToIndex:1];
- NSString* remainingInput = [input substringFromIndex:1];
- NSString* alreadyPressedString =
- [previousInput stringByAppendingString:pressedKey];
- SendKBEventWithModifiers(previousFlags, alreadyPressedString);
- SimulatePhysicalKeyboardEventInternal(
- flags, remainingInput, previousFlags, alreadyPressedString,
- completion);
- } else {
- // Time to unwind the presses.
- UnwindFakeKeyboardPressWithFlags(previousFlags, previousInput,
- completion);
- }
- });
- }
- } // namespace
- #pragma mark - Public
- namespace chrome_test_util {
- void SimulatePhysicalKeyboardEvent(UIKeyModifierFlags flags, NSString* input) {
- __block BOOL keyPressesFinished = NO;
- SimulatePhysicalKeyboardEventInternal(flags, input, 0, @"", ^{
- keyPressesFinished = YES;
- });
- BOOL __unused result = base::test::ios::WaitUntilConditionOrTimeout(1.0, ^{
- return keyPressesFinished;
- });
- }
- } // namespace chrome_test_util
|