/* * Copyright (C) 2004, 2006-2011, 2014, 2019 Apple Inc. All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS'' * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF * THE POSSIBILITY OF SUCH DAMAGE. */ #import "config.h" #import "PlatformEventFactoryIOS.h" #if PLATFORM(IOS_FAMILY) #import "IntPoint.h" #import "KeyEventCocoa.h" #import "KeyEventCodesIOS.h" #import "Logging.h" #import "WAKAppKitStubs.h" #import "WebEvent.h" #import "WindowsKeyboardCodes.h" #import namespace WebCore { static OptionSet modifiersForEvent(WebEvent *event) { OptionSet modifiers; if (event.modifierFlags & WebEventFlagMaskShiftKey) modifiers.add(PlatformEvent::Modifier::ShiftKey); if (event.modifierFlags & WebEventFlagMaskControlKey) modifiers.add(PlatformEvent::Modifier::ControlKey); if (event.modifierFlags & WebEventFlagMaskOptionKey) modifiers.add(PlatformEvent::Modifier::AltKey); if (event.modifierFlags & WebEventFlagMaskCommandKey) modifiers.add(PlatformEvent::Modifier::MetaKey); if (event.modifierFlags & WebEventFlagMaskLeftCapsLockKey) modifiers.add(PlatformEvent::Modifier::CapsLockKey); return modifiers; } static inline IntPoint pointForEvent(WebEvent *event) { return IntPoint(event.locationInWindow); } static inline IntPoint globalPointForEvent(WebEvent *event) { // iOS WebKit works as if it is full screen. Therefore Web coords are Global coords. return pointForEvent(event); } static PlatformEvent::Type mouseEventType(WebEvent *event) { switch (event.type) { case WebEventMouseDown: return PlatformEvent::MousePressed; case WebEventMouseUp: return PlatformEvent::MouseReleased; case WebEventMouseMoved: return PlatformEvent::MouseMoved; default: ASSERT_NOT_REACHED(); return PlatformEvent::MousePressed; } } class PlatformMouseEventBuilder : public PlatformMouseEvent { public: PlatformMouseEventBuilder(WebEvent *event) { m_type = mouseEventType(event); m_timestamp = WallTime::now(); m_position = pointForEvent(event); m_globalPosition = globalPointForEvent(event); m_button = LeftButton; // This has always been the LeftButton on iOS. m_clickCount = 1; // This has always been 1 on iOS. m_modifiers = modifiersForEvent(event); } }; PlatformMouseEvent PlatformEventFactory::createPlatformMouseEvent(WebEvent *event) { return PlatformMouseEventBuilder(event); } class PlatformWheelEventBuilder : public PlatformWheelEvent { public: PlatformWheelEventBuilder(WebEvent *event) { ASSERT(event.type == WebEventScrollWheel); m_type = PlatformEvent::Wheel; m_timestamp = WallTime::now(); m_position = pointForEvent(event); m_globalPosition = globalPointForEvent(event); m_deltaX = event.deltaX; m_deltaY = event.deltaY; m_granularity = ScrollByPixelWheelEvent; // iOS only supports continuous (pixel-mode) scrolling. } }; PlatformWheelEvent PlatformEventFactory::createPlatformWheelEvent(WebEvent *event) { return PlatformWheelEventBuilder(event); } String keyIdentifierForKeyEvent(WebEvent *event) { if (event.keyboardFlags & WebEventKeyboardInputModifierFlagsChanged) { switch (event.keyCode) { case VK_LWIN: // Left Command case VK_APPS: // Right Command return "Meta"_s; case VK_CAPITAL: // Caps Lock return "CapsLock"_s; case VK_LSHIFT: // Left Shift case VK_RSHIFT: // Right Shift return "Shift"_s; case VK_LMENU: // Left Alt case VK_RMENU: // Right Alt return "Alt"_s; case VK_LCONTROL: // Left Ctrl case VK_RCONTROL: // Right Ctrl return "Control"_s; default: ASSERT_NOT_REACHED(); return emptyString(); } } NSString *characters = event.charactersIgnoringModifiers; if ([characters length] != 1) { LOG(Events, "received an unexpected number of characters in key event: %u", [characters length]); return "Unidentified"_s; } return keyIdentifierForCharCode([characters characterAtIndex:0]); } String keyForKeyEvent(WebEvent *event) { if (event.keyboardFlags & WebEventKeyboardInputModifierFlagsChanged) { switch (event.keyCode) { case VK_LWIN: // Left Command case VK_APPS: // Right Command return "Meta"_s; case VK_CAPITAL: // Caps Lock return "CapsLock"_s; case VK_LSHIFT: // Left Shift case VK_RSHIFT: // Right Shift return "Shift"_s; case VK_LMENU: // Left Alt case VK_RMENU: // Right Alt return "Alt"_s; case VK_LCONTROL: // Left Ctrl case VK_RCONTROL: // Right Ctrl return "Control"_s; default: ASSERT_NOT_REACHED(); return "Unidentified"_s; } } // If more than one key is being pressed and the key combination includes one or more modifier keys // that result in the key no longer producing a printable character (e.g., Control + a), then the // key value should be the printable key value that would have been produced if the key had been // typed with the default keyboard layout with no modifier keys except for Shift and AltGr applied. // See . bool isControlDown = event.modifierFlags & WebEventFlagMaskControlKey; NSString *characters = isControlDown ? event.charactersIgnoringModifiers : event.characters; auto length = [characters length]; // characters return an empty string for dead keys. // https://developer.apple.com/reference/appkit/nsevent/1534183-characters // "Dead" is defined here https://w3c.github.io/uievents-key/#keys-composition. if (!length) return "Dead"_s; if (length > 1) return characters; return keyForCharCode([characters characterAtIndex:0]); } // https://w3c.github.io/uievents-code/ String codeForKeyEvent(WebEvent *event) { switch (event.keyCode) { // Keys in the alphanumeric section. case VK_OEM_3: return "Backquote"_s; case VK_OEM_5: return "Backslash"_s; case VK_BACK: return "Backspace"_s; case VK_OEM_4: return "BracketLeft"_s; case VK_OEM_6: return "BracketRight"_s; case VK_OEM_COMMA: return "Comma"_s; case VK_0: return "Digit0"_s; case VK_1: return "Digit1"_s; case VK_2: return "Digit2"_s; case VK_3: return "Digit3"_s; case VK_4: return "Digit4"_s; case VK_5: return "Digit5"_s; case VK_6: return "Digit6"_s; case VK_7: return "Digit7"_s; case VK_8: return "Digit8"_s; case VK_9: return "Digit9"_s; case VK_OEM_PLUS: return "Equal"_s; case VK_OEM_102: return "IntlBackslash"_s; // IntlRo. // IntlYen. case VK_A: return "KeyA"_s; case VK_B: return "KeyB"_s; case VK_C: return "KeyC"_s; case VK_D: return "KeyD"_s; case VK_E: return "KeyE"_s; case VK_F: return "KeyF"_s; case VK_G: return "KeyG"_s; case VK_H: return "KeyH"_s; case VK_I: return "KeyI"_s; case VK_J: return "KeyJ"_s; case VK_K: return "KeyK"_s; case VK_L: return "KeyL"_s; case VK_M: return "KeyM"_s; case VK_N: return "KeyN"_s; case VK_O: return "KeyO"_s; case VK_P: return "KeyP"_s; case VK_Q: return "KeyQ"_s; case VK_R: return "KeyR"_s; case VK_S: return "KeyS"_s; case VK_T: return "KeyT"_s; case VK_U: return "KeyU"_s; case VK_V: return "KeyV"_s; case VK_W: return "KeyW"_s; case VK_X: return "KeyX"_s; case VK_Y: return "KeyY"_s; case VK_Z: return "KeyZ"_s; case VK_OEM_MINUS: return "Minus"_s; case VK_OEM_PERIOD: return "Period"_s; case VK_OEM_7: return "Quote"_s; case VK_OEM_1: return "Semicolon"_s; case VK_OEM_2: return "Slash"_s; // Functional keys in alphanumeric section. case VK_LMENU: return "AltLeft"_s; case VK_RMENU: return "AltRight"_s; case VK_CAPITAL: return "CapsLock"_s; // ContextMenu. case VK_LCONTROL: return "ControlLeft"_s; case VK_RCONTROL: return "ControlRight"_s; case VK_RETURN: return "Enter"_s; // Labeled Return on Apple keyboards. case VK_LWIN: return "MetaLeft"_s; case VK_APPS: return "MetaRight"_s; case VK_LSHIFT: return "ShiftLeft"_s; case VK_RSHIFT: return "ShiftRight"_s; case VK_SPACE: return "Space"_s; case VK_TAB: return "Tab"_s; // Functional keys found on Japanese and Korean keyboards. // Convert. case VK_KANA: return "KanaMode"_s; // Lang1. // Lang2. // Lang3. // Lang4. // Lang5. // NonConvert. // Keys in the ControlPad section. // Delete case VK_END: return "End"_s; case VK_HELP: return "Help"_s; case VK_HOME: return "Home"_s; // Insert: Not present on Apple keyboards. case VK_NEXT: return "PageDown"_s; case VK_PRIOR: return "PageUp"_s; // Keys in the ArrowPad section. case VK_DOWN: return "ArrowDown"_s; case VK_LEFT: return "ArrowLeft"_s; case VK_RIGHT: return "ArrowRight"_s; case VK_UP: return "ArrowUp"_s; // Keys in the Numpad section. case VK_NUMLOCK: return "NumLock"_s; case VK_NUMPAD0: return "Numpad0"_s; case VK_NUMPAD1: return "Numpad1"_s; case VK_NUMPAD2: return "Numpad2"_s; case VK_NUMPAD3: return "Numpad3"_s; case VK_NUMPAD4: return "Numpad4"_s; case VK_NUMPAD5: return "Numpad5"_s; case VK_NUMPAD6: return "Numpad6"_s; case VK_NUMPAD7: return "Numpad7"_s; case VK_NUMPAD8: return "Numpad8"_s; case VK_NUMPAD9: return "Numpad9"_s; case VK_ADD: return "NumpadAdd"_s; // NumpadBackspace. // NumpadClear. // NumpadClearEntry. case VK_SEPARATOR: return "NumpadComma"_s; // On JIS keyboard case VK_DECIMAL: return "NumpadDecimal"_s; case VK_DIVIDE: return "NumpadDivide"_s; // NumpadEnter. case VK_CLEAR: return "NumpadEqual"_s; // NumpadHash. // NumpadMemoryAdd. // NumpadMemoryClear. // NumpadMemoryRecall. // NumpadMemoryStore. // NumpadMemorySubtract. case VK_MULTIPLY: return "NumpadMultiply"_s; // NumpadParenLeft. // NumpadParenRight. // NumpadStar: The specification says to use "NumpadMultiply" for the * key on numeric keypads. case VK_SUBTRACT: return "NumpadSubtract"_s; // Keys in the Function section. case VK_ESCAPE: return "Escape"_s; case VK_F1: return "F1"_s; case VK_F2: return "F2"_s; case VK_F3: return "F3"_s; case VK_F4: return "F4"_s; case VK_F5: return "F5"_s; case VK_F6: return "F6"_s; case VK_F7: return "F7"_s; case VK_F8: return "F8"_s; case VK_F9: return "F9"_s; case VK_F10: return "F10"_s; case VK_F11: return "F11"_s; case VK_F12: return "F12"_s; case VK_F13: return "F13"_s; case VK_F14: return "F14"_s; case VK_F15: return "F15"_s; case VK_F16: return "F16"_s; case VK_F17: return "F17"_s; case VK_F18: return "F18"_s; case VK_F19: return "F19"_s; case VK_F20: return "F20"_s; // Fn: This is typically a hardware key that does not generate a separate code. // FnLock. // PrintScreen. // ScrollLock. // Pause. // Media keys. // BrowserBack. // BrowserFavorites. // BrowserForward. // BrowserHome. // BrowserRefresh. // BrowserSearch. // BrowserStop. // Eject. // LaunchApp1. // LaunchApp2. // LaunchMail. // MediaPlayPause. // MediaSelect. // MediaStop. // MediaTrackNext. // MediaTrackPrevious. // Power. // Sleep. case VK_VOLUME_DOWN: return "AudioVolumeDown"_s; case VK_VOLUME_MUTE: return "AudioVolumeMute"_s; case VK_VOLUME_UP: return "AudioVolumeUp"_s; // WakeUp. // Legacy modifier keys. // Hyper. // Super. // Turbo. // Legacy process control keys. // Abort. // Resume. // Suspend. // Legacy editing keys. // Again. // Copy. // Cut. // Find. // Open. // Paste. // Props. // Select. // Undo. // Keys found on international keyboards. // Hiragana. // Katakana. default: return "Unidentified"_s; } } static bool isKeypadEvent(WebEvent* event) { // Check that this is the type of event that has a keyCode. if (event.type != WebEventKeyDown && event.type != WebEventKeyUp) return false; switch (event.keyCode) { case VK_CLEAR: // Num Pad Clear case VK_OEM_PLUS: // Num Pad = case VK_DIVIDE: case VK_MULTIPLY: case VK_SUBTRACT: case VK_ADD: case VK_RETURN: // Num Pad Enter case VK_DECIMAL: // Num Pad . case VK_SEPARATOR: // Num Pad , (on JIS keyboard) case VK_NUMPAD0: case VK_NUMPAD1: case VK_NUMPAD2: case VK_NUMPAD3: case VK_NUMPAD4: case VK_NUMPAD5: case VK_NUMPAD6: case VK_NUMPAD7: case VK_NUMPAD8: case VK_NUMPAD9: return true; } return false; } int windowsKeyCodeForKeyEvent(WebEvent* event) { if (event.keyboardFlags & WebEventKeyboardInputModifierFlagsChanged) return event.keyCode; // There are several kinds of characters for which we produce key code from char code: // 1. Roman letters. Windows keyboard layouts affect both virtual key codes and character codes for these, // so e.g. 'A' gets the same keyCode on QWERTY, AZERTY or Dvorak layouts. // 2. Keys for which there is no known iOS virtual key codes, like PrintScreen. // 3. Certain punctuation keys. On Windows, these are also remapped depending on current keyboard layout, // but see comment in windowsKeyCodeForCharCode(). if (!isKeypadEvent(event) && (event.type == WebEventKeyDown || event.type == WebEventKeyUp)) { // Cmd switches Roman letters for Dvorak-QWERTY layout, so try modified characters first. NSString *string = event.characters; int code = string.length > 0 ? windowsKeyCodeForCharCode([string characterAtIndex:0]) : 0; if (code) return code; // Ctrl+A on an AZERTY keyboard would get VK_Q keyCode if we relied on -[WebEvent keyCode] below. string = event.charactersIgnoringModifiers; code = string.length > 0 ? windowsKeyCodeForCharCode([string characterAtIndex:0]) : 0; if (code) return code; } // Use iOS virtual key code directly for any keys not handled above. // E.g. the key next to Caps Lock has the same Event.keyCode on U.S. keyboard ('A') and on // Russian keyboard (CYRILLIC LETTER EF). return event.keyCode; } class PlatformKeyboardEventBuilder : public PlatformKeyboardEvent { public: PlatformKeyboardEventBuilder(WebEvent *event) { ASSERT(event.type == WebEventKeyDown || event.type == WebEventKeyUp); m_type = (event.type == WebEventKeyUp ? PlatformEvent::KeyUp : PlatformEvent::KeyDown); m_modifiers = modifiersForEvent(event); m_timestamp = WallTime::now(); if (event.keyboardFlags & WebEventKeyboardInputModifierFlagsChanged) { m_text = emptyString(); m_unmodifiedText = emptyString(); m_autoRepeat = false; } else { m_text = event.characters; m_unmodifiedText = event.charactersIgnoringModifiers; m_autoRepeat = event.isKeyRepeating; } m_key = keyForKeyEvent(event); m_code = codeForKeyEvent(event); m_keyIdentifier = keyIdentifierForKeyEvent(event); m_windowsVirtualKeyCode = windowsKeyCodeForKeyEvent(event); m_isKeypad = false; // iOS does not distinguish the numpad. See . m_isSystemKey = false; m_Event = event; // Always use 13 for Enter/Return -- we don't want to use AppKit's different character for Enter. if (m_windowsVirtualKeyCode == '\r') { m_text = "\r"_s; m_unmodifiedText = m_text; } // The adjustments below are only needed in backward compatibility mode, but we cannot tell what mode we are in from here. // Turn 0x7F into 8, because backspace needs to always be 8. if (m_text == "\x7F"_s) m_text = "\x8"_s; if (m_unmodifiedText == "\x7F"_s) m_unmodifiedText = "\x8"_s; // Always use 9 for tab -- we don't want to use AppKit's different character for shift-tab. if (m_windowsVirtualKeyCode == 9) { m_text = "\x9"_s; m_unmodifiedText = m_text; } } }; PlatformKeyboardEvent PlatformEventFactory::createPlatformKeyboardEvent(WebEvent *event) { return PlatformKeyboardEventBuilder(event); } #if ENABLE(TOUCH_EVENTS) static PlatformTouchPoint::TouchPhaseType convertTouchPhase(NSNumber *touchPhaseNumber) { WebEventTouchPhaseType touchPhase = static_cast([touchPhaseNumber unsignedIntValue]); switch (touchPhase) { case WebEventTouchPhaseBegan: return PlatformTouchPoint::TouchPhaseBegan; case WebEventTouchPhaseMoved: return PlatformTouchPoint::TouchPhaseMoved; case WebEventTouchPhaseStationary: return PlatformTouchPoint::TouchPhaseStationary; case WebEventTouchPhaseEnded: return PlatformTouchPoint::TouchPhaseEnded; case WebEventTouchPhaseCancelled: return PlatformTouchPoint::TouchPhaseCancelled; default: ASSERT_NOT_REACHED(); } return PlatformTouchPoint::TouchPhaseBegan; } static PlatformEvent::Type touchEventType(WebEvent *event) { switch (event.type) { case WebEventTouchBegin: return PlatformEvent::TouchStart; case WebEventTouchEnd: return PlatformEvent::TouchEnd; case WebEventTouchCancel: return PlatformEvent::TouchCancel; case WebEventTouchChange: return PlatformEvent::TouchMove; default: ASSERT_NOT_REACHED(); return PlatformEvent::TouchCancel; } } static PlatformTouchPoint::TouchPhaseType touchPhaseFromPlatformEventType(PlatformEvent::Type type) { switch (type) { case PlatformEvent::TouchStart: return PlatformTouchPoint::TouchPhaseBegan; case PlatformEvent::TouchMove: return PlatformTouchPoint::TouchPhaseMoved; case PlatformEvent::TouchEnd: return PlatformTouchPoint::TouchPhaseEnded; default: ASSERT_NOT_REACHED(); return PlatformTouchPoint::TouchPhaseCancelled; } } class PlatformTouchPointBuilder : public PlatformTouchPoint { public: PlatformTouchPointBuilder(unsigned identifier, const IntPoint& location, TouchPhaseType phase) : PlatformTouchPoint(identifier, location, phase) { } }; class PlatformTouchEventBuilder : public PlatformTouchEvent { public: PlatformTouchEventBuilder(WebEvent *event) { m_type = touchEventType(event); m_modifiers = modifiersForEvent(event); m_timestamp = WallTime::fromRawSeconds(event.timestamp); m_gestureScale = event.gestureScale; m_gestureRotation = event.gestureRotation; m_isGesture = event.isGesture; m_position = pointForEvent(event); m_globalPosition = globalPointForEvent(event); unsigned touchCount = event.touchCount; m_touchPoints.reserveInitialCapacity(touchCount); for (unsigned i = 0; i < touchCount; ++i) { unsigned identifier = [(NSNumber *)[event.touchIdentifiers objectAtIndex:i] unsignedIntValue]; IntPoint location = IntPoint([(NSValue *)[event.touchLocations objectAtIndex:i] pointValue]); PlatformTouchPoint::TouchPhaseType touchPhase = convertTouchPhase([event.touchPhases objectAtIndex:i]); m_touchPoints.uncheckedAppend(PlatformTouchPointBuilder(identifier, location, touchPhase)); } } PlatformTouchEventBuilder(PlatformEvent::Type type, IntPoint location) { m_type = type; m_timestamp = WallTime::now(); m_gestureScale = 1; m_gestureRotation = 0; m_isGesture = 0; m_position = location; m_globalPosition = location; m_isPotentialTap = true; unsigned touchCount = 1; m_touchPoints.reserveInitialCapacity(touchCount); for (unsigned i = 0; i < touchCount; ++i) m_touchPoints.uncheckedAppend(PlatformTouchPointBuilder(1, location, touchPhaseFromPlatformEventType(type))); } }; PlatformTouchEvent PlatformEventFactory::createPlatformTouchEvent(WebEvent *event) { return PlatformTouchEventBuilder(event); } PlatformTouchEvent PlatformEventFactory::createPlatformSimulatedTouchEvent(PlatformEvent::Type type, IntPoint location) { return PlatformTouchEventBuilder(type, location); } #endif // ENABLE(TOUCH_EVENTS) } // namespace WebCore #endif // PLATFORM(IOS_FAMILY)