2
0
Эх сурвалжийг харах

intents: add keystrokes intent

Refactor VMKeyboardView so it can be used on macOS for converting
a sequence of text to keyboard strokes.
osy 1 сар өмнө
parent
commit
185ce4f8b5

+ 91 - 0
Intents/UTMInputIntent.swift

@@ -61,6 +61,97 @@ struct UTMSendScanCodeIntent: UTMIntent {
     }
     }
 }
 }
 
 
+@available(iOS 16, macOS 13, tvOS 16, watchOS 9, *)
+struct UTMSendKeystrokesIntent: UTMIntent {
+    static let title: LocalizedStringResource = "Send Keystrokes"
+    static let description = IntentDescription("Send text as a sequence of keystrokes to the virtual machine. Only supported on QEMU backend.")
+    static var parameterSummary: some ParameterSummary {
+        Summary("Send \(\.$keystrokes) to \(\.$vmEntity)") {
+            \.$modifiers
+        }
+    }
+
+    enum Modifier: Int, CaseIterable, AppEnum {
+        case capsLock
+        case shift
+        case control
+        case option
+        case command
+        case escape
+
+        static let typeDisplayRepresentation: TypeDisplayRepresentation =
+            TypeDisplayRepresentation(
+                name: "Modifier Key"
+            )
+
+        static let caseDisplayRepresentations: [Modifier: DisplayRepresentation] = [
+            .capsLock: DisplayRepresentation(title: "Caps Lock (⇪)"),
+            .shift: DisplayRepresentation(title: "Shift (⇧)"),
+            .control: DisplayRepresentation(title: "Control (⌃)"),
+            .option: DisplayRepresentation(title: "Option (⌥)"),
+            .command: DisplayRepresentation(title: "Command (⌘)"),
+            .escape: DisplayRepresentation(title: "Escape (⎋)"),
+        ]
+
+        func toSpiceKeyCode() -> Int32 {
+            switch self {
+            case .capsLock: return 0x3a
+            case .shift: return 0x2a
+            case .control: return 0x1d
+            case .option: return 0x38
+            case .command: return 0x15b
+            case .escape: return 0x01
+            }
+        }
+    }
+
+    @Dependency
+    var data: UTMData
+
+    @Parameter(title: "Virtual Machine", requestValueDialog: "Select a virtual machine")
+    var vmEntity: UTMVirtualMachineEntity
+
+    @Parameter(title: "Keystrokes", description: "Text will be converted to a sequnce of keystrokes.")
+    var keystrokes: String
+
+    @Parameter(title: "Modifiers", description: "The modifier keys will be held down while the keystroke sequence is sent.", default: [])
+    var modifiers: [Modifier]
+
+    @MainActor
+    func perform(with vm: any UTMVirtualMachine, boxed: VMData) async throws -> some IntentResult {
+        guard let vm = vm as? any UTMSpiceVirtualMachine else {
+            throw UTMIntentError.unsupportedBackend
+        }
+        guard let input = vm.ioService?.primaryInput else {
+            throw UTMIntentError.inputHandlerNotAvailable
+        }
+        for modifier in modifiers {
+            input.send(.press, code: modifier.toSpiceKeyCode())
+            try await Task.sleep(nanoseconds: kDelayNs)
+        }
+        let keyboardMap = VMKeyboardMap()
+        await keyboardMap.mapText(keystrokes) { scanCode in
+            input.send(.release, code: scanCodeToSpice(scanCode))
+        } keyDown: { scanCode in
+            input.send(.press, code: scanCodeToSpice(scanCode))
+        }
+        try await Task.sleep(nanoseconds: kDelayNs)
+        for modifier in modifiers {
+            input.send(.release, code: modifier.toSpiceKeyCode())
+            try await Task.sleep(nanoseconds: kDelayNs)
+        }
+        return .result()
+    }
+
+    private func scanCodeToSpice(_ scanCode: Int) -> Int32 {
+        var keyCode = scanCode
+        if (keyCode & 0xFF00) == 0xE000 {
+            keyCode = (keyCode & 0xFF) | 0x100
+        }
+        return Int32(keyCode)
+    }
+}
+
 @available(iOS 16, macOS 13, tvOS 16, watchOS 9, *)
 @available(iOS 16, macOS 13, tvOS 16, watchOS 9, *)
 struct UTMMouseClickIntent: UTMIntent {
 struct UTMMouseClickIntent: UTMIntent {
     static let title: LocalizedStringResource = "Send Mouse Click"
     static let title: LocalizedStringResource = "Send Mouse Click"

+ 37 - 0
Platform/Shared/VMKeyboardMap.h

@@ -0,0 +1,37 @@
+//
+// Copyright © 2025 osy. All rights reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+
+#import <Foundation/Foundation.h>
+
+NS_ASSUME_NONNULL_BEGIN
+
+typedef void (^KeyPressCallback)(NSInteger scanCode);
+
+@interface VMKeyboardMap : NSObject
+
+/// Emulate a sequence of key presses from a sequence of text
+/// 
+/// The processing will happen in a separate dispatch queue in order to handle delay between key strokes.
+/// - Parameters:
+///   - text: Text containing keypresses
+///   - keyUp: Called for each key up
+///   - keyDown: Called for each key down
+///   - completion: Completion handler after all text is processed
+- (void)mapText:(NSString *)text toKeyUp:(KeyPressCallback)keyUp keyDown:(KeyPressCallback)keyDown completion:(void (^)(void))completion;
+
+@end
+
+NS_ASSUME_NONNULL_END

+ 421 - 0
Platform/Shared/VMKeyboardMap.m

@@ -0,0 +1,421 @@
+//
+// Copyright © 2025 osy. All rights reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+
+// Parts taken from launcher-mobile
+/*
+ * launcher-mobile: a multiplatform flexVDI/SPICE client
+ *
+ * Copyright (C) 2016 flexVDI (Flexible Software Solutions S.L.)
+ *
+ * This file is part of launcher-mobile.
+ *
+ * launcher-mobile is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * launcher-mobile is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with launcher-mobile.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#import "VMKeyboardMap.h"
+#import "ctype.h"
+
+typedef struct {
+    char tc;
+    int prekey;
+    int special_prekey;
+    int special_key;
+    int key;
+} key_mapping_t;
+
+typedef struct {
+    char tc;
+    char ext1;
+    char ext2;
+    int prekey;
+    int special_prekey;
+    int special_key;
+    int key;
+} ext_key_mapping_t;
+
+const ext_key_mapping_t pc104_es_ext[] = {
+    {194, 161, 0, 0x0, 0x0, 0x0, 0x0d}, // ¡
+    {194, 191, 0, 0x0, 0x0, 0x36, 0x0d}, // ¿
+    {194, 186, 0, 0x0, 0x0, 0x0, 0x29}, // º
+    {194, 170, 0, 0x0, 0x0, 0x36, 0x29}, // ª
+    {194, 169, 0, 0x0, 0x0, 0x1d, 0x2e}, // Ctrl + C
+    {194, 174, 0, 0x0, 0x0, 0x1d, 0x13},
+
+    {195, 177, 0, 0x0, 0x0, 0x0, 0x27}, // ñ
+    {195, 145, 0, 0x0, 0x0, 0x36, 0x27}, // ñ
+    {195, 167, 0, 0x0, 0x0, 0x0, 0x2b}, // ç
+    {195, 135, 0, 0x0, 0x0, 0x36, 0x2b}, // Ç
+    {195, 161, 0, 0x28, 0x0, 0x0, 0x1e}, // á
+    {195, 169, 0, 0x28, 0x0, 0x0, 0x12}, // é
+    {195, 173, 0, 0x28, 0x0, 0x0, 0x17}, // í
+    {195, 179, 0, 0x28, 0x0, 0x0, 0x18}, // ó
+    {195, 186, 0, 0x28, 0x0, 0x0, 0x16}, // ú
+    {195, 188, 0, 0x28, 0x36, 0x0, 0x16}, // ü
+    {195, 129, 0, 0x28, 0x0, 0x36, 0x1e}, // Á
+    {195, 137, 0, 0x28, 0x0, 0x36, 0x12}, // É
+    {195, 141, 0, 0x28, 0x0, 0x36, 0x17}, // Í
+    {195, 147, 0, 0x28, 0x0, 0x36, 0x18}, // Ó
+    {195, 154, 0, 0x28, 0x0, 0x36, 0x16}, // Ú
+    {195, 156, 0, 0x28, 0x36, 0x36, 0x16}, // Ü
+    {195, 159, 0, 0x0, 0x0, 0x1d, 0x30}, // Ctrl + B
+
+    {197, 147, 0, 0x0, 0x0, 0x38, 0x0f}, // Alt + Tab
+
+    {198, 146, 0, 0x0, 0x0, 0x1d, 0x21}, // Ctrl + F
+
+    {206, 169, 0, 0x0, 0x0, 0x1d, 0x2c}, // Ctrl + Z
+
+    {226, 130, 172, 0x0, 0x0, 0x138, 0x12}, // Euro
+    {226, 137, 164, 0x0, 0x0, 0x138, 0x29}, // Backslash
+    {226, 136, 145, 0x0, 0x0, 0x1d, 0x2d}, // Ctrl + X
+    {226, 136, 154, 0x0, 0x0, 0x1d, 0x2f}, // Ctrl + V
+    {226, 136, 130, 0x0, 0x0, 0x1d, 0x20} // Ctrl + D
+};
+
+const key_mapping_t pc104_es[] = {
+    {9, 0x0, 0x0, 0x0, 0xf}, // Tab
+    {'a', 0x0, 0x0, 0x0, 0x1e},
+    {'b', 0x0, 0x0, 0x0, 0x30},
+    {'c', 0x0, 0x0, 0x0, 0x2e},
+    {'d', 0x0, 0x0, 0x0, 0x20},
+    {'e', 0x0, 0x0, 0x0, 0x12},
+    {'f', 0x0, 0x0, 0x0, 0x21},
+    {'g', 0x0, 0x0, 0x0, 0x22},
+    {'h', 0x0, 0x0, 0x0, 0x23},
+    {'i', 0x0, 0x0, 0x0, 0x17},
+    {'j', 0x0, 0x0, 0x0, 0x24},
+    {'k', 0x0, 0x0, 0x0, 0x25},
+    {'l', 0x0, 0x0, 0x0, 0x26},
+    {'m', 0x0, 0x0, 0x0, 0x32},
+    {'n', 0x0, 0x0, 0x0, 0x31},
+    {'o', 0x0, 0x0, 0x0, 0x18},
+    {'p', 0x0, 0x0, 0x0, 0x19},
+    {'q', 0x0, 0x0, 0x0, 0x10},
+    {'r', 0x0, 0x0, 0x0, 0x13},
+    {'s', 0x0, 0x0, 0x0, 0x1f},
+    {'t', 0x0, 0x0, 0x0, 0x14},
+    {'u', 0x0, 0x0, 0x0, 0x16},
+    {'v', 0x0, 0x0, 0x0, 0x2f},
+    {'w', 0x0, 0x0, 0x0, 0x11},
+    {'x', 0x0, 0x0, 0x0, 0x2d},
+    {'y', 0x0, 0x0, 0x0, 0x15},
+    {'z', 0x0, 0x0, 0x0, 0x2c},
+    {'1', 0x0, 0x0, 0x0, 0x02},
+    {'2', 0x0, 0x0, 0x0, 0x03},
+    {'3', 0x0, 0x0, 0x0, 0x04},
+    {'4', 0x0, 0x0, 0x0, 0x05},
+    {'5', 0x0, 0x0, 0x0, 0x06},
+    {'6', 0x0, 0x0, 0x0, 0x07},
+    {'7', 0x0, 0x0, 0x0, 0x08},
+    {'8', 0x0, 0x0, 0x0, 0x09},
+    {'9', 0x0, 0x0, 0x0, 0x0a},
+    {'0', 0x0, 0x0, 0x0, 0x0b},
+    {' ', 0x0, 0x0, 0x0, 0x39},
+    {'!', 0x0, 0x0, 0x36, 0x02},
+    {'@', 0x0, 0x0, 0x138, 0x03},
+    {'"', 0x0, 0x0, 0x36, 0x03},
+    {'\'', 0x0, 0x0, 0x0, 0x0c},
+    {'#', 0x0, 0x0, 0x138, 0x04},
+    {'~', 0x0, 0x0, 0x138, 0x05},
+    {'$', 0x0, 0x0, 0x36, 0x05},
+    {'%', 0x0, 0x0, 0x36, 0x06},
+    {'&', 0x0, 0x0, 0x36, 0x07},
+    {'/', 0x0, 0x0, 0x36, 0x08},
+    {'(', 0x0, 0x0, 0x36, 0x09},
+    {')', 0x0, 0x0, 0x36, 0x0a},
+    {'=', 0x0, 0x0, 0x36, 0x0b},
+    {'?', 0x0, 0x0, 0x36, 0x0c},
+    {'-', 0x0, 0x0, 0x0, 0x35},
+    {'_', 0x0, 0x0, 0x36, 0x35},
+    {';', 0x0, 0x0, 0x36, 0x33},
+    {',', 0x0, 0x0, 0x0, 0x33},
+    {'.', 0x0, 0x0, 0x0, 0x34},
+    {':', 0x0, 0x0, 0x36, 0x34},
+    {'{', 0x0, 0x0, 0x138, 0x28},
+    {'}', 0x0, 0x0, 0x138, 0x2b},
+    {'[', 0x0, 0x0, 0x138, 0x1a},
+    {']', 0x0, 0x0, 0x138, 0x1b},
+    {'*', 0x0, 0x0, 0x36, 0x1b},
+    {'+', 0x0, 0x0, 0x0, 0x1b},
+    {'\\', 0x0, 0x0, 0x138, 0x29},
+    {'|', 0x0, 0x0, 0x138, 0x02},
+    {'^', 0x0, 0x0, 0x36, 0x1a},
+    {'`', 0x0, 0x0, 0x0, 0x1a},
+    {'<', 0x0, 0x0, 0x0, 0x56},
+    {'>', 0x0, 0x0, 0x36, 0x56},
+    {'\r', 0x0, 0x0, 0x0, 0x1c},
+    {'\n', 0x0, 0x0, 0x0, 0x1c},
+    {'\'', 0x0, 0x0, 0x0, 0x28},
+    {'"', 0x0, 0x0, 0x36, 0x28},
+    {'\t', 0x0, 0x0, 0x0, 0x0F},
+    {'\b', 0x0, 0x0, 0x0, 0x0E},
+};
+
+const ext_key_mapping_t pc104_us_ext[] = {
+    {195, 167, 0, 0x0, 0x0, 0x1d, 0x2e}, // Ctrl + C
+    {197, 147, 0, 0x0, 0x0, 0x38, 0x0f}, // Alt + Tab
+    {198, 146, 0, 0x0, 0x0, 0x1d, 0x21}, // Ctrl + F
+    {206, 169, 0, 0x0, 0x0, 0x1d, 0x2c}, // Ctrl + Z
+    {226, 137, 136, 0x0, 0x0, 0x1d, 0x2d}, // Ctrl + X
+    {226, 136, 154, 0x0, 0x0, 0x1d, 0x2f}, // Ctrl + V
+    {226, 136, 171, 0x0, 0x0, 0x1d, 0x30}, // Ctrl + B
+    {226, 136, 130, 0x0, 0x0, 0x1d, 0x20} // Ctrl + D
+};
+
+const key_mapping_t pc104_us[] = {
+    {9, 0x0, 0x0, 0x0, 0xf}, // Tab
+    {'a', 0x0, 0x0, 0x0, 0x1e},
+    {'b', 0x0, 0x0, 0x0, 0x30},
+    {'c', 0x0, 0x0, 0x0, 0x2e},
+    {'d', 0x0, 0x0, 0x0, 0x20},
+    {'e', 0x0, 0x0, 0x0, 0x12},
+    {'f', 0x0, 0x0, 0x0, 0x21},
+    {'g', 0x0, 0x0, 0x0, 0x22},
+    {'h', 0x0, 0x0, 0x0, 0x23},
+    {'i', 0x0, 0x0, 0x0, 0x17},
+    {'j', 0x0, 0x0, 0x0, 0x24},
+    {'k', 0x0, 0x0, 0x0, 0x25},
+    {'l', 0x0, 0x0, 0x0, 0x26},
+    {'m', 0x0, 0x0, 0x0, 0x32},
+    {'n', 0x0, 0x0, 0x0, 0x31},
+    {'o', 0x0, 0x0, 0x0, 0x18},
+    {'p', 0x0, 0x0, 0x0, 0x19},
+    {'q', 0x0, 0x0, 0x0, 0x10},
+    {'r', 0x0, 0x0, 0x0, 0x13},
+    {'s', 0x0, 0x0, 0x0, 0x1f},
+    {'t', 0x0, 0x0, 0x0, 0x14},
+    {'u', 0x0, 0x0, 0x0, 0x16},
+    {'v', 0x0, 0x0, 0x0, 0x2f},
+    {'w', 0x0, 0x0, 0x0, 0x11},
+    {'x', 0x0, 0x0, 0x0, 0x2d},
+    {'y', 0x0, 0x0, 0x0, 0x15},
+    {'z', 0x0, 0x0, 0x0, 0x2c},
+    {'1', 0x0, 0x0, 0x0, 0x02},
+    {'2', 0x0, 0x0, 0x0, 0x03},
+    {'3', 0x0, 0x0, 0x0, 0x04},
+    {'4', 0x0, 0x0, 0x0, 0x05},
+    {'5', 0x0, 0x0, 0x0, 0x06},
+    {'6', 0x0, 0x0, 0x0, 0x07},
+    {'7', 0x0, 0x0, 0x0, 0x08},
+    {'8', 0x0, 0x0, 0x0, 0x09},
+    {'9', 0x0, 0x0, 0x0, 0x0a},
+    {'0', 0x0, 0x0, 0x0, 0x0b},
+    {' ', 0x0, 0x0, 0x0, 0x39},
+    {'!', 0x0, 0x0, 0x36, 0x02},
+    {'@', 0x0, 0x0, 0x36, 0x03},
+    {'"', 0x0, 0x0, 0x36, 0x28},
+    {'\'', 0x0, 0x0, 0x0, 0x28},
+    {'#', 0x0, 0x0, 0x36, 0x04},
+    {'~', 0x0, 0x0, 0x36, 0x29},
+    {'$', 0x0, 0x0, 0x36, 0x05},
+    {'%', 0x0, 0x0, 0x36, 0x06},
+    {'&', 0x0, 0x0, 0x36, 0x08},
+    {'/', 0x0, 0x0, 0x0, 0x35},
+    {'(', 0x0, 0x0, 0x36, 0x0a},
+    {')', 0x0, 0x0, 0x36, 0x0b},
+    {'=', 0x0, 0x0, 0x0, 0x0d},
+    {'+', 0x0, 0x0, 0x36, 0x0d},
+    {'?', 0x0, 0x0, 0x36, 0x35},
+    {'-', 0x0, 0x0, 0x0, 0x0c},
+    {'_', 0x0, 0x0, 0x36, 0x0c},
+    {';', 0x0, 0x0, 0x0, 0x27},
+    {',', 0x0, 0x0, 0x0, 0x33},
+    {'.', 0x0, 0x0, 0x0, 0x34},
+    {':', 0x0, 0x0, 0x36, 0x27},
+    {'{', 0x0, 0x0, 0x36, 0x1a},
+    {'}', 0x0, 0x0, 0x36, 0x1b},
+    {'[', 0x0, 0x0, 0x0, 0x1a},
+    {']', 0x0, 0x0, 0x0, 0x1b},
+    {'*', 0x0, 0x0, 0x36, 0x09},
+    {'+', 0x0, 0x0, 0x0, 0x1b},
+    {'\\', 0x0, 0x0, 0x0, 0x2b},
+    {'|', 0x0, 0x0, 0x36, 0x2b},
+    {'^', 0x0, 0x0, 0x36, 0x07},
+    {'`', 0x0, 0x0, 0x0, 0x29},
+    {'<', 0x0, 0x0, 0x36, 0x33},
+    {'>', 0x0, 0x0, 0x36, 0x34},
+    {'\r', 0x0, 0x0, 0x0, 0x1c},
+    {'\n', 0x0, 0x0, 0x0, 0x1c},
+    {'\'', 0x0, 0x0, 0x0, 0x28},
+    {'"', 0x0, 0x0, 0x36, 0x28},
+    {'\t', 0x0, 0x0, 0x0, 0x0F},
+    {'\b', 0x0, 0x0, 0x0, 0x0E},
+};
+
+static int indexForChar(const key_mapping_t *table, size_t table_len, char tc) {
+    int i;
+
+    for (i = 0; i < table_len; i++) {
+        if (tc == table[i].tc) {
+            return i;
+        }
+    }
+
+    return -1;
+}
+
+static int indexForExtChar(const ext_key_mapping_t *table, size_t table_len, char tc, char ext1, char ext2) {
+    int i;
+
+    for (i = 0; i < table_len; i++) {
+        if (tc == table[i].tc &&
+            ext1 == table[i].ext1) {
+            if (ext2 != 0 && ext2 == table[i].ext2) {
+                return i;
+            } else if (ext2 == 0) {
+                return i;
+            }
+        }
+    }
+
+    return -1;
+}
+
+@implementation VMKeyboardMap {
+    const key_mapping_t *_map;
+    size_t _map_len;
+    const ext_key_mapping_t *_ext_map;
+    size_t _ext_map_len;
+}
+
+- (void)configureTables {
+    NSString *language = [[NSLocale preferredLanguages] objectAtIndex:0];
+
+    if ([language isEqual:@"es-ES"]) {
+        _map = pc104_es;
+        _map_len = sizeof(pc104_es)/sizeof(pc104_es[0]);
+        _ext_map = pc104_es_ext;
+        _ext_map_len = sizeof(pc104_es_ext)/sizeof(pc104_es_ext[0]);
+    } else {
+        _map = pc104_us;
+        _map_len = sizeof(pc104_us)/sizeof(pc104_us[0]);
+        _ext_map = pc104_us_ext;
+        _ext_map_len = sizeof(pc104_us_ext)/sizeof(pc104_us_ext[0]);
+    }
+}
+
+- (void)mapText:(NSString *)text toKeyUp:(KeyPressCallback)keyUp keyDown:(KeyPressCallback)keyDown completion:(void (^)(void))completion {
+    dispatch_async(dispatch_get_global_queue(QOS_CLASS_USER_INITIATED, 0), ^{
+        [text enumerateSubstringsInRange:NSMakeRange(0, text.length) options:NSStringEnumerationByComposedCharacterSequences usingBlock:^(NSString * _Nullable substring, NSRange substringRange, NSRange enclosingRange, BOOL * _Nonnull stop) {
+            const char *seq = [substring UTF8String];
+            [self mapUTF8Sequence:seq toKeyUp:keyUp keyDown:keyDown];
+            // we need to pause a bit or the keypress will be too fast!
+            [NSThread sleepForTimeInterval:0.001f];
+        }];
+        completion();
+    });
+}
+
+- (void)mapUTF8Sequence:(const char *)ctext toKeyUp:(KeyPressCallback)keyUp keyDown:(KeyPressCallback)keyDown {
+    unsigned long ctext_len = strlen(ctext);
+    //UTMLog(@"ctext length=%lu\n", ctext_len);
+    unsigned char tc = ctext[0];
+
+    int keycode = 0;
+    int special = 0;
+    int prekey = 0;
+    int prekey_special = 0;
+    int is_upper = false;
+    int index = -1;
+
+    if (!_map) {
+        [self configureTables];
+    }
+
+    if (isalpha(tc)) {
+        if (isupper(tc)) {
+            tc = tolower(tc);
+            is_upper = true;
+        }
+    }
+
+    switch (ctext_len) {
+        case 1:
+            //UTMLog(@"char=%d\n", tc);
+            index = indexForChar(_map, _map_len, tc);
+            if (index != -1) {
+                keycode = _map[index].key;
+                special = _map[index].special_key;
+            }
+            break;
+        case 2:
+            //UTMLog(@"char=%d\n", tc);
+            //UTMLog(@"ext1=%d\n", (unsigned char) ctext[1]);
+            index = indexForExtChar(_ext_map, _ext_map_len, tc, ctext[1], 0);
+            if (index != -1) {
+                keycode = _ext_map[index].key;
+                special = _ext_map[index].special_key;
+                prekey = _ext_map[index].prekey;
+                prekey_special = _ext_map[index].special_prekey;
+            }
+            break;
+        case 3:
+            //UTMLog(@"char=%d\n", tc);
+            //UTMLog(@"ext1=%d\n", (unsigned char) ctext[1]);
+            //UTMLog(@"ext2=%d\n", (unsigned char) ctext[2]);
+            index = indexForExtChar(_ext_map, _ext_map_len, tc, ctext[1], ctext[2]);
+            if (index != -1) {
+                keycode = _ext_map[index].key;
+                special = _ext_map[index].special_key;
+                prekey = _ext_map[index].prekey;
+                prekey_special = _ext_map[index].special_prekey;
+            }
+            break;
+    }
+
+    if (keycode) {
+        if (is_upper) {
+            special = 0x2A;
+        }
+
+        if (prekey) {
+            if (prekey_special) {
+                keyDown(special);
+            }
+            keyDown(prekey);
+            [NSThread sleepForTimeInterval:0.05f];
+            keyUp(prekey);
+            if (prekey_special) {
+                keyUp(special);
+            }
+        }
+
+        if (special) {
+            keyDown(special);
+        }
+
+        keyDown(keycode);
+        [NSThread sleepForTimeInterval:0.05f];
+        keyUp(keycode);
+
+        if (special) {
+            keyUp(special);
+        }
+    }
+}
+
+@end

+ 12 - 394
Platform/iOS/Display/VMKeyboardView.m

@@ -14,294 +14,16 @@
 // limitations under the License.
 // limitations under the License.
 //
 //
 
 
-// Parts taken from launcher-mobile
-/*
- * launcher-mobile: a multiplatform flexVDI/SPICE client
- *
- * Copyright (C) 2016 flexVDI (Flexible Software Solutions S.L.)
- *
- * This file is part of launcher-mobile.
- *
- * launcher-mobile is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation, either version 2 of the License, or
- * (at your option) any later version.
- *
- * launcher-mobile is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with launcher-mobile.  If not, see <http://www.gnu.org/licenses/>.
- */
-
 #import "VMKeyboardView.h"
 #import "VMKeyboardView.h"
-#import "UTMLogging.h"
-#import "ctype.h"
-
-typedef struct {
-    char tc;
-    int prekey;
-    int special_prekey;
-    int special_key;
-    int key;
-} key_mapping_t;
-
-typedef struct {
-    char tc;
-    char ext1;
-    char ext2;
-    int prekey;
-    int special_prekey;
-    int special_key;
-    int key;
-} ext_key_mapping_t;
-
-const ext_key_mapping_t pc104_es_ext[] = {
-    {194, 161, 0, 0x0, 0x0, 0x0, 0x0d}, // ¡
-    {194, 191, 0, 0x0, 0x0, 0x36, 0x0d}, // ¿
-    {194, 186, 0, 0x0, 0x0, 0x0, 0x29}, // º
-    {194, 170, 0, 0x0, 0x0, 0x36, 0x29}, // ª
-    {194, 169, 0, 0x0, 0x0, 0x1d, 0x2e}, // Ctrl + C
-    {194, 174, 0, 0x0, 0x0, 0x1d, 0x13},
-    
-    {195, 177, 0, 0x0, 0x0, 0x0, 0x27}, // ñ
-    {195, 145, 0, 0x0, 0x0, 0x36, 0x27}, // ñ
-    {195, 167, 0, 0x0, 0x0, 0x0, 0x2b}, // ç
-    {195, 135, 0, 0x0, 0x0, 0x36, 0x2b}, // Ç
-    {195, 161, 0, 0x28, 0x0, 0x0, 0x1e}, // á
-    {195, 169, 0, 0x28, 0x0, 0x0, 0x12}, // é
-    {195, 173, 0, 0x28, 0x0, 0x0, 0x17}, // í
-    {195, 179, 0, 0x28, 0x0, 0x0, 0x18}, // ó
-    {195, 186, 0, 0x28, 0x0, 0x0, 0x16}, // ú
-    {195, 188, 0, 0x28, 0x36, 0x0, 0x16}, // ü
-    {195, 129, 0, 0x28, 0x0, 0x36, 0x1e}, // Á
-    {195, 137, 0, 0x28, 0x0, 0x36, 0x12}, // É
-    {195, 141, 0, 0x28, 0x0, 0x36, 0x17}, // Í
-    {195, 147, 0, 0x28, 0x0, 0x36, 0x18}, // Ó
-    {195, 154, 0, 0x28, 0x0, 0x36, 0x16}, // Ú
-    {195, 156, 0, 0x28, 0x36, 0x36, 0x16}, // Ü
-    {195, 159, 0, 0x0, 0x0, 0x1d, 0x30}, // Ctrl + B
-    
-    {197, 147, 0, 0x0, 0x0, 0x38, 0x0f}, // Alt + Tab
-    
-    {198, 146, 0, 0x0, 0x0, 0x1d, 0x21}, // Ctrl + F
-    
-    {206, 169, 0, 0x0, 0x0, 0x1d, 0x2c}, // Ctrl + Z
-    
-    {226, 130, 172, 0x0, 0x0, 0x138, 0x12}, // Euro
-    {226, 137, 164, 0x0, 0x0, 0x138, 0x29}, // Backslash
-    {226, 136, 145, 0x0, 0x0, 0x1d, 0x2d}, // Ctrl + X
-    {226, 136, 154, 0x0, 0x0, 0x1d, 0x2f}, // Ctrl + V
-    {226, 136, 130, 0x0, 0x0, 0x1d, 0x20} // Ctrl + D
-};
-
-const key_mapping_t pc104_es[] = {
-    {9, 0x0, 0x0, 0x0, 0xf}, // Tab
-    {'a', 0x0, 0x0, 0x0, 0x1e},
-    {'b', 0x0, 0x0, 0x0, 0x30},
-    {'c', 0x0, 0x0, 0x0, 0x2e},
-    {'d', 0x0, 0x0, 0x0, 0x20},
-    {'e', 0x0, 0x0, 0x0, 0x12},
-    {'f', 0x0, 0x0, 0x0, 0x21},
-    {'g', 0x0, 0x0, 0x0, 0x22},
-    {'h', 0x0, 0x0, 0x0, 0x23},
-    {'i', 0x0, 0x0, 0x0, 0x17},
-    {'j', 0x0, 0x0, 0x0, 0x24},
-    {'k', 0x0, 0x0, 0x0, 0x25},
-    {'l', 0x0, 0x0, 0x0, 0x26},
-    {'m', 0x0, 0x0, 0x0, 0x32},
-    {'n', 0x0, 0x0, 0x0, 0x31},
-    {'o', 0x0, 0x0, 0x0, 0x18},
-    {'p', 0x0, 0x0, 0x0, 0x19},
-    {'q', 0x0, 0x0, 0x0, 0x10},
-    {'r', 0x0, 0x0, 0x0, 0x13},
-    {'s', 0x0, 0x0, 0x0, 0x1f},
-    {'t', 0x0, 0x0, 0x0, 0x14},
-    {'u', 0x0, 0x0, 0x0, 0x16},
-    {'v', 0x0, 0x0, 0x0, 0x2f},
-    {'w', 0x0, 0x0, 0x0, 0x11},
-    {'x', 0x0, 0x0, 0x0, 0x2d},
-    {'y', 0x0, 0x0, 0x0, 0x15},
-    {'z', 0x0, 0x0, 0x0, 0x2c},
-    {'1', 0x0, 0x0, 0x0, 0x02},
-    {'2', 0x0, 0x0, 0x0, 0x03},
-    {'3', 0x0, 0x0, 0x0, 0x04},
-    {'4', 0x0, 0x0, 0x0, 0x05},
-    {'5', 0x0, 0x0, 0x0, 0x06},
-    {'6', 0x0, 0x0, 0x0, 0x07},
-    {'7', 0x0, 0x0, 0x0, 0x08},
-    {'8', 0x0, 0x0, 0x0, 0x09},
-    {'9', 0x0, 0x0, 0x0, 0x0a},
-    {'0', 0x0, 0x0, 0x0, 0x0b},
-    {' ', 0x0, 0x0, 0x0, 0x39},
-    {'!', 0x0, 0x0, 0x36, 0x02},
-    {'@', 0x0, 0x0, 0x138, 0x03},
-    {'"', 0x0, 0x0, 0x36, 0x03},
-    {'\'', 0x0, 0x0, 0x0, 0x0c},
-    {'#', 0x0, 0x0, 0x138, 0x04},
-    {'~', 0x0, 0x0, 0x138, 0x05},
-    {'$', 0x0, 0x0, 0x36, 0x05},
-    {'%', 0x0, 0x0, 0x36, 0x06},
-    {'&', 0x0, 0x0, 0x36, 0x07},
-    {'/', 0x0, 0x0, 0x36, 0x08},
-    {'(', 0x0, 0x0, 0x36, 0x09},
-    {')', 0x0, 0x0, 0x36, 0x0a},
-    {'=', 0x0, 0x0, 0x36, 0x0b},
-    {'?', 0x0, 0x0, 0x36, 0x0c},
-    {'-', 0x0, 0x0, 0x0, 0x35},
-    {'_', 0x0, 0x0, 0x36, 0x35},
-    {';', 0x0, 0x0, 0x36, 0x33},
-    {',', 0x0, 0x0, 0x0, 0x33},
-    {'.', 0x0, 0x0, 0x0, 0x34},
-    {':', 0x0, 0x0, 0x36, 0x34},
-    {'{', 0x0, 0x0, 0x138, 0x28},
-    {'}', 0x0, 0x0, 0x138, 0x2b},
-    {'[', 0x0, 0x0, 0x138, 0x1a},
-    {']', 0x0, 0x0, 0x138, 0x1b},
-    {'*', 0x0, 0x0, 0x36, 0x1b},
-    {'+', 0x0, 0x0, 0x0, 0x1b},
-    {'\\', 0x0, 0x0, 0x138, 0x29},
-    {'|', 0x0, 0x0, 0x138, 0x02},
-    {'^', 0x0, 0x0, 0x36, 0x1a},
-    {'`', 0x0, 0x0, 0x0, 0x1a},
-    {'<', 0x0, 0x0, 0x0, 0x56},
-    {'>', 0x0, 0x0, 0x36, 0x56},
-    {'\r', 0x0, 0x0, 0x0, 0x1c},
-    {'\n', 0x0, 0x0, 0x0, 0x1c},
-    {'\'', 0x0, 0x0, 0x0, 0x28},
-    {'"', 0x0, 0x0, 0x36, 0x28},
-    {'\t', 0x0, 0x0, 0x0, 0x0F},
-    {'\b', 0x0, 0x0, 0x0, 0x0E},
-};
+#import "VMKeyboardMap.h"
 
 
-const ext_key_mapping_t pc104_us_ext[] = {
-    {195, 167, 0, 0x0, 0x0, 0x1d, 0x2e}, // Ctrl + C
-    {197, 147, 0, 0x0, 0x0, 0x38, 0x0f}, // Alt + Tab
-    {198, 146, 0, 0x0, 0x0, 0x1d, 0x21}, // Ctrl + F
-    {206, 169, 0, 0x0, 0x0, 0x1d, 0x2c}, // Ctrl + Z
-    {226, 137, 136, 0x0, 0x0, 0x1d, 0x2d}, // Ctrl + X
-    {226, 136, 154, 0x0, 0x0, 0x1d, 0x2f}, // Ctrl + V
-    {226, 136, 171, 0x0, 0x0, 0x1d, 0x30}, // Ctrl + B
-    {226, 136, 130, 0x0, 0x0, 0x1d, 0x20} // Ctrl + D
-};
+@interface VMKeyboardView ()
 
 
-const key_mapping_t pc104_us[] = {
-    {9, 0x0, 0x0, 0x0, 0xf}, // Tab
-    {'a', 0x0, 0x0, 0x0, 0x1e},
-    {'b', 0x0, 0x0, 0x0, 0x30},
-    {'c', 0x0, 0x0, 0x0, 0x2e},
-    {'d', 0x0, 0x0, 0x0, 0x20},
-    {'e', 0x0, 0x0, 0x0, 0x12},
-    {'f', 0x0, 0x0, 0x0, 0x21},
-    {'g', 0x0, 0x0, 0x0, 0x22},
-    {'h', 0x0, 0x0, 0x0, 0x23},
-    {'i', 0x0, 0x0, 0x0, 0x17},
-    {'j', 0x0, 0x0, 0x0, 0x24},
-    {'k', 0x0, 0x0, 0x0, 0x25},
-    {'l', 0x0, 0x0, 0x0, 0x26},
-    {'m', 0x0, 0x0, 0x0, 0x32},
-    {'n', 0x0, 0x0, 0x0, 0x31},
-    {'o', 0x0, 0x0, 0x0, 0x18},
-    {'p', 0x0, 0x0, 0x0, 0x19},
-    {'q', 0x0, 0x0, 0x0, 0x10},
-    {'r', 0x0, 0x0, 0x0, 0x13},
-    {'s', 0x0, 0x0, 0x0, 0x1f},
-    {'t', 0x0, 0x0, 0x0, 0x14},
-    {'u', 0x0, 0x0, 0x0, 0x16},
-    {'v', 0x0, 0x0, 0x0, 0x2f},
-    {'w', 0x0, 0x0, 0x0, 0x11},
-    {'x', 0x0, 0x0, 0x0, 0x2d},
-    {'y', 0x0, 0x0, 0x0, 0x15},
-    {'z', 0x0, 0x0, 0x0, 0x2c},
-    {'1', 0x0, 0x0, 0x0, 0x02},
-    {'2', 0x0, 0x0, 0x0, 0x03},
-    {'3', 0x0, 0x0, 0x0, 0x04},
-    {'4', 0x0, 0x0, 0x0, 0x05},
-    {'5', 0x0, 0x0, 0x0, 0x06},
-    {'6', 0x0, 0x0, 0x0, 0x07},
-    {'7', 0x0, 0x0, 0x0, 0x08},
-    {'8', 0x0, 0x0, 0x0, 0x09},
-    {'9', 0x0, 0x0, 0x0, 0x0a},
-    {'0', 0x0, 0x0, 0x0, 0x0b},
-    {' ', 0x0, 0x0, 0x0, 0x39},
-    {'!', 0x0, 0x0, 0x36, 0x02},
-    {'@', 0x0, 0x0, 0x36, 0x03},
-    {'"', 0x0, 0x0, 0x36, 0x28},
-    {'\'', 0x0, 0x0, 0x0, 0x28},
-    {'#', 0x0, 0x0, 0x36, 0x04},
-    {'~', 0x0, 0x0, 0x36, 0x29},
-    {'$', 0x0, 0x0, 0x36, 0x05},
-    {'%', 0x0, 0x0, 0x36, 0x06},
-    {'&', 0x0, 0x0, 0x36, 0x08},
-    {'/', 0x0, 0x0, 0x0, 0x35},
-    {'(', 0x0, 0x0, 0x36, 0x0a},
-    {')', 0x0, 0x0, 0x36, 0x0b},
-    {'=', 0x0, 0x0, 0x0, 0x0d},
-    {'+', 0x0, 0x0, 0x36, 0x0d},
-    {'?', 0x0, 0x0, 0x36, 0x35},
-    {'-', 0x0, 0x0, 0x0, 0x0c},
-    {'_', 0x0, 0x0, 0x36, 0x0c},
-    {';', 0x0, 0x0, 0x0, 0x27},
-    {',', 0x0, 0x0, 0x0, 0x33},
-    {'.', 0x0, 0x0, 0x0, 0x34},
-    {':', 0x0, 0x0, 0x36, 0x27},
-    {'{', 0x0, 0x0, 0x36, 0x1a},
-    {'}', 0x0, 0x0, 0x36, 0x1b},
-    {'[', 0x0, 0x0, 0x0, 0x1a},
-    {']', 0x0, 0x0, 0x0, 0x1b},
-    {'*', 0x0, 0x0, 0x36, 0x09},
-    {'+', 0x0, 0x0, 0x0, 0x1b},
-    {'\\', 0x0, 0x0, 0x0, 0x2b},
-    {'|', 0x0, 0x0, 0x36, 0x2b},
-    {'^', 0x0, 0x0, 0x36, 0x07},
-    {'`', 0x0, 0x0, 0x0, 0x29},
-    {'<', 0x0, 0x0, 0x36, 0x33},
-    {'>', 0x0, 0x0, 0x36, 0x34},
-    {'\r', 0x0, 0x0, 0x0, 0x1c},
-    {'\n', 0x0, 0x0, 0x0, 0x1c},
-    {'\'', 0x0, 0x0, 0x0, 0x28},
-    {'"', 0x0, 0x0, 0x36, 0x28},
-    {'\t', 0x0, 0x0, 0x0, 0x0F},
-    {'\b', 0x0, 0x0, 0x0, 0x0E},
-};
+@property (nullable, nonatomic) VMKeyboardMap *keyboardMap;
 
 
-static int indexForChar(const key_mapping_t *table, size_t table_len, char tc) {
-    int i;
-    
-    for (i = 0; i < table_len; i++) {
-        if (tc == table[i].tc) {
-            return i;
-        }
-    }
-    
-    return -1;
-}
-
-static int indexForExtChar(const ext_key_mapping_t *table, size_t table_len, char tc, char ext1, char ext2) {
-    int i;
-    
-    for (i = 0; i < table_len; i++) {
-        if (tc == table[i].tc &&
-            ext1 == table[i].ext1) {
-            if (ext2 != 0 && ext2 == table[i].ext2) {
-                return i;
-            } else if (ext2 == 0) {
-                return i;
-            }
-        }
-    }
-    
-    return -1;
-}
+@end
 
 
-@implementation VMKeyboardView {
-    const key_mapping_t *_map;
-    size_t _map_len;
-    const ext_key_mapping_t *_ext_map;
-    size_t _ext_map_len;
-}
+@implementation VMKeyboardView
 
 
 - (UIKeyboardType)keyboardType {
 - (UIKeyboardType)keyboardType {
     return UIKeyboardTypeASCIICapable;
     return UIKeyboardTypeASCIICapable;
@@ -331,22 +53,6 @@ static int indexForExtChar(const ext_key_mapping_t *table, size_t table_len, cha
     return UITextSmartInsertDeleteTypeNo;
     return UITextSmartInsertDeleteTypeNo;
 }
 }
 
 
-- (void)configureTables {
-    NSString *language = [[NSLocale preferredLanguages] objectAtIndex:0];
-    
-    if ([language isEqual:@"es-ES"]) {
-        _map = pc104_es;
-        _map_len = sizeof(pc104_es)/sizeof(pc104_es[0]);
-        _ext_map = pc104_es_ext;
-        _ext_map_len = sizeof(pc104_es_ext)/sizeof(pc104_es_ext[0]);
-    } else {
-        _map = pc104_us;
-        _map_len = sizeof(pc104_us)/sizeof(pc104_us[0]);
-        _ext_map = pc104_us_ext;
-        _ext_map_len = sizeof(pc104_us_ext)/sizeof(pc104_us_ext[0]);
-    }
-}
-
 - (BOOL)hasText {
 - (BOOL)hasText {
     return YES;
     return YES;
 }
 }
@@ -358,102 +64,14 @@ static int indexForExtChar(const ext_key_mapping_t *table, size_t table_len, cha
 }
 }
 
 
 - (void)insertText:(nonnull NSString *)text {
 - (void)insertText:(nonnull NSString *)text {
-    dispatch_async(dispatch_get_global_queue(QOS_CLASS_USER_INITIATED, 0), ^{
-        [text enumerateSubstringsInRange:NSMakeRange(0, text.length) options:NSStringEnumerationByComposedCharacterSequences usingBlock:^(NSString * _Nullable substring, NSRange substringRange, NSRange enclosingRange, BOOL * _Nonnull stop) {
-            const char *seq = [substring UTF8String];
-            [self insertUTF8Sequence:seq];
-            // we need to pause a bit or the keypress will be too fast!
-            [NSThread sleepForTimeInterval:0.001f];
-        }];
-    });
-}
-
-- (void)insertUTF8Sequence:(const char *)ctext {
-    unsigned long ctext_len = strlen(ctext);
-    //UTMLog(@"ctext length=%lu\n", ctext_len);
-    unsigned char tc = ctext[0];
-    
-    int keycode = 0;
-    int special = 0;
-    int prekey = 0;
-    int prekey_special = 0;
-    int is_upper = false;
-    int index = -1;
-    
-    if (!_map) {
-        [self configureTables];
-    }
-    
-    if (isalpha(tc)) {
-        if (isupper(tc)) {
-            tc = tolower(tc);
-            is_upper = true;
-        }
-    }
-    
-    switch (ctext_len) {
-        case 1:
-            //UTMLog(@"char=%d\n", tc);
-            index = indexForChar(_map, _map_len, tc);
-            if (index != -1) {
-                keycode = _map[index].key;
-                special = _map[index].special_key;
-            }
-            break;
-        case 2:
-            //UTMLog(@"char=%d\n", tc);
-            //UTMLog(@"ext1=%d\n", (unsigned char) ctext[1]);
-            index = indexForExtChar(_ext_map, _ext_map_len, tc, ctext[1], 0);
-            if (index != -1) {
-                keycode = _ext_map[index].key;
-                special = _ext_map[index].special_key;
-                prekey = _ext_map[index].prekey;
-                prekey_special = _ext_map[index].special_prekey;
-            }
-            break;
-        case 3:
-            //UTMLog(@"char=%d\n", tc);
-            //UTMLog(@"ext1=%d\n", (unsigned char) ctext[1]);
-            //UTMLog(@"ext2=%d\n", (unsigned char) ctext[2]);
-            index = indexForExtChar(_ext_map, _ext_map_len, tc, ctext[1], ctext[2]);
-            if (index != -1) {
-                keycode = _ext_map[index].key;
-                special = _ext_map[index].special_key;
-                prekey = _ext_map[index].prekey;
-                prekey_special = _ext_map[index].special_prekey;
-            }
-            break;
-    }
-    
-    if (keycode) {
-        if (is_upper) {
-            special = 0x2A;
-        }
-        
-        if (prekey) {
-            if (prekey_special) {
-                [self.delegate keyboardView:self didPressKeyDown:special];
-            }
-            [self.delegate keyboardView:self didPressKeyDown:prekey];
-            [NSThread sleepForTimeInterval:0.05f];
-            [self.delegate keyboardView:self didPressKeyUp:prekey];
-            if (prekey_special) {
-                [self.delegate keyboardView:self didPressKeyUp:special];
-            }
-        }
-        
-        if (special) {
-            [self.delegate keyboardView:self didPressKeyDown:special];
-        }
-        
-        [self.delegate keyboardView:self didPressKeyDown:keycode];
-        [NSThread sleepForTimeInterval:0.05f];
-        [self.delegate keyboardView:self didPressKeyUp:keycode];
-        
-        if (special) {
-            [self.delegate keyboardView:self didPressKeyUp:special];
-        }
+    if (!self.keyboardMap) {
+        self.keyboardMap = [[VMKeyboardMap alloc] init];
     }
     }
+    [self.keyboardMap mapText:text toKeyUp:^(NSInteger scanCode) {
+        [self.delegate keyboardView:self didPressKeyUp:(int)scanCode];
+    } keyDown:^(NSInteger scanCode) {
+        [self.delegate keyboardView:self didPressKeyDown:(int)scanCode];
+    } completion:^(){}];
 }
 }
 
 
 - (BOOL)canBecomeFirstResponder {
 - (BOOL)canBecomeFirstResponder {

+ 1 - 0
Services/Swift-Bridging-Header.h

@@ -27,6 +27,7 @@
 #include "UTMLegacyQemuConfigurationPortForward.h"
 #include "UTMLegacyQemuConfigurationPortForward.h"
 #include "UTMLogging.h"
 #include "UTMLogging.h"
 #include "UTMASIFImage.h"
 #include "UTMASIFImage.h"
+#include "VMKeyboardMap.h"
 #if !defined(WITH_REMOTE)
 #if !defined(WITH_REMOTE)
 #include "UTMProcess.h"
 #include "UTMProcess.h"
 #include "UTMQemuSystem.h"
 #include "UTMQemuSystem.h"

+ 12 - 0
UTM.xcodeproj/project.pbxproj

@@ -676,6 +676,10 @@
 		CE88A1602E24B2B400EAA28E /* UTMInputIntent.swift in Sources */ = {isa = PBXBuildFile; fileRef = CE88A15F2E24B2B400EAA28E /* UTMInputIntent.swift */; };
 		CE88A1602E24B2B400EAA28E /* UTMInputIntent.swift in Sources */ = {isa = PBXBuildFile; fileRef = CE88A15F2E24B2B400EAA28E /* UTMInputIntent.swift */; };
 		CE88A1612E24B2B400EAA28E /* UTMInputIntent.swift in Sources */ = {isa = PBXBuildFile; fileRef = CE88A15F2E24B2B400EAA28E /* UTMInputIntent.swift */; };
 		CE88A1612E24B2B400EAA28E /* UTMInputIntent.swift in Sources */ = {isa = PBXBuildFile; fileRef = CE88A15F2E24B2B400EAA28E /* UTMInputIntent.swift */; };
 		CE88A1622E24B2B400EAA28E /* UTMInputIntent.swift in Sources */ = {isa = PBXBuildFile; fileRef = CE88A15F2E24B2B400EAA28E /* UTMInputIntent.swift */; };
 		CE88A1622E24B2B400EAA28E /* UTMInputIntent.swift in Sources */ = {isa = PBXBuildFile; fileRef = CE88A15F2E24B2B400EAA28E /* UTMInputIntent.swift */; };
+		CE88A1652E24E4C000EAA28E /* VMKeyboardMap.m in Sources */ = {isa = PBXBuildFile; fileRef = CE88A1642E24E4C000EAA28E /* VMKeyboardMap.m */; };
+		CE88A1662E24E4C000EAA28E /* VMKeyboardMap.m in Sources */ = {isa = PBXBuildFile; fileRef = CE88A1642E24E4C000EAA28E /* VMKeyboardMap.m */; };
+		CE88A1672E24E4C000EAA28E /* VMKeyboardMap.m in Sources */ = {isa = PBXBuildFile; fileRef = CE88A1642E24E4C000EAA28E /* VMKeyboardMap.m */; };
+		CE88A1682E24E4C000EAA28E /* VMKeyboardMap.m in Sources */ = {isa = PBXBuildFile; fileRef = CE88A1642E24E4C000EAA28E /* VMKeyboardMap.m */; };
 		CE89CB0E2B8B1B5A006B2CC2 /* VisionKeyboardKit in Frameworks */ = {isa = PBXBuildFile; platformFilters = (xros, ); productRef = CE89CB0D2B8B1B5A006B2CC2 /* VisionKeyboardKit */; };
 		CE89CB0E2B8B1B5A006B2CC2 /* VisionKeyboardKit in Frameworks */ = {isa = PBXBuildFile; platformFilters = (xros, ); productRef = CE89CB0D2B8B1B5A006B2CC2 /* VisionKeyboardKit */; };
 		CE89CB102B8B1B6A006B2CC2 /* VisionKeyboardKit in Frameworks */ = {isa = PBXBuildFile; platformFilters = (xros, ); productRef = CE89CB0F2B8B1B6A006B2CC2 /* VisionKeyboardKit */; };
 		CE89CB102B8B1B6A006B2CC2 /* VisionKeyboardKit in Frameworks */ = {isa = PBXBuildFile; platformFilters = (xros, ); productRef = CE89CB0F2B8B1B6A006B2CC2 /* VisionKeyboardKit */; };
 		CE89CB122B8B1B7A006B2CC2 /* VisionKeyboardKit in Frameworks */ = {isa = PBXBuildFile; platformFilters = (xros, ); productRef = CE89CB112B8B1B7A006B2CC2 /* VisionKeyboardKit */; };
 		CE89CB122B8B1B7A006B2CC2 /* VisionKeyboardKit in Frameworks */ = {isa = PBXBuildFile; platformFilters = (xros, ); productRef = CE89CB112B8B1B7A006B2CC2 /* VisionKeyboardKit */; };
@@ -1986,6 +1990,8 @@
 		CE88A1532E247CCE00EAA28E /* UTMIntent.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UTMIntent.swift; sourceTree = "<group>"; };
 		CE88A1532E247CCE00EAA28E /* UTMIntent.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UTMIntent.swift; sourceTree = "<group>"; };
 		CE88A1572E247D0100EAA28E /* UTMActionIntent.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UTMActionIntent.swift; sourceTree = "<group>"; };
 		CE88A1572E247D0100EAA28E /* UTMActionIntent.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UTMActionIntent.swift; sourceTree = "<group>"; };
 		CE88A15F2E24B2B400EAA28E /* UTMInputIntent.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UTMInputIntent.swift; sourceTree = "<group>"; };
 		CE88A15F2E24B2B400EAA28E /* UTMInputIntent.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UTMInputIntent.swift; sourceTree = "<group>"; };
+		CE88A1632E24E4C000EAA28E /* VMKeyboardMap.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = VMKeyboardMap.h; sourceTree = "<group>"; };
+		CE88A1642E24E4C000EAA28E /* VMKeyboardMap.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = VMKeyboardMap.m; sourceTree = "<group>"; };
 		CE928C2926ABE6690099F293 /* UTMAppleVirtualMachine.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UTMAppleVirtualMachine.swift; sourceTree = "<group>"; };
 		CE928C2926ABE6690099F293 /* UTMAppleVirtualMachine.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UTMAppleVirtualMachine.swift; sourceTree = "<group>"; };
 		CE928C3026ACCDEA0099F293 /* VMAppleRemovableDrivesView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VMAppleRemovableDrivesView.swift; sourceTree = "<group>"; };
 		CE928C3026ACCDEA0099F293 /* VMAppleRemovableDrivesView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VMAppleRemovableDrivesView.swift; sourceTree = "<group>"; };
 		CE9375A024BBDDD10074066F /* VMConfigDriveDetailsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VMConfigDriveDetailsView.swift; sourceTree = "<group>"; };
 		CE9375A024BBDDD10074066F /* VMConfigDriveDetailsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VMConfigDriveDetailsView.swift; sourceTree = "<group>"; };
@@ -3045,6 +3051,8 @@
 				84909A8C27CACD5C005605F1 /* UTMPlaceholderVMView.swift */,
 				84909A8C27CACD5C005605F1 /* UTMPlaceholderVMView.swift */,
 				84909A9027CADAE0005605F1 /* UTMUnavailableVMView.swift */,
 				84909A9027CADAE0005605F1 /* UTMUnavailableVMView.swift */,
 				CED779E42C78C82A00EB82AE /* UTMTips.swift */,
 				CED779E42C78C82A00EB82AE /* UTMTips.swift */,
+				CE88A1632E24E4C000EAA28E /* VMKeyboardMap.h */,
+				CE88A1642E24E4C000EAA28E /* VMKeyboardMap.m */,
 			);
 			);
 			path = Shared;
 			path = Shared;
 			sourceTree = "<group>";
 			sourceTree = "<group>";
@@ -3734,6 +3742,7 @@
 				CE2D930524AD46670059923A /* VMDisplayMetalViewController.m in Sources */,
 				CE2D930524AD46670059923A /* VMDisplayMetalViewController.m in Sources */,
 				848D99C428670F650055C215 /* UTMQemuConfiguration+Arguments.swift in Sources */,
 				848D99C428670F650055C215 /* UTMQemuConfiguration+Arguments.swift in Sources */,
 				CEB63A7624F4654400CAF323 /* Main.swift in Sources */,
 				CEB63A7624F4654400CAF323 /* Main.swift in Sources */,
+				CE88A1652E24E4C000EAA28E /* VMKeyboardMap.m in Sources */,
 				848F71E8277A2A4E006A0240 /* UTMSerialPort.swift in Sources */,
 				848F71E8277A2A4E006A0240 /* UTMSerialPort.swift in Sources */,
 				CE2D958F24AD4FF00059923A /* VMCardView.swift in Sources */,
 				CE2D958F24AD4FF00059923A /* VMCardView.swift in Sources */,
 				8432329028C2CDAD00CFBC97 /* VMNavigationListView.swift in Sources */,
 				8432329028C2CDAD00CFBC97 /* VMNavigationListView.swift in Sources */,
@@ -3833,6 +3842,7 @@
 				83C15C5F26CC441500ADFD45 /* KeyCodeMap.swift in Sources */,
 				83C15C5F26CC441500ADFD45 /* KeyCodeMap.swift in Sources */,
 				84C505AE28C588EC007CE8FF /* SizeTextField.swift in Sources */,
 				84C505AE28C588EC007CE8FF /* SizeTextField.swift in Sources */,
 				CE25125329C80A18000790AB /* UTMScriptingCloneCommand.swift in Sources */,
 				CE25125329C80A18000790AB /* UTMScriptingCloneCommand.swift in Sources */,
+				CE88A1682E24E4C000EAA28E /* VMKeyboardMap.m in Sources */,
 				CE2D956A24AD4F990059923A /* VMPlaceholderView.swift in Sources */,
 				CE2D956A24AD4F990059923A /* VMPlaceholderView.swift in Sources */,
 				CEF0306626A2AFDF00667B63 /* VMWizardOSLinuxView.swift in Sources */,
 				CEF0306626A2AFDF00667B63 /* VMWizardOSLinuxView.swift in Sources */,
 				CEE7E938287CFDB100282049 /* UTMLegacyQemuConfiguration+Constants.m in Sources */,
 				CEE7E938287CFDB100282049 /* UTMLegacyQemuConfiguration+Constants.m in Sources */,
@@ -3984,6 +3994,7 @@
 				CE231D432BDDF280006D6DC3 /* UTMDonateView.swift in Sources */,
 				CE231D432BDDF280006D6DC3 /* UTMDonateView.swift in Sources */,
 				CE65BABF26A4D8DD0001BD6B /* VMConfigDisplayConsoleView.swift in Sources */,
 				CE65BABF26A4D8DD0001BD6B /* VMConfigDisplayConsoleView.swift in Sources */,
 				848F71ED277A2F47006A0240 /* UTMSerialPortDelegate.swift in Sources */,
 				848F71ED277A2F47006A0240 /* UTMSerialPortDelegate.swift in Sources */,
+				CE88A1662E24E4C000EAA28E /* VMKeyboardMap.m in Sources */,
 				83A004BA26A8CC95001AC09E /* UTMDownloadTask.swift in Sources */,
 				83A004BA26A8CC95001AC09E /* UTMDownloadTask.swift in Sources */,
 				8401868A288A44C20050AC51 /* VMWindowState.swift in Sources */,
 				8401868A288A44C20050AC51 /* VMWindowState.swift in Sources */,
 				843BF8312844853E0029D60D /* UTMQemuConfigurationNetwork.swift in Sources */,
 				843BF8312844853E0029D60D /* UTMQemuConfigurationNetwork.swift in Sources */,
@@ -4249,6 +4260,7 @@
 				CEF7F5F02AEEDCC400E34952 /* NumberTextField.swift in Sources */,
 				CEF7F5F02AEEDCC400E34952 /* NumberTextField.swift in Sources */,
 				CEF7F5F12AEEDCC400E34952 /* VMToolbarOrnamentModifier.swift in Sources */,
 				CEF7F5F12AEEDCC400E34952 /* VMToolbarOrnamentModifier.swift in Sources */,
 				CEF01DB42B6724A300725A0F /* UTMSpiceVirtualMachine.swift in Sources */,
 				CEF01DB42B6724A300725A0F /* UTMSpiceVirtualMachine.swift in Sources */,
+				CE88A1672E24E4C000EAA28E /* VMKeyboardMap.m in Sources */,
 				CEF7F5F22AEEDCC400E34952 /* VMCommands.swift in Sources */,
 				CEF7F5F22AEEDCC400E34952 /* VMCommands.swift in Sources */,
 				CEF7F5F32AEEDCC400E34952 /* UTMLegacyQemuConfiguration+Networking.m in Sources */,
 				CEF7F5F32AEEDCC400E34952 /* UTMLegacyQemuConfiguration+Networking.m in Sources */,
 				CEF7F5F42AEEDCC400E34952 /* VMConfirmActionModifier.swift in Sources */,
 				CEF7F5F42AEEDCC400E34952 /* VMConfirmActionModifier.swift in Sources */,